LeetCode 410. Split Array Largest Sum

方法一:DFS+Memoization

某种程度来说本题和 Word Break 很像。直接dfs暴力做肯定会超时,加上memoization即可。

dfs(start, m, ...) 表示从index start开始,分成m组的minmax值。

时间复杂度 比较复杂,应该和DP一样是 O(n^2*m)

class Solution {
public:
    int splitArray(vector<int>& nums, int m) {
        int n=nums.size();
        // sum[i] - sum of first i items
        vector<long> sum(n+1,0);
        for (int i=1;i<=n;++i){
            sum[i] = sum[i-1]+nums[i-1];
        }
        vector<vector<long>> memo(n,vector<long>(m+1,-1));
        return dfs(0,m,nums,sum,memo);
    }
    
    // start - start index
    long dfs(int start, int m, vector<int> &nums, vector<long> &sum, vector<vector<long>> &memo){
        if (m==1) return sum[nums.size()]-sum[start];
        if (memo[start][m]!=-1) return memo[start][m];
        
        long minmax=LONG_MAX;
        for (int i=start+1;i<nums.size();++i){
            minmax = min(minmax, max(sum[i]-sum[start], dfs(i,m-1,nums,sum,memo)));
        }
        return memo[start][m]=minmax;
    }
};

 

方法二:DP

dp[i][j] 表示前i个分成j组的minmax的值。

dp[i][j] = min_k{ max(dp[k][j-1], a[k]+...+a[i-1]) }  (0<=k<i)

corner case: dp[i][j] = INT_MAX (j>i),  dp[i][1] = sum[i]

前i个其实最多只能分为i份,所以如果 j>i,不可能。设为INT_MAX就不会影响到最小值。

因此可以将dp全部初始化为INT_MAX,而且 只要将 dp[0][0] 设为0,dp[i][1] = sum[i]也可以被整合进for循环里。

当然,按照corner case初始化也是可以的。而且循环k的时候也可以从后向前,如果presum太大可以提前结束循环。

https://leetcode.com/problems/split-array-largest-sum/discuss/89838/C++-DP-SOLUTION

时间复杂度 O(n^2*m)

class Solution {
public:
    int splitArray(vector<int>& nums, int m) {
        int n=nums.size();
        // dp[i][j] - the minmax value of dividing first i items into j parts
        // dp[0][j] = INT_MAX,  dp[i][1] = sum[i];
        // dp[i][j] = min_k max(dp[k][j-1], a[k]+...+a[i-1])  (0<=k<i)
        vector<vector<long>> dp(n+1,vector<long>(m+1,INT_MAX));
        
        // sum[i] - sum of first i items
        vector<long> sum(n+1,0);
        for (int i=1;i<=n;++i){
            sum[i] = sum[i-1]+nums[i-1];
        }
        dp[0][0] = 0;
        for (int i=1;i<=n;++i){
            for (int j=1;j<=m;++j){
                for (int k=0;k<i;++k){
                    dp[i][j] = min(dp[i][j], max(dp[k][j-1], sum[i]-sum[k]));
                }
            }
        }
        return dp[n][m];
    }
};

 

方法三:Binary Search+Greedy

原题是Optimization问题,要找到最大subarray的最小值。我们可以先解决对应的Search Problem,然后用二分解决优化问题。即,给定一个上限cap,判断能否在满足要求的情况下(最大subarray<=cap),将数组分为m段。

这个Search Problem可以用Greedy来做,如果subarray sum>cap,说明需要新开一段。

二分部分标准写法,搜索区间是 [max{nums[i]}, sum_{nums[i]}],如果canSplit(mid),我们需要找更小的值,high=mid,如果不行,那就放松cap,low=mid+1。同时也保证了解一直在搜索区间里。

当然另一种写法 while (low<=high); low=mid+1; high=low-1; 当然也是可以的,搜索区间是 [low,high],解区间是[low,high+1]。可以复习一下 Binary Search

时间复杂度 O(n*log(sum(nums) - max(nums)))

class Solution {
public:
    int splitArray(vector<int>& nums, int m) {
        long low=0, high=0;
        for (long num:nums){
            low = max(low,num);
            high += num;
        }
        while (low<high){
            long mid=(low+high)/2;
            if (canSplit(mid,nums,m))
                high = mid;
            else
                low = mid+1;
        }
        return low;
    }
    
    bool canSplit(long cap, vector<int> &nums, int m){
        int count=1;
        long sum=0;
        for (int num:nums){
            sum += num;
            if (sum>cap){
                ++count;
                if (count>m) return false;
                sum = num;
            }
        }
        return true;
    }
};

 

转载于:https://www.cnblogs.com/hankunyan/p/10988054.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值