🌕🌕 410. 分割数组的最大值

吞佛童子2022年10月10日
  • algorithm
  • dp
  • 二分 + 贪心
大约 2 分钟

🌕🌕 410. 分割数组的最大值

难度: 🌕🌕

问题描述

img_3.png


解法 1 - dp

class Solution {
    public int splitArray(int[] nums, int m) {
        // 思路:
        // dp[i][j] -- 以 [0, i] 划分为 j 段 得到的子数组的和的最大值的 最小
        // dp[i][j] = min{dp[i][j], max{dp[k][j - 1], sum[k + 1, len - 1]}}
        int len = nums.length;
        if(len == 1) {
            return nums[0];
        }
        int[][] dp = new int[len][len + 1];
        for(int i = 0; i < len; i ++) {
            Arrays.fill(dp[i], Integer.MAX_VALUE);
        }
        // 初始化 - 单个元素只能划分为 1 段
        int[] arr = new int[len]; // 前缀和数组
        arr[0] = nums[0];
        dp[0][1] = arr[0];
        for(int i = 1; i < len; i ++) {
            arr[i] = arr[i - 1] + nums[i];
            dp[i][1] = arr[i];
        }
        // dp
        for(int i = 1; i < len; i ++) {
            for(int j = 2; j <= Math.min(i + 1, m); j ++) {
                // [0, i] 中遍历找分割点 k
                for(int k = 0; k <= i; k ++) {
                    dp[i][j] = Math.min(dp[i][j], Math.max(dp[k][j - 1], arr[i] - arr[k]));
                }
            }
        }
        return dp[len - 1][m];
    }
}

输出 1

img_2.png


解法 2 - 二分 + 贪心

class Solution {
    public int splitArray(int[] nums, int m) {
        // 思路:
        // 将整个数组划分为多个子数组,那么子数组的和 ∈ [max, sum]
        // max 为数组中最大的那个元素值,因为这个元素比存在于某个子数组中
        // sum 为数组元素总和
        // sum 只要数组不变,那么 sum 也为恒定值
        // 尝试在 [max, sum] 间找到一个值,设为 mid,将这个值作为所有子数组和的最大值的最小值,即目标值
        // 然后判断能否在该目标下,将数组划分为 m 个子数组
        // 如果满足,减小 mid,继续查找
        // 如果不满足,增大 mid,继续查找
        // 这就是 二分
        int len = nums.length;
        if(len == 1) {
            return nums[0];
        }
        int max = nums[0];
        int sum = 0;
        // 遍历,得到 max & sum
        for(int i = 0; i < len; i ++) {
            max = Math.max(max, nums[i]);
            sum += nums[i];
        }
        // 确定 max & sum 后,找中点,判断能否将其划分为 m 个子数组
        int left = max;
        int right = sum;
        while(left < right) {
            int mid = left + ((right - left) >> 1);
            if(isValid(nums, m, mid)) {
                // 可以将 nums 划分为 m 个子数组,子数组最大和均 <= mid,缩小 mid
                right = mid;
            } else {
                // 不可以划分为 m 个子数组,增大 mid
                left = mid + 1;
            }
        }
        return left;
    }

    private boolean isValid(int[] nums, int m, int target) {
        int prev = 0;
        int count = 1; // 要使得每个子数组的和均小于 target,nums 至少需要被分成 count 个数组
        for(int i = 0; i < nums.length; i ++) {
            if(prev + nums[i] <= target) {
                prev += nums[i]; // 无需再次分为子数组
            } else {
                // 当前值需要划分到一个新的数组里面
                prev = nums[i];
                count ++;
            }
        }
        return count <= m;
    }
}

输出 2

img_4.png

上次编辑于: 2022/10/10 下午8:43:48
贡献者: liuxianzhishou