最大子序列和问题 | 超详细 多解法 原创
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。(注意这里的连续是关键点)
1 循环解法
三重循环
- 很容易想到的是最原始方法------ 暴力三重循环
最外层循环确定序列起始位置,第二层循环确定序列结束位置,第三层循环遍历元素相加,然后判断是否更新最大值.代码简单,不做赘述.
// 三重循环
function maxsub1(a, n) {
let maxsum = 0;
for (let i = 0; i < n; i++) {
for (let j = i; j < n; j++) {
let currsum = 0;
for (let k = i; k <= j; k++) {
currsum += a[k];
}
if (currsum > maxsum) {
maxsum = currsum;
}
}
}
return maxsum;
}
二重循环
-
略微改进的方法------二重循环
即向后寻找子序列结束位置时动态计算当前子序列和,并不断比较
// 二重循环
function maxsub2(a, n) {
let maxsum = 0;
for (let i = 0; i < n; i++) {
let currsum = 0;
for (let j = i; j < n; j++) {
currsum += a[j];
if (currsum > maxsum) {
maxsum = currsum;
}
}
}
return maxsum;
}
一重循环
这里先给出代码,思想和后面的动态规划类似,详细思路可参考后文
function max_subseq(arr) {
const len = arr.length;
let maxsum = 0, currsum = 0;
for (let i = 0; i < len; i++) {
currsum += arr[i];
if (currsum > maxsum) {
maxsum = currsum;
}
else if (currsum < 0) {
currsum = 0;
}
}
return maxsum;
}
2 分治思想
关于分治
- Divide arguments into groups, until trivial cases found and solve the trivial cases
- Use recursive steps to get solutions to small groups (分)
- Use non-recursive steps to combine small group solutions to form the final solution (治)
具体而言
- 对于单个元素而言
- 他可能处在最大子序列的起始位置,末尾位置,中间位置
- 对于每一个递归阶段
- 都是需要分别计算,左序列最大值,右序列最大值,跨越中间元素的序列最大值
- 然后比较这三者,得到最终最大值
- 最后递归返回,合并结果(治),得到最大子序列和
// 求最大子序列和
// ,跨越中间元素的序列和最大值
function find_overlap(arr, left, right, mid) {
let sum1 = 0, sum2 = 0;
for (let i = mid, tmp = 0; i >= left; i--) {
tmp += arr[i];
if (tmp > sum1) sum1 = tmp;
}
for (let j = mid + 1, tmp = 0; j <= right; j++) {
tmp += arr[j];
if (tmp > sum2) sum2 = tmp;
}
return sum1 + sum2;
}
// 辅助函数 求三者最大值
function find_maxOfThree(a, b, c) {
return ((a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c));
}
// 求最大子序列和
function max_subseq(arr, left, right) {
// 递归结束条件
if (left > right) return 0;
if (left === right) return arr[left];
const mid = Math.floor((left + right)/2);
const max_left = max_subseq(arr, left, mid -1);
const max_right = max_subseq(arr, mid + 1, right);
const max_mid = find_overlap(arr, left, right, mid);
return find_maxOfThree(max_left, max_mid, max_right);
}
// 测试用例
const arr = [2, -3, 4, -1, 3, 7, -5];
const res =max_subseq(arr, 0, arr.length - 1);
console.log(res);
3 动态规划
关于动态规划
-
动态规划的关键点
-
最优子结构(Optimal Substructure):问题的最优解可以通过其子问题的最优解来构造。换句话说,全局最优解包含了局部最优解。
-
重叠子问题(Overlapping Subproblems):在递归过程中,不同的子问题可能会多次求解相同的子问题。动态规划通过记忆化或者自底向上的方法来避免重复计算,将子问题的结果保存在表格中。
-
状态转移方程(State Transition Equation):描述了问题的当前状态和与之相关的子问题之间的关系。状态转移方程是动态规划问题的核心,它定义了如何从已解决的子问题的解构建原问题的解。
-
-
动态规划的基本步骤
-
定义状态(State Definition):明确定义问题的状态,找到问题的子结构。
-
找到状态转移方程(State Transition Equation):根据问题的最优子结构,找到问题状态之间的转移关系,用数学公式表示。
-
初始化(Initialization):将问题的最小规模情况下的解初始化,通常是边界情况。
-
自底向上或自顶向下求解:自底向上是从问题的最小规模开始逐步求解,自顶向下是从问题的最大规模开始逐步递归求解。
-
优化和空间复杂度分析:优化算法,降低时间和空间复杂度。
-
具体而言
通过观察归纳我们可以得到以下规律:
- no subsequence starting with a negative can be the best (minimal) one
负数不可能是目标序列的起始元素
- if find that sequence i..j is negative, can jump from i to j+1
如果以前一个元素结尾的子序列和小于0,我们可以直接舍弃之前的相加结果,继续从当前元素开始计算
- 定义状态(子问题)
dp[i]:表示以 nums[i] 结尾 的 连续 子数组的最大和。
- 状态转移方程
或
代码实现 (python版本)
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
size = len(nums)
if size == 0:
return 0
dp = [0 for _ in range(size)]
dp[0] = nums[0]
for i in range(1, size):
if dp[i - 1] >= 0:
dp[i] = dp[i - 1] + nums[i]
else:
dp[i] = nums[i]
return max(dp)
// 复杂度 O(n)
......END ......