【Lintcode】593. Stone Game II

这篇博客介绍了如何使用动态规划解决一个合并石堆的问题。给定一圈石头,每次可以合并相邻的两堆,合并代价是两堆石头质量之和。通过复制原数组并构建新的动态规划状态转移方程,可以找到将所有石头合并成一堆的最小能量。算法的时间复杂度为O(n^3),空间复杂度为O(n^2)。
摘要由CSDN通过智能技术生成

题目地址:

https://www.lintcode.com/problem/stone-game-ii/description

给定一圈石头一共 n n n堆,每堆石头的质量由数组 A A A给出,每次允许将相邻的两堆石头合并,每次合并需要花费的能量是两堆石头的质量之和。问如果要将所有石头合并成一堆,需要花费的最小能量是多少。

思路是动态规划。问题的关键在于如何处理首尾两堆合并的情况。我们采取的办法是,将 A A A复制一份连到后面,得一个新数组 B B B,也就是说 B [ 0 ] = A [ 0 ] , . . . , B [ n − 1 ] = A [ n − 1 ] , B [ n ] = A [ 0 ] , B [ n + 1 ] = A [ 1 ] , . . . , B [ 2 n − 2 ] = A [ n − 2 ] B[0]=A[0],...,B[n-1]=A[n-1],B[n]=A[0],B[n+1]=A[1],...,B[2n-2]=A[n-2] B[0]=A[0],...,B[n1]=A[n1],B[n]=A[0],B[n+1]=A[1],...,B[2n2]=A[n2],接着对这个 B B B考虑长度为 n n n的区间合并石堆的最小花费即可。关于算法的正确性,我们只需考虑最后一次合并的时候,左右石堆的左端点和右端点即可。对于 A A A的最优合并方式,考虑最后一次合并的两个大石堆,我们可以顺时针来看 A A A这个环,取第一个大石堆的顺时针方向的第一个小石堆,和第二个大石堆顺时针方向的最后一个小石堆,这两个小石堆在 A A A中的下标一定要么相邻,要么一个是 0 0 0一个是 n − 1 n-1 n1,但无论怎样,都可以在 B B B中找到对应的长度为 n n n的一段,恰好以这两个端点为端点,所以算法是对的。
而对于 B B B而言,设 f [ i ] [ j ] f[i][j] f[i][j]是合并 B [ i : j ] B[i:j] B[i:j]这个区间的石堆需要的最小花费,则可以枚举最后一次合并的分界点来做分类,所以有: f [ i ] [ j ] = min ⁡ i ≤ k ≤ j − 1 { f [ i ] [ k ] + f [ k + 1 ] [ j ] + ∑ l = i j B [ l ] } f[i][j]=\min_{i\le k\le j-1}\{f[i][k]+f[k+1][j]+\sum_{l=i}^{j}B[l]\} f[i][j]=ikj1min{f[i][k]+f[k+1][j]+l=ijB[l]}最后只需要计算一下 min ⁡ j − i + 1 = n f [ i ] [ j ] \min_{j-i+1=n}f[i][j] ji+1=nminf[i][j]代码如下:

public class Solution {
    /**
     * @param A: An integer array
     * @return: An integer
     */
    public int stoneGame2(int[] A) {
        // write your code here
        if (A == null || A.length == 0) {
            return 0;
        }
        
        int n = A.length;
        int[] B = new int[n * 2 - 1];
        for (int i = 0; i < B.length; i++) {
            B[i] = A[i % n];
        }
        
        A = B;
        
        // 求一下前缀和,以方便求区间和
        int[] preSum = new int[A.length + 1];
        for (int i = 0; i < A.length; i++) {
            preSum[i + 1] = preSum[i] + A[i];
        }
        
        int[][] dp = new int[A.length][A.length];
        
        // 枚举区间长度
        for (int len = 2; len <= n; len++) {
        	// 枚举左端点
            for (int i = 0; i + len - 1 < A.length; i++) {
            	// 算出区间左右端点,并进行计算
                int l = i, r = l + len - 1;
                dp[l][r] = Integer.MAX_VALUE;
                for (int k = l; k <= r - 1; k++) {
                    dp[l][r] = Math.min(dp[l][r], dp[l][k] + dp[k + 1][r] + preSum[r + 1] - preSum[l]);
                }
            }
        }
        
        // 找一下长度为n的区间合并石堆最小消耗
        int res = Integer.MAX_VALUE;
        for (int l = 0; l + n - 1 < A.length; l++) {
            res = Math.min(res, dp[l][l + n - 1]);
        }
        
        return res;
    }
}

时间复杂度 O ( n 3 ) O(n^3) O(n3),空间 O ( n 2 ) O(n^2) O(n2)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值