最大子序和(题号53)
题目
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [0]
输出:0
示例 4:
输入:nums = [-1]
输出:-1
示例 5:
输入:nums = [-100000]
输出:-100000
提示:
1 <= nums.length <= 3 * 104
-105 <= nums[i] <= 105
进阶:如果你已经实现复杂度为 O(n)
的解法,尝试使用更为精妙的 分治法 求解。
链接
解释
这题确实是个简单题,用DP可以很轻松的做出来,难就难在进阶里面的分治法。
关于分治这里不多做赘述,官方说的已经很清楚了,可以点击这里查看详情,并且这里的题解也包含JavaScript
,非常方便,想了解分治的同学可以好好看看。
自己的答案(DFS)
var maxSubArray = function(nums) {
var max = Number.MIN_SAFE_INTEGER
function dfs(i, sum) {
for (let j = i; j < nums.length; j++){
sum += nums[j]
max = Math.max(max, sum)
if (j > i) {
dfs(j, 0)
}
}
}
dfs(0, 0)
return max
};
很简单的一个DFS,结果也很简单,在执行第198个用例的时候超时了。
自己的答案(DP+数组)
var maxSubArray = function(nums) {
var len = nums.length
max = nums[0]
arr = new Array(len)
arr[0] = nums[0]
for (let i = 1; i < nums.length; i++) {
arr[i] = Math.max(arr[i - 1] + nums[i], nums[i])
max = Math.max(max, arrHas[i])
}
return max
};
DP数组的解法也很简单,主要的DP公式就是前一个DP最大值加上当前元素,和当前元素进行比较。
取两者的最大值,之后每次循环都需要和max
做一次对比,以防在途中遇到最大值遗忘了。
自己的答案(DP+降维)
和之前的许多DP一样,这个DP问题也可以降低空间复杂度,从O(n)
降低到O(1)
。
var maxSubArray = function(nums) {
var max = Number.MIN_SAFE_INTEGER
prv = max
for (const num of nums) {
prv = Math.max(prv + num, num)
max = Math.max(max, prv)
}
return max
};
简单方便,越看越喜欢,哈哈哈😂
更好的方法(DP+判断)
这个答案就让我很懵了,一开始看到这个答案以为是贪心,结果发现他其实就是某种意义上的DP,并不是贪心,话不多说,先看代码👇(原答案链接):
var maxSubArray = function(nums) {
let ans = nums[0];
let sum = 0;
for(let num of nums) {
// if(sum > 0) { 可以写成这样
if(sum + num > num ){
sum = sum + num;
} else {
sum = num;
}
ans = Math.max(ans, sum);
};
return ans;
};
看起来是不是有点奇妙,尤其是这个大小的比较,很像贪心啊,其实这里处理sum
还真就是DP,类似于👆降维后的DP,不过我利用的是Math.max
,他这里用的大小的比较。
这里的判断逻辑也很简单,如果前面的sum
小于0,那
sum + num > num
必然是false
,所以直接将num
赋值给sum
即可,如果sum
大于0,那就在sum
的基础上再加上当前元素,因为必然会更大。
最后也是和我一样,每次获取到sum
之后都和max
进行一下对比,保证不会遗漏到中途的某个数值。
不得不说这个老哥的清奇的思路,佩服佩服。
更好的方法(分治)
来了来了,这题的重头戏来了——分治。
其实这题用分治来解决并不是最佳解决方案,因为其时间复杂度比DP要高,虽然空间复杂度都是一样的,但变量多了不少,还需要多生成几个方法,占用了更多的内存。
具体的代码这里也不放了,笔者自己盲写了5遍才将将记住,里面的逻辑着实是有点麻烦,不过看懂了也还好,就看能不能记住了。
具体的还是看官方解答吧👉:最大子序和的第二种解法。
说的真的很好,而且很详细。
笨阶乘(题号1006)
题目
通常,正整数 n
的阶乘是所有小于或等于 n
的正整数的乘积。例如,factorial(10) = 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1
。
相反,我们设计了一个笨阶乘 clumsy
:在整数的递减序列中,我们以一个固定顺序的操作符序列来依次替换原有的乘法操作符:乘法(*),除法(/),加法(+)和减法(-)。
例如,clumsy(10) = 10 * 9 / 8 + 7 - 6 * 5 / 4 + 3 - 2 * 1
。然而,这些运算仍然使用通常的算术运算顺序:我们在任何加、减步骤之前执行所有的乘法和除法步骤,并且按从左到右处理乘法和除法步骤。
另外,我们使用的除法是地板除法(floor division),所以 10 * 9 / 8 等于 11
。这保证结果是一个整数。
实现上面定义的笨函数:给定一个整数 N
,它返回 N
的笨阶乘。
示例 1:
输入:4
输出:7
解释:7 = 4 * 3 / 2 + 1
示例 2:
输入:10
输出:12
解释:12 = 10 * 9 / 8 + 7 - 6 * 5 / 4 + 3 - 2 * 1
提示:
1 <= N <= 10000
-2^31 <= answer <= 2^31 - 1
(答案保证符合 32 位整数。)
链接
解释
这题的题目并不复杂,重点是四则运算的优先级问题。
官方给的答案是栈,在JavaScript
中就是数组,利用数组的pop
方法取出最后一个元素,之后对其进行四则运算。如果是乘法或者除法就取出元素,运算完成后再插入;如果是加法或者减法,则直接推入元素,减法推入反转之后的元素。
还有一种不常见的方法就是纯数学方法解决,这感觉没啥可说的,就自己的推导出一个数学公式出来即可。
笔者这里给出的答案其实有点类似于官方栈操作,当并没有用具体的栈,而是用两层循环,具体可以看👇的代码。
自己的答案(双层循环)
var clumsy = function (N) {
var res = null
X = Math.ceil(N / 4)
for (let x = 0; x < X; x++) {
var tmp = 1
for (let i = 0; i < 4; i++) {
var localNum = N - (x * 4 + i)
if (localNum === 0) {
res = res === null ? tmp : res -= tmp
break
}
switch (i) {
case 0:
tmp = localNum
break;
case 1:
tmp = tmp * localNum
break;
case 2:
tmp = ~~(tmp / localNum)
break;
default:
res = res === null ? tmp : res - tmp
res += localNum
break;
}
}
}
return res
};
由于四则运算的优先级规律,这里笔者将数组拆分的结果分成四个一组,使用X
代表组的个数,如此判断总共有多少个四则运算,之后将每组四则运算的结果放到一块,变成一个数组,最后在所有的数字之间使用减号连接即可。
拿10举个例子,这里将数字分成了:
10 * 9 / 8
+7
6 * 5 / 4
+3
2 * 1
之后相加这些数字即可,相对于栈来说还是有点冗余的,判断条件有些略多,尤其是在最后一个X
长度不足4的情况下需要再进行一次判断。
时间复杂度还是O(n)
,因为X
和i
的个数还是N
个,空间复杂一如既往是O(1)
。
更好的方法(栈)
var clumsy = function (N) {
var stack = [N--]
index = 0
while (N > 0) {
var activeIndex = index % 4
switch (activeIndex) {
case 0:
stack.push(stack.pop() * N)
break;
case 1:
stack.push(~~(stack.pop() / N))
break;
case 2:
stack.push(N)
break;
default:
stack.push(-N)
break;
}
N--
index++
}
return stack.reduce((item, total) => {
return item + total
}, 0)
}
这里稍微改造了一下官方的答案,比方说用switch...case
代替了if...else
,还有就是使用~~
代替了Math.floor
和Math.ceil
。
主要逻辑也比较简单,如果是乘法和除法就取出栈末尾的数据,进行运算后再推入到栈的末尾,如果是加法直接推入栈的末尾,如果是减法就在数组前加一个符号推入栈。
在遍历完成后将栈里面所有的数字相加,返回和即可。
更好的方法(数学)
数学这块就不多说了,官方答案已经说的很清楚了,不多赘述。
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇
有兴趣的也可以看看我的个人主页👇