leetcode 410. Split Array Largest Sum

来源:互联网 发布:unity3d 角色资源 编辑:程序博客网 时间:2024/06/07 20:08

Given an array which consists of non-negative integers and an integer m, you can split the array into m non-empty continuous subarrays. Write an algorithm to minimize the largest sum among these m subarrays.

Note:
If n is the length of array, assume the following constraints are satisfied:

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

Examples:

Input:nums = [7,2,5,10,8]m = 2Output:18Explanation:There are four ways to split nums into two subarrays.The best way is to split it into [7,2,5] and [10,8],where the largest sum among the two subarrays is only 18.
这道题很明显又是一道递归 with memo 或者 DP 的题。

这道题又一次印证了 map 没有 int[][]数组 快的结论!因为我使用 HashMap<Integer,HashMap<Integer,Integer> 华丽丽地TLE了,但是改成 int[][] 就AC了。

我的思路是:int[row][column] :row存储了当前数组的开始索引 left,column 存储了当前的 m。因此 memo[i][j] 就是:从 i 开始的数组要被划分为 j 个子数组时的结果值。

public int splitArray(int[] nums, int m) {int[][] memo=new int[nums.length][m+1];return split(nums, 0, nums.length-1, m, memo);}public int split(int[] nums,int left,int right,int m,int[][] memo){if(memo[left][m]>0){return memo[left][m];}int result;if(m==1){result=0;for(int i=left;i<=right;i++){result+=nums[i];}}else{result=Integer.MAX_VALUE;int sum=0;for(int i=left;i<=right-1;i++){sum+=nums[i];int min=Math.max(sum, split(nums, i+1, right, m-1, memo));result=Math.min(result, min);}}memo[left][m]=result;return result;}
还有大神用 DP 来做。这里的 DP 是精简空间复杂度后的 DP 。
DP[i] 存储当前 m 状态下 从索引 i 开始将数组分割成 m 个子数组时的结果值。 
DP[i] 初始化时的m=1,因此DP[i]就存储 i~length 的和。如 DP[0] 就是 0~length 的和,DP[length-1] = nums[length-1]。
然后从 m=2,一直计算到我们需要的 m。
从 i =0开始,一直算到 maxPart,这里的 maxPart 指:如果我们需要分为 m 个部分,那么到了某个索引就该停止。比如如果 m=2,那么到了 nums.length-1 就该停了,得留 1 个给剩下的 part。如果 m=3,那么到了 nums.length-2 就该停了,得留 2 个分给剩下的 2 个 part。
p是分割点(是从p和p+1之间砍一刀),leftSum派给左边,然后rightSum就看 dp[p+1] 。此时的 dp[p+1] 是从索引 p+1 开始将数组分割成 m-1 个子数组时的结果值。(因为p+1 还没有被当前 m 覆盖,还停留在之前循环 m-1 时的值)
public static int splitArray(int[] nums, int m) {    int[] dp = new int[nums.length];    for(int i = nums.length-1; i>=0; i--)         dp[i] = i== nums.length -1 ? nums[i] : dp[i+1] + nums[i];    for(int im = 2; im <= m; im ++) {        int maxPart = nums.length + 1 - im;        for(int i=0; i<maxPart; i++) {            dp[i] = Integer.MAX_VALUE;            int leftSum = 0;            for(int p=i; p<maxPart; p++) {                leftSum += nums[p];                if(leftSum > dp[i])                    break;  // There's no more better soluiton, stop the search.                int val = Math.max(leftSum, dp[p+1]);                if(val < dp[i])                    dp[i] = val;            }            if(im == m)  // The last round, get first one is enough                break;        }    }            return dp[0];}.
还有大神用了很奇特的思路:二分查找 来做。

  1. 最后的结果介于 数组的最大元素值数组所有元素的和 之间。
  2. 使用二分查找来获得正确结果。 l = max number of array; r = sum of all numbers in the array; 每次我们取 mid = (l + r) / 2;
  3. 使用 greedy 算法来缩小 left 和 right 的边界。(把mid看做是我们需要求得的值)这个算法的思路在于:将数组从左开始遍历,累积加到sum,当发现sum>mid时,就切一刀,使得刀与刀之间的 元素sum<=mid。再继续开始从0累积sum。然后看一共切了多少刀。如果刀数>m,说明 mid 取得太小了,将左边界+1。如果刀数<m,说明 mid 取得太大了,将右边界+1。步骤如下:
    3.1 将数组从左开始切
    3.2 确保在每2个cut之间的元素的和(首尾全包括) 要小于等于 mid 
    3.3 那么有两种结果: 要么我们能将该数组分割为超过 m 个子数组,要么不能。
    如果我们能, 这意味着我们取的 mid 太小了,以至于不能把数组分为 m 部分,并且每部分的和都不超过 mid  。此时应当增加左边界 l = mid + 1;
    如果我们不能, 说明我们可以将数组分为 m 部分,并且每部分的和都不超过mid 。当前 mid 能够满足要求,但是我们需要得到最小的 mid。此时应当减少右边界 r = mid - 1;
public class Solution {    public int splitArray(int[] nums, int m) {        int max = 0; long sum = 0;        for (int num : nums) {            max = Math.max(num, max);            sum += num;        }        if (m == 1) return (int)sum;        //binary search        long l = max; long r = sum;        while (l <= r) {            long mid = (l + r)/ 2;            if (valid(mid, nums, m)) {                r = mid - 1;            } else {                l = mid + 1;            }        }        return (int)l;    }    public boolean valid(long target, int[] nums, int m) {        int count = 1;        long total = 0;        for(int num : nums) {            total += num;            if (total > target) {                total = num;                count++;                if (count > m) {                    return false;                }            }        }        return true;    }}