LeetCode: 410. 分割数组的最大值

给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。

注意:
数组长度 n 满足以下条件:

1 ≤ n ≤ 1000
1 ≤ m ≤ min(50, n)
示例:

输入:
nums = [7,2,5,10,8]
m = 2

输出:
18

解释:
一共有四种方法将nums分割为2个子数组。
其中最好的方式是将其分为[7,2,5] 和 [10,8],
因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。

分析:

       据题解所说,“将数组分为m段,求……”是动态规划题目常见的问法。(P.s.我怎么觉得不常见,枯了)

       既然是动态规划,那么定义数组很重要。令f[i][j]表示将数组的前i个数分割为j段所能得到的最大连续子数组的和的最小值。(就是这么拗口!)那么状态转移方程,我们考虑f[i][j],枚举k,其中将前k个数分为j - 1段,第k个数到第i个数分为最后一段。(即我们不断枚举最后一个连续子数组的长度,前面的j - 1个数组看为一个整体)。那么这j个数组的最大值就是f[k][j - 1]与sub(k + 1, i)的最大值,其中sub(i, j)表示第i个数累加到第j个数的和。状态转移方程可以写成下面的形式:

f[i][j] = \min_{k = 0}^{i - 1}\left \{ max(f[k][j - 1], sub(k + 1, i)) \right \}

       需要注意的是,对于f[i][j],只有i >= j时才是合法的状态。如果不合法,我们可以把f[i][j]的初始值设为一个很大的值,这样经过max后再经过min,就不会对结果产生影响。

       还有我们需要考虑一下初值,根据上面的状态转移方程,如果j = 1,那么会在f[k][j - 1],也就是f[k][0]中进行比较,如果k = 0,那它是个合法的状态,根据数组定义,我们令其为0;如果k != 0,那么就是一个不合法的状态,通过我们设置的很大的值可以将其滤过去。所以我们要设置f[0][0] = 0。

       这个题最难的地方个人感觉还是数组的定义,如果不能确定数组的含义,那么状态转移方程也是写不下去的。把一个数组划分成m个子数组,看着是一个很棘手的问题。但是我们可以通过得到前面j - 1个数组的最大值,枚举最后一个连续子数组的长度,实质上就是简化成了两个子数组的问题。化繁为简,这种思想还是很重要的。

class Solution {
public:
    int splitArray(vector<int>& nums, int m) {
        int n = nums.size();
        vector<vector<long long>> f(n + 1, vector<long long>(m + 1, LLONG_MAX));
        vector<long long> sub(n + 1, 0);
        for (int i = 0; i < n; i++) {
            sub[i + 1] = sub[i] + nums[i];
        }
        f[0][0] = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= min(i, m); j++) {
                for (int k = 0; k < i; k++) {
                    f[i][j] = min(f[i][j], max(f[k][j - 1], sub[i] - sub[k]));
                }
            }
        }
        return (int)f[n][m];
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值