LeetCode 2338 统计理想数组的数目
题目链接
题目简介
给你两个整数 n n n 和 m a x V a l u e maxValue maxValue ,用于描述一个 理想数组 。
对于下标从 0 0 0 开始、长度为 n n n 的整数数组 a r r arr arr ,如果满足以下条件,则认为该数组是一个 理想数组 :
- 每个 a r r [ i ] arr[i] arr[i] 都是从 1 1 1 到 m a x V a l u e maxValue maxValue 范围内的一个值,其中 0 < = i < n 0 <= i < n 0<=i<n 。
- 每个
a
r
r
[
i
]
arr[i]
arr[i] 都可以被
a
r
r
[
i
−
1
]
arr[i - 1]
arr[i−1] 整除,其中
0
<
i
<
n
0 < i < n
0<i<n 。
返回长度为 n 的 不同 理想数组的数目。由于答案可能很大,返回对 1 0 9 + 7 10^9 + 7 109+7 取余的结果。
提示
- 2 < = n < = 1 0 4 2 <= n <= 10^4 2<=n<=104
- 1 < = m a x V a l u e < = 1 0 4 1 <= maxValue <= 10^4 1<=maxValue<=104
解题思路
递归函数(TLE)
l a s t V a l u e lastValue lastValue指数组首元素必须为 l a s t V a l u e lastValue lastValue的倍数
- 对于求解长度为
n
n
n、
l
a
s
t
V
a
l
u
e
lastValue
lastValue为
l
l
l、
m
a
x
V
a
l
u
e
maxValue
maxValue为
k
k
k的问题
f
(
n
,
l
,
k
)
f(n,l,k)
f(n,l,k),可以规约为求解
∑ i = l k f ( n − 1 , i , k ) , 其中 i m o d l = 0 \sum_{i=l}^{k}f(n-1,i,k) , 其中i\ mod\ l=0 i=l∑kf(n−1,i,k),其中i mod l=0
//cpp
class Solution {
public:
int mod = 1e9 + 7;
long long solve(int n,int lastValue,int maxValue){
if(n==0)return 1;
long long cnt = 0;
for(int i=lastValue;i<=maxValue;i+=lastValue){
cnt += solve(n-1,i,maxValue);
cnt %= mod;
}
return cnt;
}
int idealArrays(int n, int maxValue) {
return solve(n,1,maxValue);
}
};
- 由于递归函数对于相同的问题会重复计算,且有较高的时间复杂度,最终结果为TLE
动态规划(TLE)
- 动态规划数组 d p [ n ] [ m a x V a l u e + 1 ] dp[n][maxValue+1] dp[n][maxValue+1]
- 转移公式
d p [ i ] [ j ] = ∑ k = j m a x V a l u e d p [ i − 1 ] [ k ] , 其中 k m o d j = 0 dp[i][j] = \sum_{k=j}^{maxValue}dp[i-1][k] ,其中k\ mod\ j=0 dp[i][j]=k=j∑maxValuedp[i−1][k],其中k mod j=0 - 最终结果为
∑ j = 1 m a x V a l u e d p [ n − 1 ] [ j ] \sum_{j=1}^{maxValue}dp[n-1][j] j=1∑maxValuedp[n−1][j] - 采用了滚动数组的思路
//cpp
class Solution {
public:
int idealArrays_ref(int n, int maxValue) {
//dp
int dp[2][maxValue+1],t,i,j,k,idx1,idx2,ret;
for(i=1;i<=maxValue;i++)dp[0][i] = 1;
for(i=1;i<n;i++){
idx1 = i%2;
idx2 = !idx1;
for(j=1;j<=maxValue;j++){
t=0;
for(k=j;k<=maxValue;k+=j){
t += dp[idx2][k];
t %= mod;
}
dp[idx1][j] = t;
}
}
ret = 0;
idx1 = (n-1)%2;
for(i=1;i<=maxValue;i++){
ret += dp[idx1][i];
ret %= mod;
}
return ret;
}
}
- 由于动态规划同样有较大的时间复杂度 O ( n ∗ m a x V a l u e ∗ l n ( m a x V a l u e ) ) O(n*maxValue*ln(maxValue)) O(n∗maxValue∗ln(maxValue)),最终结果为TLE
待定系数法(AC)
- 不难(hennan)发现最终的结果可以用多项式表示,即
∑ i = 0 e x p k i ∗ x i (1) \sum_{i=0}^{exp}k_i*x^i \tag{1} i=0∑expki∗xi(1)
其中 2 e x p < = m a x V a l u e 且 2 e x p + 1 > m a x V a l u e , x = n + 1 , k i 为常数项 其中2^{exp}<=maxValue 且2^{exp+1}>maxValue ,\\x=n+1,k_i为常数项 其中2exp<=maxValue且2exp+1>maxValue,x=n+1,ki为常数项 - 这样我们就可以通过动态规划求得 n ∈ [ 0 , e x p ] n\in[0,exp] n∈[0,exp]时公式(1)的值,因此我们就得到了 e x p + 1 exp+1 exp+1个含有 e x p + 1 exp+1 exp+1个未知数(常数项 k i k_i ki)的线性方程组
n = 0 n=0 n=0时公式(1)值为1,即只存在一个空数组
- 通过求解线性方程组便获得了所有系数的值,将 x = n + 1 x=n+1 x=n+1代入公式(1)即可求得结果
- 由于本方法所需精度较高,所以使用了java的BigDecimal
//java
import java.math.BigDecimal;
class Solution {
static int mod = (int)1e9+7;
static long dp[][];
static final int div_precison = 256;
public static void linear_equ(BigDecimal coeff[],int n) {
//double data[][] = new double[n][n+1];
BigDecimal data[][] = new BigDecimal[n][n+1];
for(int i=0;i<n;i++) {
for(int j=0;j<n+1;j++) {
data[i][j] = new BigDecimal(0);
}
}
for (int i = 0;i < n;i++){
for (int k = 0;k < n+1;k++){
if(k<n) { //data[i][k] = Math.pow(i+1,k);
data[i][k] = new BigDecimal(i+1);
data[i][k] = data[i][k].pow(k);
}
else{
data[i][k] = new BigDecimal(dp[i][1]);
}
}
}
for (int k = 0;k < n;k++){
for (int j = k + 1;j < n;j++){
BigDecimal t1 = new BigDecimal(0).add(data[j][k]);
BigDecimal t2 = new BigDecimal(0).add(data[k][k]);
for (int m = 0;m < n+1;m++){
//data[j][m] -= data[k][m] * data[j][k] / data[k][k];
data[j][m] = data[j][m].subtract(
data[k][m].multiply(t1).divide(t2));
}
}
}
for (int i = n-1;i >= 0;i--){
BigDecimal t = new BigDecimal(0);
for (int k = 0;k < n;k++){
if (k == i)continue;
t = t.add(coeff[k].multiply(data[i][k]));
}
coeff[i] = data[i][n].subtract(t).
divide(data[i][i],div_precison,BigDecimal.ROUND_DOWN) ;
}
}
public static int pre(int num){
int ret = 0;
while(Math.pow(2,ret)<=num)ret++;
return ret-1;
}
public static int idealArrays(int n, int maxValue) {
int exp_num = pre(maxValue);
dp = new long[exp_num+1][maxValue+1];
double startTime,endTime;
startTime = System.currentTimeMillis();
for(int i=1;i<=maxValue;i++)dp[0][i] = 1;
for(int i=1;i<=exp_num;i++){
for(int j=1;j<=maxValue;j++){
long t=0;
for(int k=j;k<=maxValue;k+=j){
t += dp[i-1][k];
}
dp[i][j] = t;
}
}
endTime = System.currentTimeMillis();
System.out.println("elapsed time(bottleneck):"+(endTime-startTime)+"ms");
BigDecimal coeff[] = new BigDecimal[exp_num+1]; // coefficient array
for(int i=0;i<coeff.length;i++) {
coeff[i] = new BigDecimal(0);
}
linear_equ(coeff,exp_num+1);
BigDecimal ret_num = new BigDecimal(0);
BigDecimal MOD = new BigDecimal(mod);
for(int i=0;i<coeff.length;i++){ //ret_num += pow(n+1,i) * ret[i];
BigDecimal t1 = new BigDecimal(n+1);
t1 = t1.pow(i);
ret_num = ret_num.add(t1.multiply(coeff[i]));
ret_num = ret_num.remainder(MOD);
}
System.out.println(ret_num);
return ret_num.add(new BigDecimal("0.5")).intValue();//round(ret_num)
}
public static void main(String[] args) {
double startTime,endTime;
startTime = System.currentTimeMillis();
System.out.println(idealArrays((int)1e6,(int)1e6));
endTime = System.currentTimeMillis();
System.out.println("elapsed time(tot):"+(endTime-startTime)+"ms");
}
}
- 最终成功AC,时间复杂度为 O ( m a x V a l u e ∗ l n ( m a x V a l u e ) ) O(maxValue*ln(maxValue)) O(maxValue∗ln(maxValue)),主要瓶颈在于求 n ∈ [ 0 , e x p ] n\in[0,exp] n∈[0,exp]时公式(1)的值