最大子数组问题

2023-11-02

最大子数组问题

本文只是做一个记录,更细致的思路请查看算法导论

最大子数组结构体

typedef struct {
    int low, high, sum;
} SubArray;

暴力求解
计算所有的数组区间的和进而得到最大的子数组,算法复杂度为θ(n²)。这种方法在小规模的数据表现很好,d但是在大规模数据中则很糟糕,但可用作分治算法的改进。实现的思路是先计算从以i为起始的最大子数组,然后从[1..n],[2..n] .. [n..n]中找到最大的子数组。

SubArray MaxSubArray_BruteForce(int low, int high, int A[]) {
    int sum;
    int max_subarray = 0;
    SubArray *S = (SubArray *)calloc((high - low + 2), sizeof(SubArray));

    for (int i = low, k = 0; i <= high; ++i, ++k) {
        S[k].sum = INT_MIN;
        S[k].low = i;
        sum = 0;
        for (int j = i; j <= high; ++j) {   // 计算从以`i`为起始的最大子数组
            sum += A[j];
            if (S[k].sum < sum) {
                S[k].sum = sum;
                S[k].high = j;
            }   
        }   
        if (S[max_subarray].sum <= S[k].sum)    // = 是出现 {0,0,1}的情况精确的计算子数组为[2,2]{1}
            max_subarray = i - low;
    }   

    return S[max_subarray];
}

分治算法
从分治算法的时间分析来看,

