【每日一题Day167】LC1000合并石头的最低成本 | 区间dp

合并石头的最低成本【LC1000】

N 堆石头排成一排,第 i 堆中有 stones[i] 块石头。

每次*移动(move)*需要将连续的 K 堆石头合并为一堆,而这个移动的成本为这 K 堆石头的总数。

找出把所有石头合并成一堆的最低成本。如果不可能,返回 -1

等校车的时候用手机看了一眼,感觉是区间dp,但这几天都在练习区间dp,心里想不会那么巧吧

然后坐下来就没往区间dp想,首先先确定石头堆数和K的关系,然后想到贪心,先合并的K堆石, 在之后还会与其他石头进行合并,因此优先合并连续k堆石头最小的k堆,然后删除合并的石头,加入新合并后的,然后再进行删除,直到石头的堆数为k。然后遇到了错误的案例,上面的贪心不是最优的,然后想着那就每次枚举所有的 k k k堆石头吧,然后超时了。最后放弃,看题解,emm,真的是区间dp…

递归+记忆化
  • 思路

    • 确定石头堆数 n n n k k k的关系

      我们最终需要将 n n n堆石头合并成1堆,需要减少 n − 1 n-1 n1堆,每次操作可以合并 k k k堆石头,可以减少 k − 1 k-1 k1堆石头。那么 n − 1 n-1 n1必须可以整除 k − 1 k-1 k1,才可以合并成1堆石头。因此当不能整除时,直接返回-1。

    • 找到子问题

      • 最后一步一定是从 n = k n=k n=k堆石头合并成1堆石头,或者本身就只有一堆石头,合并的代价为这些石头的总和
      • 第一堆石头是怎么得到的?
        • 就是初始情况下的stone[0]
        • 通过一次合并得到
        • 通过两次合并得到
      • 对于右边剩余的石头,我们需要计算的是把这些石头堆合并成 k − 1 k-1 k1堆需要的最低成本

    因此可以定义递归函数 d f s ( i , j , p ) dfs(i,j,p) dfs(i,j,p)表示将第 i i i堆至第 j j j堆石头合并成 p p p堆的最低成本

    • 递归过程:

      • 如果p为1,那么下一个子问题时,将这些石头合并成 k k k堆的最小成本,即 d f s ( i , j , k ) dfs(i,j,k) dfs(i,j,k)
        d f s ( i , j , 1 ) = d f s ( i , j , k ) + ∑ q = i j s t o n e [ q ] dfs(i,j,1)=dfs(i,j,k)+\sum _{q=i}^{j}stone[q] dfs(i,j,1)=dfs(i,j,k)+q=ijstone[q]
        某个区间之和使用前缀和数组优化

      • 如果p不为1,那么将石头分为两部分,以m为分割点,使左边部可以合并为1堆,因此 m = i + ( k − 1 ) x m=i+(k-1)x m=i+(k1)x,那么剩余部分合并为 p − 1 p-1 p1

      d f s ( i , j , p ) = m i n m = i + ( k − 1 ) x d f s ( i , m , 1 ) + d f s ( m + 1 , j , p − 1 ) dfs(i,j,p)=min_{m=i+(k-1)x}{dfs(i,m,1)+dfs(m+1,j,p-1)} dfs(i,j,p)=minm=i+(k1)xdfs(i,m,1)+dfs(m+1,j,p1)

    • 递归边界

      d f s ( i , i , 1 ) = 0 dfs(i,i,1)=0 dfs(i,i,1)=0 ,只有一堆石头,不需要合并

    • 递归入口

      d f s ( 0 , n − 1 , 1 ) dfs(0,n-1,1) dfs(0,n1,1)

    优化:

    【每日一题Day167】LC1000合并石头的最低成本 | 区间dp_第1张图片

  • 实现

    class Solution {
        int[][][] dp;
        int[] sum;
        int k;
        public int mergeStones(int[] stones, int k) {
            int n = stones.length;
            if ((n - 1) % (k - 1) != 0) return -1;
            dp = new int[n + 1][n + 1][k + 1];
            sum = new int[n + 1];
            this.k = k;
            for (int i = 0; i <= n; i++){
                for (int j = 0; j <= n; j++){
                    Arrays.fill(dp[i][j], -1);
                }
            }
            for (int i = 0; i < n; i++){
                sum[i + 1] = sum[i] + stones[i];
            }
            return dfs(0, n - 1 , 1);
        }
        public int dfs(int i, int j, int p){
            if (dp[i][j][p] != -1) return dp[i][j][p];
            if (p == 1){
                return i == j ? 0 : dfs(i, j, k) + sum[j + 1] - sum[i];
            }
            int res = Integer.MAX_VALUE;
            for (int m = i; m < j; m += k - 1){
                res = Math.min(res, dfs(i, m, 1) + dfs(m + 1, j, p - 1));
            }
            dp[i][j][p] = res;
            return res;
        }
    
    }
    
    • 复杂度

      • 时间复杂度: O ( n 3 ) O(n^3) O(n3),其中 n 为 stones 的长度。动态规划的时间复杂度 = 状态个数 × 单个状态的计算时间。这里状态个数为 O ( n 2 k ) O(n^2k) O(n2k),单个状态的计算时间为 O ( n / k ) O(n/k) O(n/k) ,因此时间复杂度为 O ( n 3 ) O(n^3) O(n3)
      • 空间复杂度: O ( n 2 ∗ k ) O(n^2*k) O(n2k)
  • 实现:优化

    class Solution {
        private int[][] memo;
        private int[] s;
        private int k;
    
        public int mergeStones(int[] stones, int k) {
            int n = stones.length;
            if ((n - 1) % (k - 1) > 0) // 无法合并成一堆
                return -1;
    
            s = new int[n + 1];
            for (int i = 0; i < n; i++)
                s[i + 1] = s[i] + stones[i]; // 前缀和
            this.k = k;
            memo = new int[n][n];
            for (int i = 0; i < n; ++i)
                Arrays.fill(memo[i], -1); // -1 表示还没有计算过
            return dfs(0, n - 1);
        }
    
        private int dfs(int i, int j) {
            if (i == j) return 0; // 只有一堆石头,无需合并
            if (memo[i][j] != -1) return memo[i][j];
            int res = Integer.MAX_VALUE;
            for (int m = i; m < j; m += k - 1)
                res = Math.min(res, dfs(i, m) + dfs(m + 1, j));
            if ((j - i) % (k - 1) == 0) // 可以合并成一堆
                res += s[j + 1] - s[i];
            return memo[i][j] = res;
        }
    }
    
    作者:灵茶山艾府
    链接:https://leetcode.cn/problems/minimum-cost-to-merge-stones/solutions/2207235/tu-jie-qu-jian-dpzhuang-tai-she-ji-yu-yo-ppv0/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    
    • 复杂度

      • 时间复杂度: O ( n 3 / k ) O(n^3/k) O(n3/k),其中 n 为 stones 的长度。动态规划的时间复杂度 = 状态个数 × 单个状态的计算时间。这里状态个数为 O ( n 2 ) O(n^2) O(n2),单个状态的计算时间为 O ( n / k ) O(n/k) O(n/k) ,因此时间复杂度为 O ( n 3 / k ) O(n^3/k) O(n3/k)
      • 空间复杂度: O ( n 2 ∗ k ) O(n^2*k) O(n2k)
