【Lintcode】1798. Minimum Cost to Merge Stones

题目地址:

https://www.lintcode.com/problem/minimum-cost-to-merge-stones/description

给定一个长 n n n数组 A A A,再给定一个大于 1 1 1的正整数 k k k,每次允许将 A A A中长 k k k的区间合并为区间数之和,代价就是这个和。问最终要将 A A A合并为一个数的最低代价。如果不存在这种方案则返回 − 1 -1 1

显然对于 m m m个数,经过若干次合并之后所剩下的数的最小个数是确定的。每次合并之后,数字就减少了 k − 1 k-1 k1个,所以合并能得到的剩余数的最小个数就是 n m o d    ( k − 1 ) n \mod (k-1) nmod(k1)。如果要求最后剩下 1 1 1个数,就要求 n m o d    ( k − 1 ) = 1 n \mod (k-1)=1 nmod(k1)=1,也就是 n − 1 ≡ 0 ( m o d    ( k − 1 ) ) n-1\equiv 0 (\mod (k-1)) n10(mod(k1))。这样可以事先排除掉不可能的情况。
接着用动态规划。设 f [ i ] [ j ] f[i][j] f[i][j]是合并 A [ i : j ] A[i:j] A[i:j]得到最少个数的数的情况下,消耗的最小代价。当 j − i + 1 < k j-i+1< k ji+1<k的时候,此时无法进行合并,所以 f [ i ] [ j ] = 0 f[i][j]=0 f[i][j]=0。对于别的情况,也就是区间长度大于等于 k k k的情况,可以根据最后合并的时候的分割点分类讨论。我们可以只讨论最后合并的分割点左边全合并为 1 1 1个数的情况(因为所有的合并方案,最后剩下的数必然存在第一个数,所以所有方案都可以按照“第一个数是怎么来的”来分类,也就是可以按照使得左边合并为 1 1 1个数的分割点来分类),要使得左边合并为 1 1 1个数,就是要左边数的个数满足上面的方程 n − 1 ≡ 0 ( m o d    ( k − 1 ) ) n-1\equiv 0 (\mod (k-1)) n10(mod(k1)),即它模 k − 1 k-1 k1必须等于 1 1 1。此外,如果区间长度 l = j − i + 1 l=j-i+1 l=ji+1满足 l − 1 ≡ 0 ( m o d    ( k − 1 ) ) l-1\equiv 0(\mod (k-1)) l10(mod(k1)),还可以再合并一次(因为在这种情况下,长 l l l的区间最后合并只会剩下一个数,而左边合并完剩下 1 1 1个数,所以右边合并完最少剩下的数一定是 k − 1 k-1 k1,所以还能合并 1 1 1次),所以还需要再加上 ∑ A [ i : j ] \sum A[i:j] A[i:j]。综上,有 f [ i ] [ j ] = 1 j − i ≡ 0 ( m o d    ( k − 1 ) ) ∑ A [ i : j ] + min ⁡ i ≤ s ≤ j ∧ s − i ≡ 0 ( m o d    ( k − 1 ) ) ( f [ i ] [ s ] + f [ s ] [ j ] ) f[i][j]=1_{j-i\equiv 0(\mod (k-1))}\sum A[i:j]+\min_{i\le s\le j\land s-i\equiv 0(\mod (k-1))}(f[i][s]+f[s][j]) f[i][j]=1ji0(mod(k1))A[i:j]+isjsi0(mod(k1))min(f[i][s]+f[s][j])代码如下:

public class Solution {
    
    
    /**
     * @param stones:
     * @param K:
     * @return: return a integer
     */
    public int mergeStones(int[] stones, int K) {
    
    
        // write your code here
        int n = stones.length;
        // 排除掉不可能最后剩下一个数的情况
        if ((n - 1) % (K - 1) != 0) {
    
    
            return -1;
        }
        
        // 后面要频繁计算区间和,所以这里预处理一下前缀和
        int[] preSum = new int[n + 1];
        for (int i = 0; i < n; i++) {
    
    
            preSum[i + 1] = preSum[i] + stones[i];
        }
        
        int[][] dp = new int[n][n];
        // 总长度小于K的时候无法合并,代价就是0,不用算了直接略过
        for (int len = K; len <= n; len++) {
    
    
        	// 枚举左端点
            for (int l = 0; l + len - 1 < n; l++) {
    
    
            	// 算出右端点
                int r = l + len - 1;
                dp[l][r] = Integer.MAX_VALUE / 2;
                // 枚举使得A[l:r]可以合并成1个数的分割点
                for (int i = l; i < r; i += K - 1) {
    
    
                    dp[l][r] = Math.min(dp[l][r], dp[l][i] + dp[i + 1][r]);
                }
                
                // 如果总长度满足合并只剩一个数的条件,则可以再合并一次
                if ((len - 1) % (K - 1) == 0) {
    
    
                    dp[l][r] += preSum[r + 1] - preSum[l];
                }
            }
        }
        
        return dp[0][n - 1];
    }
}

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

猜你喜欢

转载自blog.csdn.net/qq_46105170/article/details/113066029