求最大连续子集
问题
前两天看到一道算法题, 想了几天, 然后到网上搜了搜, 基本和我想到的相契合. 来, 题目如下:
给出一个数组, 求出和最大的连续子集. 举个例子: 数组 [1, 2, 3, 4, 5]
那和最大的就是数组本身了.
但是, 如果中间出现负数, 那情况立刻就不一样了, 你需要考虑是否能够将负数左边的内容包含进来, 从而令子集的和最大化.
下面给出本人递进的思考思路.
方案一
暴力一点, 直接遍历所有情况. 暴力破解没什么好说的, 下面是 php 代码:
$arr = []; // 一个数组
$maxSum = 0;
for($i = 0; $i < count($arr); $i++){
for ($j = $i; $j < count($arr); $j++){
// 计算当前子集的和
$sum = 0;
for ($n = $i; $n <= $j; $n++) $sum += $arr[$n];
if($sum > $maxSum) $maxSum = $sum;
}
}
上面代码很好理解, 将所有子集都遍历一遍, 然后比较是否是最大的和. 简单粗暴, 就是时间复杂度有些高: O(n^3)
方案二
仔细观察上面的方案, 你会发现其实是可以减少一次循环的. 第三层循环不过是将第二层的内容进行逐次累加, 也就是第二层循环的第 n 次累加结果, 正好是第 n+1次的倒数第二条结果. 如果将这样的结果进行向后传递, 就可以将第三层循环减掉了.
$arr = []; // 一个数组
$maxSum = 0;
for($i = 0; $i < count($arr); $i++){
$sun = 0;
for ($j = $i; $j < count($arr); $j++){
$sum += $arr[$j];
if($sum > $maxSum) $maxSum = $sum;
}
}
看这段代码, 和方案一
实现的内容其实是一样的, 但减少了一次循环, 时间复杂度直接降低了一个档次: O(n^2)
你以为到此为止了么? 天真, 如果这么简单, 那我还写个毛啊. 再来.
方案三
时间复杂度再往下降, 好吧, 我承认, 下面这段代码是我看到网上其他大佬的方案后才恍然大悟的. 恕在下愚钝. 先看代码:
$maxSum = 0; // 至今的最大值
$maxHere = 0; // 遍历到此的最大值
for($i = 0; $i < count($arr); $i++){
$maxHere = max($maxHere + $arr[$i], 0);
$maxSum = max($maxSum, $maxHere);
}
以上代码时间复杂度为 O(n). 很牛逼. 因为这个方法不是咱想出来的, 咱就不分析他是如何出来的了, 简单看一看她为什么能够求出结果值.
当遍历到$i 的位置时, maxHere
保存了 i-1
的最大和. 若加上当前值为正数, 则可以继续往后加, 因为正数相加必然令数字变大. 但如果相加结果为负数, 则将其重置为0 , 因为负数相加必然令数字变小. 所以 第三行 取相加结果与0的较大值.
每次遍历之后, maxHere
变量都保存了前面至当前位置的最大和, 将其与maxSum
比较即可. 至此, 即可完成时间复杂度 O(n) .
总结
其实, 当最终结果摆到我面前的时候, 我会有一种恍然大悟的感觉. 但我之前在方案二卡了几天, 没有想到 O(n) 的算法. 是思维限制了我? 是智商拉低了我? 还是仅仅因为我没有与其打过照面??
很多问题都是这样, 当答案明明白白摆在你面前的时候, 你会觉得, 哼不过如此, 故弄玄虚. 但如果不告诉你答案, 你就是绞尽脑汁, 费尽气力, 燃烧你的卡路里, 也想不到这个答案. 这感觉就像是大名鼎鼎的 NP 问题, 当答案摆在你面前时, 你能够轻而易举的验证它, 但如果不告诉你答案, 你就是得不到它.
让我想起了我当初学数学的时候, 很多题目给我的感觉也是这样. 当看过答案之后, 我会发现, 哦不过如此, so easy. 但是, 当时考试的时候就是没有写出来. 当时是如何解决这种情况呢? 大量做题. 俗话说, 熟能生巧, 熟读唐诗三百首, 不会作诗也会吟. 说的都是重复的力量. 我虽然英语是个渣渣, 但是 public
cost
class
这些词, 我一看就会, 为什么? 每天都在写, 想不会都难啊.
说下来, 如何解决上面的问题呢? 简单说, 多做题. 或者说, 当你解决类似的问题多了, 再次遇到它, 不用说, 下意识就会将解决方案从记忆中检索出来, 并将方案改造成适合当前场景的样子.
重在参与嘛. 将前几天困扰自己几天的问题记录一下, 虽然现在看很 low. 当然, 但愿下次看到能够想起解决方案.