410. 分割数组的最大值

本文探讨了如何将一个非负整数数组分成m个连续子数组,以使这m个子数组各自和的最大值最小。介绍了动态规划和二分查找结合贪心算法两种解题方法,并提供了详细的思路解析及C++代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

1 ≤ n ≤ 1000
1 ≤ m ≤ min(50, n)

示例:

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

输出: 18

很好的一道题,对于动态规划和二分查找都是比较经典的一种问法。
<可惜菜菜子一个都没想到,还是要多练习才对啊(T_T)>

方法一:动态规划

「将数组分割为 m 段,求……」是动态规划题目常见的问法。

  f [ i ] [ j ] \ f[i][j]  f[i][j]:将数组的前 i 个数分割为 j 段所能得到的最大连续子数组和的最小值。
考虑第 j 段的具体范围:
枚举 k,前 k 个数被分割为 j−1 段,第 k+1 到第 i 个数为第 j 段。
此时,这 j 段子数组中和的最大值,就等于 f [ k ] [ j − 1 ] f[k][j-1] f[k][j1] s u b ( k + 1 , i ) sub(k+1,i) sub(k+1,i) 中的较大值,其中 sub ( i , j ) \textit{sub}(i,j) sub(i,j)表示数组 nums \textit{nums} nums中下标落在区间 [ i , j ] [i,j] [i,j] 内的数的和。
在这里插入图片描述
注意,对于 sub ( i , j ) \textit{sub}(i,j) sub(i,j)的求取,通过前缀和直接得到。

C++最值常量:
有符号基本整型:INT_MIN,INT_MAX
有符号长整型:LONG_MIN,LONG_MAX || LLONG_MAX
双精度实数:DBL_MIN,DBL_MAX

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=1;i<=n;i++){
            sub[i]=sub[i-1]+nums[i-1];
        }

        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]);
    }
};

方法二:二分查找+贪心

「使……最大值尽可能小」是二分搜索题目常见的问法。

当我们选定一个值 x,我们可以线性地验证是否存在一种分割方案,满足其最大分割子数组和不超过 x。

class Solution {
public:
    bool check(vector<int>& nums, int x, int m) {
        long long sum = 0;
        int cnt = 1;
        for (int i = 0; i < nums.size(); i++) {
            if (sum + nums[i] > x) {
                cnt++;
                sum = nums[i];
            } else {
                sum += nums[i];
            }
        }
        return cnt <= m;
    }

    int splitArray(vector<int>& nums, int m) {
        long long left = 0, right = 0;
        for (int i = 0; i < nums.size(); i++) {
            right += nums[i];
            if (left < nums[i]) {
                left = nums[i];
            }
        }
        while (left < right) {
            long long mid = (left + right) >> 1;
            if (check(nums, mid, m)) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值