T(n)={θ(1)aθ(n/a)+θ(n)ififn=1n>1 T ( n ) = { θ ( 1 ) i f n = 1 a θ ( n / a ) + θ ( n ) i f n > 1

如果要寻找[low..high]的最大子数组,使用分治的技术意味我们要将数组分为规模尽可能相等的两个子数组(上a为2),寻找出中间的位置mid,再考虑求解子数组[low..mid]和[mid+1..high]。数组[low..high]的最大子数组[i..j]必然是下面三种情况:
- 完全位于子数组[low..mid]中,因此 low <= i <= high <= mid
- 完全位于子数组[mid+1..high]中,因此 mid+1 <= i <= j <= high
- 跨域了中点mid,因此位于 low <= i <= mid <= j <= high

且规定跨越中点的数组 必须包含mid 和 mid + 1

递归的求解子数组[low..mid]和[mid+1..high],减小子问题问题的规模,最后寻找跨越中点的最大子数组,与归并排序的归并过程相似,我们把寻找跨越中点的最大子数组作为子问题的合并过程。简单的逻辑为
1. 寻找子数组[low..mid]的最大子数组
2. 寻找子数组[mid+1..high]的最大子数组
3. 寻找[low..mid mid+1..high]的最大子数组
4. 返回三个最大子数组中的子数组。注:这里的比较必须要用加上等号,这样可以跳过0更精确的求到最大子数组

SubArray MaxCrossSubArray(int low, int mid, int high, int A[]) {
    int left_sum, right_sum, sum;
    SubArray S;

    left_sum = right_sum = INT_MIN;
    sum = 0;
    for (int i = mid; i >= low; i--) {
        sum += A[i];
        if (left_sum < sum) {
            S.low = i;
            left_sum = sum;
        }
    }   

    sum = 0;
    for (int j = mid + 1; j <= high; j++) {
        sum += A[j];
        if (right_sum < sum) {
            S.high = j;
            right_sum = sum;
        }
    }   

    S.sum = left_sum + right_sum;
    return S;
}

SubArray MaxSubArray_DivideConquer(int low, int high, int A[]) {
    if (low == high) {
        SubArray S;
        S.low = low;
        S.high = high;
        S.sum = A[low];
        return S;
    }

    int mid = (low + high) / 2;
    SubArray L = MaxSubArray_DivideConquer(low, mid, A);
    SubArray R = MaxSubArray_DivideConquer(mid + 1, high, A);
    SubArray M = MaxCrossSubArray(low, mid, high, A);
    return Max3(L, R, M);
}

改进后的递归算法
前面提到暴力求解虽然在大规模数据表现非常差,但是在小规模的求解时优势很大。当子问题的规模小于某个值n时,我们用暴力算法求解

// 暴力算法和分治算法在 40 左右达到性能交叉点
// 在规模在 10 左右,暴力算法大幅领先分治算法
SubArray MaxSubArray_Synergy(int low, int high, int A[]) {
    if (high - low < 10)
        return MaxSubArray_BruteForce(low, high, A);
    int mid = (low + high) / 2;
    SubArray L = MaxSubArray_Synergy(low, mid, A);
    SubArray R = MaxSubArray_Synergy(mid + 1, high, A);
    SubArray M = MaxCrossSubArray(low, mid, high, A);
    return Max3(L, R, M);
}

线性算法
已知[1..j]的最大子数组,计算[1..j+1]最大子数组的思路:[1..j+1]的最大子数组要么是[1..j]的最大子数组,要么是某个子数组[i..j+1] (1 <= i <= j+1 )。具体实现如注释所述

SubArray MaxSubArray_Linear(int low, int high, int A[]) {
    SubArray S = {-1, -1, INT_MIN};
    int sum = 0;
    int slow = -1;
    for (int i = low; i <= high; ++i) {
        if (sum > 0) {          // 加上A[i]后当前最大子数组为正,中间的非负数项继续保留
            sum += A[i];
        } else {                // 重新寻找最大子数组
            sum = A[i];
            slow = i;
        }

        if (sum > S.sum) {      // 新的最大子数组大于旧最大子数组
            S.low = slow;
            S.high = i;
            S.sum = sum;
        }
    }
    return S;
} 

写代码时碰到的一些问题
- 刚开始我的递归实现是还有一个SubArray指针的,但是在内存里面真正的SubArray只有一份,每一次计算SubArray变量都在改变。其实我们只需要子数组的下标与和,所以直接在函数内定义等到函数结束栈内存回收也没有关系,因为已经返回。
- 暴力算法刚开始的i都是从0开始,如果只是单纯的调用不会产生问题,但是当递归算法小规模取调用的情况下就会出现段错误(越界了)
- 最大子数组大小一样但是几种算法的下标不一样,出现这种的问题是没有把0过滤,如在线性算法中 应该为 if (sum > 0) 而不应该为 if (sum >= 0),递归算法中的Max3比较最大子数组的问题则应该加上等号判断。还有一种是出现这种情况{1, 1, -2, 1, 1}, 这几种算法出现了两种答案[0-1]和[3-4],这个问题目前还没有解决

四种算法的时间复杂度:

BruteForceDivideConquerBruteForce+DivideConquerLinearθ(n2)θ(nlog(n))θ(nlog(n))θ(n) { B r u t e F o r c e θ ( n 2 ) D i v i d e C o n q u e r θ ( n l o g ( n ) ) B r u t e F o r c e + D i v i d e C o n q u e r θ ( n l o g ( n ) ) L i n e a r θ ( n )

处理10w(为什么不是更大的数,因为暴力算法太慢了),四种算法的时间大概是
- 暴力算法 15.12s
- 简单递归算法 0.15s
- 优化后的递归算法 0.12s
- 线性算法忽略不计 0.003s

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

最大子数组问题 的相关文章

  • 若干经典基础算法题目练习

    练习1 判断是否为素数 ConsoleAppIsPrime1 cpp 定义控制台应用程序的入口点 函数功能 判断一个输入的数是否为素数 函数原形 bool Prime int x 参数 int x 将要判断的数 返回值 bool型变量 判断
  • 区间图着色问题

    这是算法导论贪心算法一章的一个习题 题目描述 假定有一组活动 我们需要将它们安排到一些教室 任意活动都可以在任意教室进行 我们希望使用最少的教室完成所有的活动 设计一个高效的贪心算法求每个活动应该在哪个教室进行 这个问题称为区间图着色问题
  • 算法导论 第三节 分治法

    分治法 1 分 把一个大问题分成若干个小问题 即原问题的n变小 2 治 递归的解决每一个子问题 然后把这些子问题的解合并成整个大问题的解 归并排序 1 一分为二 2 递归的对每一个子数组进行排序 3 合并 线性的n时间内就可以完成 归并排序
  • 算法导论 学习笔记 第四章 递归

    n logba Theta n log b a lga Theta lga 这一章主要是介绍了三种求运行时间的方法 这三种方法都是解决递归问题的 就像分治法这种问题的算法的运行时间 这三种方法分别是替换法 递归树方法 主方法 4 1替换法
  • NP完全性理论与近似算法

    一 NP完全性理论 1 在图灵机计算模型中 移动函数 是单值的 即对于Q Tk中的每一个值 当它属于 的定义域时Q T L R S k 中只有唯一的值与之对应 称这种图灵机为确定性图灵机 简记为DTM Deterministic Turin
  • 二分搜索树

    经典写法 内心os 就是有点繁琐hh include
  • ​LeetCode刷题实战88:合并两个有序数组

    算法的重要性 我就不多说了吧 想去大厂 就必须要经过基础知识和业务逻辑面试 算法面试 所以 为了提高大家的算法能力 这个公众号后续每天带大家做一道算法题 题目就从LeetCode上面选 今天和大家聊的问题叫做 合并两个有序数组 我们先来看题
  • ​LeetCode刷题实战214:最短回文串

    算法的重要性 我就不多说了吧 想去大厂 就必须要经过基础知识和业务逻辑面试 算法面试 所以 为了提高大家的算法能力 这个公众号后续每天带大家做一道算法题 题目就从LeetCode上面选 今天和大家聊的问题叫做 最短回文串 我们先来看题面 h
  • 写一个个人认为比较详细的adaboost算法

    最近在看机器学习中adaboost adaptive boostint 算法部分的内容 在csdn上面查找一番发现 好像没有讲的特别的详尽的 当然可能是我人品不佳 所以没有找到 为了防止同样的事情发生在其他人的身上 所以就写了这篇博文 尽量
  • NlogN复杂度寻找数组中两个数字和等于给定值

    算法导论 22页2 3 7 描述一个运行时间为O nlogn 的算法 找出n个元素的S数组中是否存在两个元素相加等于给定x值 AC解 a 1 3 6 7 9 15 29 def find2sumx nums x nums sort le r
  • 算法导论——插入排序——伪代码和Java实现

    第二章第一节 插入排序 我们首先介绍插入排序 相信大部分人都打过扑克牌 许多人喜欢发一张牌就拿一张牌到手上 并且按顺序来放好牌 开始时我们左手为空 牌在桌子上 然后我们每次从桌子上拿走一张牌并将它插入左手中的位置 为了找到一张牌的正确位置
  • 《算法导论》常见算法总结

    前言 本篇文章总结中用到很多其他博客内容 本来想附上原作链接 但很久了未找到 这里关于原创性均来源于原作者 分治法 分治策略的思想 顾名思义 分治是将一个原始问题分解成多个子问题 而子问题的形式和原问题一样 只是规模更小而已 通过子问题的求
  • 图的深度优先遍历和广度优先遍历

    1 深度优先遍历 DFS 1 从某个顶点V出发 访问顶点并标记为已访问 2 访问V的其中一个邻接点 通常最左边的那个 如果没有访问过 访问该顶点并标记为已访问 然后再访问该顶点的邻接点 递归执行 先一直往后走 如果该顶点已访问过 退回上一个
  • 算法导论——分治法、归并排序——伪代码和Java实现

    第二章第三节 分治法 我们首先先介绍分治法 分治法的思想 将原问题分解为几个规模较小但类似于原问题的子问题 递归地求解这些子问题 然后在合并这些子问题的解来解决原问题的解 还是拿扑克牌举例子 假设桌上有两堆牌面朝上的牌 牌面朝上 有值 每堆
  • 算法导论之单源最短路径(Bellman-Ford和Dijkstra)

    Bellman Ford 一 Bellman Ford算法的思想 Bellman Ford算法 以下简称BF算法 用于解决边的权重可以为负值的单源最短路径 它通过对边进行松弛操作逐渐降低从源结点s到各结点v的最短路径估计值v d 直到该估计
  • 算法导论 练习 2.2

    2 2 1 答案 n theta n 渐进符号的定义会在第三章里明确给出 所以这里就不写证明了 详细证明见第三章习题 好多好多啊 2 2 2 选择排序 数据结构课程基本排序算法之一 代码 SELECTION SORT A n length
  • 红黑树(算法导论版)

    1 定义 1 每个节点是红色或者黑色的 2 根节点是黑色的 3 所有叶子结点 NIL 都是黑色的 4 如果一个节点是红色 则它的两个子节点都是黑色的 5 对每个节点 从该节点到其所有后代叶节点的简单路径上 均包含相同数目的黑色节点 2 性质
  • 算法导论三-分治法

    分治法 简单说 分治法即分而治之 即将问题分化为小问题来处理 简化起来看有二到三个步骤 分 将问题分解为若干子问题 复杂度n降低 治 递归解决子问题 合 合并子问题的解 常见分治法的递归式为 T n 2T n 2 n 即分为两个解法一样的子
  • 分治法 ( Divide And Conquer ) 详解

    文章目录 引言 分治法的范式 递归式 求解递归式的三种方法 代入法 递归树法 主方法 引言 在这篇 blog 中 我首先会介绍一下分治法的范式 接着给出它的递归式通式 最后我会介绍三种方法 代入法 递归树 和主方法 求解递归式 分治法的范式
  • 最常用的五大算法

    一 贪心算法 贪心算法 又称贪婪算法 是指 在对问题求解时 总是做出在当前看来是最好的选择 也就是说 不从整体最优上加以考虑 他所做出的仅是在某种意义上的局部最优解 贪心算法不是对所有问题都能得到整体最优解 但对范围相当广泛的许多问题他能产

随机推荐

  • 黑马Python教程实战项目--美多商城(五)

    一 用户基本信息 首先需要为用户模型类 也就是用户数据表 补充一个邮箱验证状态字段 用来记录用户的邮箱是否验证成功 然后新建用户中心视图类 继承LoginRequiredMixin和View类 在子路由中添加路由 定义get方法 在requ
  • 虚拟机非正常关机,重启网卡

    在命令行运行以下命令即可重新连接上网络 sudo service network manager stop sudo rm var lib NetworkManager NetworkManager state sudo service n
  • Google云

    Google 云计算 Cloud Computing 是个新概念 但也不过是分布式处理 Distributed Computing 并行处理 Parallel Computing 和网格计算 Grid Computing 的发展 也许是一个
  • 余弦计算相似度度量

    目录 pytorch 余弦相似度 余弦计算相似度度量 pytorch 余弦相似度 余弦相似度1到 1之间 1代表正相关 0代表不相关 1代表负相关 def l2 norm input axis 1 norm torch norm input
  • [改善Java代码]适当设置阻塞队列长度

    阻塞队列BlockingQueue扩展了Queue Collection接口 对元素的插入和提取使用了 阻塞 处理 我们知道Collection下的实现类一般都采用了长度自行管理方式 也就是变长
  • adamax参数_5 Optimizer-庖丁解牛之pytorch

    优化器是机器学习的很重要部分 但是在很多机器学习和深度学习的应用中 我们发现用的最多的优化器是 Adam 为什么呢 pytorch有多少优化器 我什么时候使用其他优化器 本文将详细讲述 在torch optim 包中有如下优化器torch
  • 从ChatGPT到ChatCAD:基于大型语言模型的医学图像交互式计算机辅助诊断

    1 导读 2023年年初最火热的话题之一就是OpenAI的ChatGPT1 给人类带来了巨大的冲击 1月底 美国 财富 杂志2 3月合刊的封面文章 全球爆红的ChatGPT是如何诞生的 引爆了创投圈 在这巨大的浪潮冲击下 如何让其在医疗领域
  • 某公司员工的工资计算方法如下:一周内工作时间不超过40小时,按正常工作时间计酬;超出40小时的工作时间部分,按正常工作时间报酬的1.5倍计酬。员工按进公司时间分为新职工和老职工,进公司不少于5年的员工

    简单计算工资 张景敏 2021 1 22 include
  • 多物理场仿真 Chrono 转向机构

    为方便查阅 此文是原网站文档翻译 如有侵权 请与本人联系 目录 转向垂臂 齿条小齿轮 旋转臂 基础类别ChSteering规定 任何衍生的转向机构类别 转向机构模板 都提供了一个转向连杆体 可转向悬架可以连接到该转向连杆体上 通常通过悬架的
  • chrome下载

    https zhuanlan zhihu com p 438998185
  • Docker基础修炼1--Docker简介及快速入门体验

    本文作为Docker基础系列第一篇文章 将详细阐述和分析三个问题 Docker是什么 为什么要用Docker 如何快速掌握Docker技术 本系列文章中Docker的用法演示是基于CentOS7进行 因此假设读者已经掌握了初步的Linux知
  • 使用选择排序和二分法对传入的数组进行排序和查找

    选择排序和二分法 使用二分法查找数组中某个值得位置是要在数组提前排好序的前提下才能使用 所以要将数组进行排序 数组排序有冒泡排序 选择排序 插入排序等 今天我们使用选择排序对数组进行排序 测试类代码如下图 运行结果如下图 位置为i 1 所以
  • JSP基础理论

    来自 千峰涛哥B站资料 一 JSP概述 1 1 Servlet使用的不足 Servlet是一个动态网页技术 客户端通过请求Servlet类可以相应给客户端一个动态网页 但是Servlet在使用过程中有什么不足之处呢 开发方式麻烦 继承Htt
  • 在Java基础上对比学习C#基本语法

    文章目录 一 引包 二 构造函数 三 析构函数 四 C 数据类型 五 加框 boxing 和消框 unboxing 六 运算符 七 控制语句 八 类的继承 九 方法参数的种类 十 操作符重载 十一 this关键字 十二 类的多态 十三 抽象
  • 目标检测模型的评价指标 mAP

    在使用机器学习解决实际问题时 通常有很多模型可用 每个模型都有自己的怪癖 quirks 并且基于各种因素 性能会有所不同 模型性能的评定都是在某个数据集上进行的 通常这个数据集被称为 validation 或 test 数据集 模型性能的评
  • Java+Swing形成GUI图像界面

    一 Swing 简介 Swing 主要用来开发 GUI 程序 GUI Graphical User Interface 即图形用户界面 Java 中针对 GUI 设计提供了丰富的类库 这些类分别位于 java awt 和 java swin
  • Android高仿qq及微信底部菜单的几种实现方式

    文章目录 导航类型 第一种方式 侧滑菜单 底部导航 已经实现聊天 表情 图片 位置 语音等信息的发送 第二种方式 Fragment PopupWindow仿QQ空间最新版底部菜单栏 第三种方式 FragmentTabHost实现qq底部Ta
  • (一)在ubuntu20.04安装VPN服务

    很多时候需要从世界各地来访问公司服务器 电脑 工厂设备 实现方式有很多种 主要分为VPN和内网穿透方式 但是他们俩都存在一些问题 例如内网穿透主要利用外网IP 端口映射内网IP地址 端口方式 需要在设备端 电脑端装软件 例如frp方式需要在
  • 5.C++力扣刷题645

    题目 集合 s 包含从 1 到 n 的整数 不幸的是 因为数据错误 导致集合里面某一个数字复制了成了集合里面的另外一个数字的值 导致集合丢失了一个数字并且有一个数字重复 给定一个数组 nums 代表了集合 S 发生错误后的结果 请你找出重复
  • 最大子数组问题

    最大子数组问题 本文只是做一个记录 更细致的思路请查看算法导论 最大子数组结构体 typedef struct int low high sum SubArray 暴力求解 计算所有的数组区间的和进而得到最大的子数组 算法复杂度为 n 这种