题目地址:
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]=s∣bmin{g[k−1][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:k−1]的乘积变为 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:k−1]的乘积变为 f [ j ] / s f[j]/s f[j]/s的最小代价是 g [ k − 1 ] [ m [ f [ j ] / s ] ] g[k-1][m[f[j]/s]] g[k−1][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)。