记录 1 道算法题
最大子数组和
53. 最大子数组和 - 力扣(LeetCode) (leetcode-cn.com)
要求:返回一个数组内最大的子数组的和,子数组是指数组中一段1个元素以上组成的数组,不能跳开,位置是连续的。[-2,1,-3,4,-1,2,1,-5,4],最大子数组和:6。
最简单的是贪心的做法,我们假设一旦以往的和比当前元素小的时候,继续计算已经不是最大的子数组和,这时候要重新开始计算子数组。并且是将之前子数组的元素都放弃,从当前元素开始计算。
function maxSubArray(nums) {
// pre 是当前子数组的和
let pre = nums[0]
// sum 是最大子数组的和
let sum = pre
for(let i = 1; i < nums.length; i++) {
const n = nums[i]
// 更新子数组的和
pre = Math.max(pre + n, n)
// 比较最大的子数组和
sum = Math.max(pre, sum)
}
return sum
}
另外一种做法是利用分治,只计算分治的区间的最大子数组和,保留最大的那个。为此需要几个辅助的值,1、左边界为子数组开头的最大和,2、右边界为子数组结尾的最大和,3、左边界到右边界的总和,4、区间中最大的子数组和。
通过分治,将左右的两种 4 个值进行合并,最后一直递归返回,就能得出最大的子数组和,其他3种是为了辅助计算区间中的最大子数组和而存在的。
class State {
constructor(lSum, rSum, mSum, tSum) {
// 左边界为子数组开头的最大和
this.lSum = lSum
// 右边界为子数组结尾的最大和
this.rSum = rSum
// 区间中最大的子数组和
this.mSum = mSum
// 左边界到右边界的总和
this.tSum = tSum
}
}
function maxSubArray(nums) {
return getSum(nums, 0, nums.length - 1).mSum
}
function getSum(nums, left, right) {
// 只有一个数的时候,直接返回
if (left === right) return new State(nums[left], nums[left], nums[left], nums[left])
// 分治统计 4 个值
const mid = Math.floor((left + right) / 2)
const leftGroup = getSub(nums, left, mid)
const rightGroup = getSub(nums, mid + 1, right)
// 生成需要的 4 个值
return mergeGroup(leftGroup, rightGroup)
}
function mergeGroup(leftGroup, rightGroup) {
// 总和就是两个的总和相加
const tSum = leftGroup.tSum + rightGroup.tSum
// 左边界为开始的最大和需要看,左边的数组的 lSum 是不是占满整个左数组,如果是则要多考虑右数组的 lSum
const lSum = Math.max(leftGroup.lSum, leftGroup.lSum + rightGroup.lSum)
// 右边界为子数组结束位置的最大和同理
const rSum = Math.max(rightGroup.rSum, rightGroup.rSum + leftGroup.lSum)
// 最大的子数组和是左右的最大子数组和里面的其中一个,或者是由左边和右边的一部分组成,跨左右两个数组
const mSum = Math.max(
leftGroup.mSum,
rightGroup.mSum,
leftGroup.rSum + rightGroup.lSum
)
return new State(lSum, rSum, mSum, tSum)
}