Hi 大家好,我是张小猪。欢迎来到『宝宝也能看懂』系列特别篇 - 30-Day LeetCoding Challenge。
这是一个 leetcode 官方的小活动。可以在官网看到,从 4 月 1 号开始,每天官方会选出一道题,在 24 小时内完成即可获得一点小奖励。虽然奖励似乎也没什么用,不过作为一个官方的打卡活动,小猪还是来打一下卡吧,正好作为每天下班回家后的娱乐。
这里是 4 月 3 号的题,也是题目列表中的第 53 题 -- 『最大子序和』
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例 1:
输入: [-2,1,-3,4,-1,2,1,-5,4], 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
EASY
题目中比较关键的点就在于,要求目标数组是一个连续子数组。这也就意味着,我们不能更改顺序,也不能只从中间间隔的挑选一些子项。如果用一个形象的栗子的话,我们可以想象成现在有一个长度可变的窗口,它的左边界可以左右移动,右边界也可以,这样它在数组中框选出来的就是一个连续子数组。
基于这个栗子,我们来看看在遍历数组项的时候,我们会遇到哪些情况。首先对于右边界来说,可能会有两种情况:
然后我们再尝试站在左边界的视角,看看以上两个情况:
这里得到了几个遍历过程中的状况,目前看起来似乎还是比较的云里雾里。不过不要着急,我们继续分析。
首先,最直接的方式,那就是尝试所有可能的窗口,然后就能找到和最大的窗口情况。不过这种方式的时间复杂度太高,相信并不会有小伙伴这么写。这里只是作为一个最基本的实现,具体代码如下:
const maxSubArray = nums => { let max = nums[0]; for (let i = 0; i < nums.length; ++i) { let cur = 0; for (let j = i; i < nums.length; ++j) { cur += nums[j]; cur > max && (max = cur); } } return max; };
上面的直接方案在遍历过程中完全没有利用到我们的目的和之前分析的情况。如果加上这一些,我们可以想象一下,在遍历数组的过程中,其实我们面临的状况总共就只有两种:继续扩充当前窗口或者基于此位置建立新窗口。
为什么这么说呢?假设当前窗口的和为 x,而我们遇到一个新的值 y,那么站在右边界的视角,由于不知道未来的情况,所以无论是正负数我们都会默认不断的扩充当前窗口。除非这时候 x + y < y,即站在左边界的视角,可以通过向右移动来扔掉一堆负担。
这也就是为什么在最开始的时候我们会先分析左右两个边界的情况了。最后,为了得到全局最大值,我们会在过程中不断的比较局部最大值。具体代码如下:
const maxSubArray = nums => { let max = cur = nums[0]; for (let i = 1; i < nums.length; i++) { cur += nums[i]; nums[i] > cur && (cur = nums[i]); cur > max && (max = cur); } return max; };
这里其实还是基于我们最初分析的情况,不过我们换一个姿势来看看。假设当前的位置始终为窗口的右边界,那么是否需要把窗口的左边界进行左移,完全取决于左边累计下来的结果。而这个累计结果是可以通过计算一直保持下来的。具体代码如下:
const maxSubArray = nums => { let max = nums[0]; for (let i = 1; i < nums.length; i++) { nums[i - 1] > 0 && (nums[i] += nums[i - 1]); nums[i] > max && (max = nums[i]); } return max; };
作为『30-Day LeetCoding Challenge』的第三题,这道题目的突破点就在于分析出如何利用现有的结果来简化情况从而导向最终目标。希望能帮到有需要的小伙伴。
如果觉得不错的话,记得『三连』哦。小猪爱你们哟~