动态规划 - 分割数组的最大值

给定一个非负整数数组和一个整数 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,在所有情况中最小。

一道很经典的DP问题, 不过所有的算法比赛中, 我们在第一时间总会尝试用 borced solution
DFS 搜索把n长度的数据划分为m段,超时, 不过这段dfs代码设计的还是十分巧妙的, 适合很多种对数据做搜索的解法。

class Solution {
    private int ans;
    private int n, m;
    private void dfs(int[] nums, int i, int cntSubarrays, int curSum, int curMax) {
        if (i == n && cntSubarrays == m) {
            ans = Math.min(ans, curMax);
            return;
        }
        if (i == n) {
            return;
        }
        if (i > 0) {
            dfs(nums, i + 1, cntSubarrays, curSum + nums[i], Math.max(curMax, curSum + nums[i]));
        }
        if (cntSubarrays < m) {
            dfs(nums, i + 1, cntSubarrays + 1, nums[i], Math.max(curMax, nums[i]));
        }
    }
    public int splitArray(int[] nums, int M) {
        ans = Integer.MAX_VALUE;
        n = nums.length;
        m = M;
        dfs(nums, 0, 0, 0, 0);
        return ans;
    }
}

看了官方答案, dp才是王道,二分搜索+贪心, 想法太绕了, 一般不太容易理解。

思路

这个问题满足无后向性的特点。我们可以用动态规划来解决它。

无后向性的特点意味着,一旦当前状态确定了,它就不会被之后的状态影响。在这个问题里面,如果我们在将 nums[0..i] 分成 j 份时得到了当前最小的分割数组的最大值,不论后面的部分怎么分割这个值不会受到影响。

算法

首先我们把 f[i][j] 定义为将 nums[0..i] 分成 j 份时能得到的最小的分割子数组最大值。

对于第 j 个子数组,它为数组中下标 k + 1i 的这一段。因此,f[i][j]可以从 max(f[k][j - 1], nums[k + 1] + ... + nums[i])这个公式中得到。遍历所有可能的 k,会得到 f[i][j]的最小值。

整个算法那的最终答案为f[n][m],其中 n 为数组大小。
good solution


class Solution {
    public int splitArray(int[] nums, int m) {
        int n = nums.length;
        int[][] f = new int[n + 1][m + 1];
        int[] sub = new int[n + 1];
        for (int i = 0; i <= n; i++) {
            for (int j = 0; j <= m; j++) {
                f[i][j] = Integer.MAX_VALUE;
            }
        }
        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 <= m; j++) {
                for (int k = 0; k < i; k++) {
                    f[i][j] = Math.min(f[i][j], Math.max(f[k][j - 1], sub[i] - sub[k]));
                }
            }
        }
        return f[n][m];        
    }
}

你可能感兴趣的:(LeetCode)