首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

最大子段和(动态规划)

  • 25-04-25 02:29
  • 3146
  • 12668
blog.csdn.net

原理

  1. 最大子段和问题定义
    给定一个整数序列,要求找出该序列中具有最大和的连续子段(子数组)。例如,对于序列 [-2, 1, -3, 4, -1, 2, 1, -5, 4],其最大子段和为 6,对应的子段是 [4, -1, 2, 1]。

  2. 动态规划思路及状态定义

    • 状态定义:使用 dp[i] 表示以第 i 个元素结尾的最大子段和。例如,dp[3] 就代表以序列中第 3 个元素结尾的连续子段能取得的最大和(这里序列索引从 0 开始计数)。
    • 状态转移方程推导依据:对于每个元素 nums[i](i > 0),有两种选择来构成以它结尾的最大子段和。一种是将当前元素 nums[i] 加入到以 nums[i - 1] 结尾的最大子段中(即 dp[i - 1] + nums[i]),另一种就是当前元素 nums[i] 单独作为一个子段(即 nums[i] 本身),取这两种情况中的较大值作为 dp[i] 的值,也就是 dp[i] = max(dp[i - 1] + nums[i], nums[i])。因为要使得以 nums[i] 结尾的子段和最大,要么接上前面的子段(前提是能使和更大),要么就自己单独成段(前面的子段和为负时,接上反而会使和变小)。
    • 最终结果获取:在计算出所有以每个元素结尾的最大子段和 dp[i] 后,整个序列的最大子段和就是所有 dp[i] 中的最大值,通过遍历 dp 数组不断比较更新来找到这个最大值,存储在变量 result 中并最终返回。

步骤

  1. 边界情况处理及初始化(函数开头部分)
    • 首先判断输入的整数序列 nums 是否为空,如果为空(即 nums.size() == 0),按照题目要求直接返回 0,表示空序列的最大子段和为 0。
    • 创建一个与输入序列 nums 长度相同的 dp 向量(vector dp(nums.size());),用于存储以每个元素结尾的最大子段和。然后将 dp[0] 初始化为 nums[0],因为以第一个元素结尾的最大子段和就是这个元素本身,同时将 result 也初始化为 dp[0],此时 result 暂时记录着当前找到的最大子段和(初始就是第一个元素的值)。
  2. 计算以每个元素结尾的最大子段和并更新最大子段和记录值(循环部分)
    • 通过 for 循环从索引 1 开始遍历整个输入序列 nums(for(int i = 1; i < nums.size(); i++)),对于每个索引 i:
      • 根据状态转移方程 dp[i] = max(dp[i - 1] + nums[i], nums[i]) 来计算以第 i 个元素结尾的最大子段和,也就是比较将当前元素加入到前一个元素结尾的最大子段中后的和与当前元素单独作为子段的和,取较大值赋给 dp[i]。
      • 接着比较当前计算得到的 dp[i] 和已记录的最大子段和 result 的大小,如果 dp[i] > result,则更新 result 的值为 dp[i],确保 result 始终记录着到当前元素为止所找到的最大子段和。
  3. 返回结果(函数末尾)
    • 当循环结束,即遍历完整个输入序列后,result 中存储的就是整个序列的最大子段和,将其作为函数的返回值返回,即 return result;。

图示法表示步骤(以序列 [-2, 1, -3, 4, -1, 2, 1, -5, 4] 为例)

  1. 初始化
    • 输入序列 nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4],长度为 9。
    • 创建 dp 向量,dp[0] = nums[0] = -2,同时 result = dp[0] = -2。
  2. 计算 dp[1](以 nums[1] 即元素 1 结尾的最大子段和)
    • 根据状态转移方程,dp[1] = max(dp[0] + nums[1], nums[1]) = max(-2 + 1, 1) = 1,此时 dp[1] = 1。然后比较 dp[1] 和 result 的大小,因为 1 > -2,所以更新 result = 1。
  3. 计算 dp[2](以 nums[2] 即元素 -3 结尾的最大子段和)
    • dp[2] = max(dp[1] + nums[2], nums[2]) = max(1 + (-3), -3) = -2,dp[2] = -2。再比较 dp[2] 和 result 的大小,-2 < 1,result 保持为 1。
  4. 计算 dp[3](以 nums[3] 即元素 4 结尾的最大子段和)
    • dp[3] = max(dp[2] + nums[3], nums[3]) = max(-2 + 4, 4) = 4,dp[3] = 4。比较后更新 result = 4,因为 4 > 1。
  5. 计算 dp[4](以 nums[4] 即元素 -1 结尾的最大子段和)
    • dp[4] = max(dp[3] + nums[4], nums[4]) = max(4 + (-1), -1) = 3,dp[4] = 3。由于 3 < 4,result 不变。
  6. 计算 dp[5](以 nums[5] 即元素 2 结尾的最大子段和)
    • dp[5] = max(dp[4] + nums[5], nums[5]) = max(3 + 2, 2) = 5,dp[5] = 5。更新 result = 5,因为 5 > 4。
  7. 计算 dp[6](以 nums[6] 即元素 1 结尾的最大子段和)
    • dp[6] = max(dp[5] + nums[6], nums[6]) = max(5 + 1, 1) = 6,dp[6] = 6。更新 result = 6,因为 6 > 5。
  8. 计算 dp[7](以 nums[7] 即元素 -5 结尾的最大子段和)
    • dp[7] = max(dp[6] + nums[7], nums[7]) = max(6 + (-5), -5) = 1,dp[7] = 1。由于 1 < 6,result 不变。
  9. 计算 dp[8](以 nums[8] 即元素 4 结尾的最大子段和)
    • dp[8] = max(dp[7] + nums[8], nums[8]) = max(1 + 4, 4) = 5,dp[8] = 5。因为 5 < 6,result 保持为 6。
  10. 返回结果
    • 循环结束,result = 6,返回 6,即为序列 [-2, 1, -3, 4, -1, 2, 1, -5, 4] 的最大子段和。

