动态规划+滑动窗口:非重叠子数组的最大和 | 豆包MarsCodeAI刷题

115 阅读4分钟

题目链接

非重叠子数组的最大和

前置知识

  • 子数组和子序列
    子数组:一段连续的区间 如[1,2,3]有5个子数组
    [1] [2] [3] [1,2] [2,3]
    
    子序列: 不改变元素排列顺序的前提下,不需要满足连续性质的区间,如[1,2,3]有6个子序列
    [1] [2] [3] [1,2] [2,3] [1,3]
    

解题思路

  • 简化题意:

在num数组中找出两个相互独立的子数组,并使这两个子数组之和最大,长度为n1, n2

  • 考虑如何找出全部长度为n的子数组?

定长双指针(滑动窗口), 令 left = 0, 使用right遍历nums, 当 right-left+1 == n时,此时[left,right]区间就是一个长度为n的子数组,然后左右指针同时移动即可得到全部子数组

  • 考虑如何得到长度为n的子数组的最大值?

遍历nums时累加nums[right],当right-left + 1 == n时记录此时的s,并判断是否为最大,移动左指针时减去nums[left]

  • 考虑如何得到前i个长度为n的子数组的最大值?

我们设f[i] 是前i个长度为n的子数组的最大值
那么f[i]的值可能是前一个子数组的最大值或者是当前新的子数组的值
f[i] = max(f[i-1],s) s是当前区间的和
简单来说,我们在上一个思路上加入f[i]数组记录即可
伪代码

 //注意 这段代码只是基础演示 只是让大家理解这个效果
 nums = []int{3,2,4} 
 n = 1
 f := make([]int, len(nums) - n + 1) //长度为n数字有len(nums) - n + 1个 如 len(nums) = 3, n = 1
 s := 0
 i := 0
 for r, v := range nums {
     s += v //累加
     if r - i + 1 == n {
        f[i] = max(f[max(i-1, 0)], s) // max(i-1, 0)是因为第一次 i = 0 会出现越界
        s -= nums[i]
        i++
          
     }
 }
 /*
 最终f = [3,3,4]
 */
  • 我们已经可以求得一个长度为n的子数组的最大值了,那怎么求长度为n1,n2的子数组分别的最大值?

很明显 我们跑两次求长度为n的子数组代码即可!

  • 不相交的时候该怎么求?

假设n1=1, n2=2 nums=[1,2,3,4]

我们跑完两次上面的程序得到 f1=[1,2,3,4] f2=[3,5,7]

此时我们先遍历f1

要保证不相交 即f2不包含f[i]区间的子数组的最大值

假设此时是f[1] 那么f2可用的区间就是[2,3,4]

我们跳过f1的长度就是不相交的, 即 f2[i+n1]

我们应该得到此时区间[2,3,4]的最大值,但我们求出的f2并不能帮我们快速计算,因为f2也是顺序计算的

我们转换下思路,如果我们f2逆序来算 那么f2 = [7,7,7]

f2[0] 代表的就是区间[0:len(nums)]的最大值
f2[1] 代表的就是区间[2:len(nums)]的最大值
那我们就可以快速计算出选择区间f1[1]可以得到的最大值 1 + 7 = 8

  • 考虑情况覆盖完了吗?
    没有 我们现在只考虑了f1顺序,f2逆序的情况,我们还要考虑f2顺序, f1逆序的情况
    我们把上面的程序封装成函数,调换f1,f2即可,最后取两种情况的最大值即为答案

题解代码

package main

import "fmt"

func exec(nums []int, firstLen int, secondLen int) int {
    n := len(nums)
    f1 := make([]int, n-firstLen+1)
    f2 := make([]int, n-secondLen+1)
    //f1顺序生成
    l := 0
    s := 0
    for i, v := range nums {
        s += v
        if i-l+1 == firstLen {
            f1[l] = max(f1[max(l-1, 0)], s)
            s -= nums[l]
            l++
        }
    }
    //f2逆序生成
    l = n - 1
    s = 0
    for i := n - 1; i >= 0; i-- {
        s += nums[i]
        if l-i+1 == secondLen {
            f2[i] = max(f2[min(i+1, len(f2)-1)], s) //max保证不越界
            s -= nums[l]
            l--
        }
    }
    ans := 0
    //计算不相交的最大值
    for i := 0; i < len(f1) && i+firstLen < len(f2); i++ {
        ans = max(ans, f1[i]+f2[i+firstLen])
    }
    return ans
}
//两种情况取最大值
func solution(nums []int, firstLen int, secondLen int) int {
	return max(exec(nums, firstLen, secondLen), exec(nums, secondLen, firstLen))

}

def exec(nums, firstLen, secondLen):
    n = len(nums)
    f1 = [0] * (n - firstLen + 1)
    f2 = [0] * (n - secondLen + 1)
    l = 0
    s = 0
    for i, v in enumerate(nums):
        s += v
        if i - l + 1 == firstLen:
            f1[l] = max(f1[max(l - 1, 0)], s)
            s -= nums[l]
            l += 1
    
    l = n - 1
    s = 0
    for i in range(n - 1, -1, -1):
        s += nums[i]
        if l - i + 1 == secondLen:
            f2[i] = max(f2[min(i + 1, len(f2) - 1)], s)
            s -= nums[l]
            l -= 1
    
    ans = 0
    for i in range(len(f1)):
        if i + firstLen < len(f2):
            ans = max(ans, f1[i] + f2[i + firstLen])
    return ans

def solution(nums, firstLen, secondLen):
    return max(exec(nums, firstLen, secondLen), exec(nums, secondLen, firstLen))