【Lintcode】1322. Product Equal B

题目地址:

https://www.lintcode.com/problem/product-equal-b/description

给定一个长 n n n数组 A A A,再给定一个数 b b b,每次可以花费 1 1 1的代价将序列中的某个数加 1 1 1或者减 1 1 1。问使得整个 A A A的乘积成为 b b b的最小代价。

思路是动态规划。先求出 b b b的所有因子,设为其为数组 f f f,即 f [ i ] f[i] f[i] b b b的从小到大的第 i + 1 i+1 i+1个因子。设 g [ k ] [ j ] g[k][j] g[k][j]是调整 A [ 0 : k ] A[0:k] A[0:k]这个子数组使得 ∏ A [ 0 : k ] = f [ j ] \prod A[0:k]=f[j] A[0:k]=f[j]的最小代价。若 s s s b b b的因子,设其坐标是 m [ s ] m[s] m[s]。我们枚举 A [ k ] A[k] A[k]变成 s s s的情况, s s s b b b的所有因子。则有: g [ k ] [ j ] = min ⁡ s ∣ b { g [ k − 1 ] [ m [ f [ j ] / s ] ] + ∣ A [ k ] − s ∣ } g[k][j]=\min_{s|b} \{g[k-1][m[f[j]/s]]+|A[k]-s|\} g[k][j]=sbmin{g[k1][m[f[j]/s]]+A[k]s}上式的含义是,我们考虑如果要将 A [ 0 : k ] A[0:k] A[0:k]的乘积变为 f [ j ] f[j] f[j]的最小代价。如果最后一个数要变成 s s s,则先要的代价是 ∣ A [ k ] − s ∣ |A[k]-s| A[k]s,此外要让 A [ 0 : k − 1 ] A[0:k-1] A[0:k1]的乘积变为 f [ j ] / s f[j]/s f[j]/s,当然 f [ j ] / s f[j]/s f[j]/s也是 b b b的因子,其在 f f f中的下标是 m [ f [ j ] / s ] m[f[j]/s] m[f[j]/s],所以调整 A [ 0 : k − 1 ] A[0:k-1] A[0:k1]的乘积变为 f [ j ] / s f[j]/s f[j]/s的最小代价是 g [ k − 1 ] [ m [ f [ j ] / s ] ] g[k-1][m[f[j]/s]] g[k1][m[f[j]/s]] s s s取遍所有 b b b的因子就得到了 g [ k ] [ j ] g[k][j] g[k][j]。初始条件 g [ 0 ] [ j ] = ∣ A [ 0 ] − f [ j ] ∣ g[0][j]=|A[0]-f[j]| g[0][j]=A[0]f[j]。代码如下:

import java.util.*;

public class Solution {
    /**
     * @param B: the all Ai product equal to B
     * @param A: the positive int array
     * @return: return the minium cost
     */
    public int getMinCost(int B, int[] A) {
        // write your code here
        List<Integer> fac = new ArrayList<>();
        Map<Integer, Integer> map = new HashMap<>();
        // 求B的所有因子,存入fac列表,并用map记录每个因子在fac中的下标
        for (int i = 1; i <= B; i++) {
            if (B % i == 0) {
                fac.add(i);
                map.put(i, fac.size() - 1);
            }
        }
    
        int n = fac.size();
        
        // dp[k][i]是调整A[0 : k]使得整个乘积是fac[i]的最小代价
        int[][] dp = new int[A.length][n];
        for (int i = 0; i < dp[0].length; i++) {
            dp[0][i] = Math.abs(A[0] - fac.get(i));
        }
    
        for (int i = 1; i < A.length; i++) {
        	// 先初始化为正无穷
            Arrays.fill(dp[i], Integer.MAX_VALUE);
            // 枚举A[0 : i]的乘积是fac[j]
            for (int j = 0; j < n; j++) {
                // 枚举最后一个数调整为fac[k]
                for (int k = 0; k <= j; k++) {
                    if (fac.get(j) % fac.get(k) == 0) {
                        dp[i][j] = Math.min(dp[i][j], dp[i - 1][map.get(fac.get(j) / fac.get(k))] + Math.abs(A[i] - fac.get(k)));
                    }
                }
            }
        }
        
        return dp[A.length - 1][n - 1];
    }
}

时间复杂度 O ( b + n k 2 ) O(b+nk^2) O(b+nk2) k k k b b b的因子个数,空间 O ( n k ) O(nk) O(nk)

可以用滚动数组优化。代码如下:

import java.util.*;

public class Solution {
    /**
     * @param B: the all Ai product equal to B
     * @param A: the positive int array
     * @return: return the minium cost
     */
    public int getMinCost(int B, int[] A) {
        // write your code here
        List<Integer> fac = new ArrayList<>();
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 1; i <= B; i++) {
            if (B % i == 0) {
                fac.add(i);
                map.put(i, fac.size() - 1);
            }
        }
    
        int n = fac.size();
        int[][] dp = new int[2][n];
        for (int i = 0; i < dp[0].length; i++) {
            dp[0][i] = Math.abs(A[0] - fac.get(i));
        }
    
        for (int i = 1; i < A.length; i++) {
            Arrays.fill(dp[i & 1], Integer.MAX_VALUE);
            for (int j = 0; j < n; j++) {
                for (int k = 0; k <= j; k++) {
                    if (fac.get(j) % fac.get(k) == 0) {
                        dp[i & 1][j] = Math.min(dp[i & 1][j], dp[i - 1 & 1][map.get(fac.get(j) / fac.get(k))] + Math.abs(A[i] - fac.get(k)));
                    }
                }
            }
        }
        
        return dp[A.length - 1 & 1][n - 1];
    }
}

时间复杂度不变,空间 O ( n ) O(n) O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值