代码程序以及代码关键行注释

  1. class Solution {
  2. public:
  3. int maxSubArray(vector<int>& nums) {
  4. if (!nums.size()) return 0; // 如果输入序列为空,返回0
  5. vector<int> dp(nums.size()); // 创建dp向量,用于存储以每个元素结尾的最大子段和
  6. dp[0] = nums[0]; // 初始化dp[0]为第一个元素,因为以第一个元素结尾的最大子段和就是它本身
  7. int result = dp[0]; // 初始化result为dp[0],用于记录当前找到的最大子段和
  8. for (int i = 1; i < nums.size(); i++) {
  9. dp[i] = max(dp[i - 1] + nums[i], nums[i]); // 根据状态转移方程计算以第i个元素结尾的最大子段和
  10. if (dp[i] > result) result = dp[i]; // 如果当前计算的最大子段和大于已记录的result,更新result
  11. }
  12. return result; // 返回整个序列的最大子段和
  13. }
  14. };

完整代码程序

  1. #include <iostream>
  2. #include <vector>
  3. #include <algorithm>
  4. using namespace std;
  5. class Solution {
  6. public:
  7. int maxSubArray(vector<int>& nums) {
  8. if (!nums.size()) return 0;
  9. vector<int> dp(nums.size());
  10. dp[0] = nums[0];
  11. int result = dp[0];
  12. for (int i = 1; i < nums.size(); i++) {
  13. dp[i] = max(dp[i - 1] + nums[i], nums[i]);
  14. if (dp[i] > result) result = dp[i];
  15. }
  16. return result;
  17. }
  18. };
  19. int main() {
  20. vector<int> nums = {-2, 1, -3, 4, -1, 2, 1, -5, 4}; // 可自行修改输入序列
  21. Solution solution;
  22. int maxSum = solution.maxSubArray(nums);
  23. cout << "最大子段和为: " << maxSum << endl;
  24. return 0;
  25. }

时间复杂度分析

  1. 计算过程:
    • 在 maxSubArray 函数中,通过一个 for 循环遍历输入序列 nums,循环次数为 n(n 是 nums 的长度,即序列元素个数),每次循环内部执行的操作主要是调用 max 函数来比较两个值并进行赋值,以及可能的 result 值更新操作,这些操作的时间复杂度都是常数级别,即 O(1)。
  2. 时间复杂度:
    • 综合来看,整个算法的时间复杂度为 ,其中 n 为输入序列的长度。这意味着随着序列长度的增加,计算最大子段和所需的时间会呈线性增长,时间效率相对较高。
  3. 影响因素及优化方向:
    • 时间复杂度主要受输入序列长度 n 的影响,序列越长,遍历计算所需时间越长。对于该算法,其已经是解决最大子段和问题的较优时间复杂度解法之一,不过在一些特殊场景下,如果需要同时处理大量不同的序列,可能可以考虑对代码进行进一步的底层优化(比如利用一些特定的编译器优化选项来提升代码执行效率),或者结合并行计算等技术(如果硬件支持且问题适合并行处理)来加速计算过程,但这些优化方式通常需要更深入的技术知识以及对具体运行环境的考虑。

总结

  1. 算法特点及适用场景:
    • 该算法基于动态规划思想,通过简洁合理地定义状态和状态转移方程,高效地解决了最大子段和问题,时间复杂度为 ,在处理较长序列时也能保持较好的时间效率。适用于各种需要在给定序列中寻找具有最大和的连续子段的场景,比如在数据分析中,分析股票价格走势序列以找到一段连续时间内收益最大的区间,或者分析传感器采集的数据序列中某段连续数据的最大累加值等情况,应用范围较广。
  2. 注意事项及可能遇到的问题:
    • 在代码实现方面,要注意对输入序列为空的边界情况处理,确保程序的健壮性。同时,虽然算法本身逻辑相对简单,但在理解状态转移方程时要明确 dp[i] 的含义以及两种选择构成最大子段和的情况,避免在实现过程中出现逻辑错误导致结果不准确。另外,在实际应用中,如果输入序列的数据规模极大(例如海量数据场景),可能需要进一步考虑内存占用情况以及结合更高级的优化手段(如前面提到的并行计算等)来满足性能要求。
注:本文转载自blog.csdn.net的小王Jacky的文章"https://blog.csdn.net/m0_49844997/article/details/144570449"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

113
数据结构与算法
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top