动态规划
class Solution {
    public int mergeStones(int[] stones, int k) {
        int n = stones.length;
        if ((n - 1) % (k - 1) > 0) // 无法合并成一堆
            return -1;

        var s = new int[n + 1];
        for (int i = 0; i < n; i++)
            s[i + 1] = s[i] + stones[i]; // 前缀和

        var f = new int[n][n];
        for (int i = n - 1; i >= 0; --i)
            for (int j = i + 1; j < n; ++j) {
                f[i][j] = Integer.MAX_VALUE;
                for (int m = i; m < j; m += k - 1)
                    f[i][j] = Math.min(f[i][j], f[i][m] + f[m + 1][j]);
                if ((j - i) % (k - 1) == 0) // 可以合并成一堆
                    f[i][j] += s[j + 1] - s[i];
            }
        return f[0][n - 1];
    }
}

作者:灵茶山艾府
链接:https://leetcode.cn/problems/minimum-cost-to-merge-stones/solutions/2207235/tu-jie-qu-jian-dpzhuang-tai-she-ji-yu-yo-ppv0/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 复杂度

    • 时间复杂度: O ( n 3 / k ) O(n^3/k) O(n3/k),其中 n 为 stones 的长度。动态规划的时间复杂度 = 状态个数 × 单个状态的计算时间。这里状态个数为 O ( n 2 ) O(n^2) O(n2),单个状态的计算时间为 O ( n / k ) O(n/k) O(n/k) ,因此时间复杂度为 O ( n 3 / k ) O(n^3/k) O(n3/k)
    • 空间复杂度: O ( n 2 ∗ k ) O(n^2*k) O(n2k)

你可能感兴趣的:(每日一题,动态规划,深度优先,算法)