Kimi LeetCode 3621. 位计数深度为 K 的整数数目 I Java实现
LeetCode 3621. 位计数深度为 K 的整数数目 I
题目描述
给定两个整数 `n` 和 `k`。对任意正整数 `x`,定义序列:
- `p₀ = x`
- `pᵢ₊₁ = popcount(pᵢ)`(二进制中 1 的个数)
序列最终会到达 1。`x` 的 popcount-depth 定义为使得 `p_d = 1` 的最小非负整数 `d`。
求 `[1, n]` 中 popcount-depth 恰好等于 `k` 的整数个数。
约束: `1 ≤ n ≤ 10¹⁵`,`0 ≤ k ≤ 5`
---
解题思路
关键观察:
- `D(1) = 0`(1 本身就是终点)
- `x > 1` 时,`D(x) = 1 + D(popcount(x))`
- 因此 `D(x) = k` 等价于 `D(popcount(x)) = k - 1`
算法步骤:
1. 预处理深度:对 `1 ~ 60` 的所有数预计算 popcount-depth(`n ≤ 10¹⁵` 的二进制最多 50 位)
2. 确定目标 popcount:找出所有满足 `D(c) = k - 1` 的 `c`
3. 数位 DP:统计 `[1, n]` 中二进制恰好有 `c` 个 1 的数的个数
4. 边界处理:`k = 0` 时只有 `x = 1`;`k = 1` 时需排除 `x = 1`(因为 `D(1) = 0`)
时间复杂度: `O(log² n)`
空间复杂度: `O(log² n)`
---
Java 实现
```java
import java.util.*;
class Solution {
// 计算一个数的 popcount(二进制1的个数)
private int popcount(int x) {
int cnt = 0;
while (x > 0) {
cnt += (x & 1);
x >>= 1;
}
return cnt;
}
// 计算 popcount-depth
private int getDepth(int x, int[] memo) {
if (x == 1) return 0;
if (memo[x] != -1) return memo[x];
memo[x] = 1 + getDepth(popcount(x), memo);
return memo[x];
}
// 数位DP:统计 [0, n] 中二进制恰好有 targetOnes 个1的数的个数
// s 是 n 的二进制字符串
private long dfs(int pos, int ones, boolean tight, String s, int targetOnes, Long[][] memo) {
if (ones > targetOnes) return 0; // 1的个数已超,剪枝
if (pos == s.length()) {
return ones == targetOnes ? 1 : 0;
}
int tightFlag = tight ? 1 : 0;
if (!tight && memo[pos][ones] != null) {
return memo[pos][ones];
}
int limit = tight ? (s.charAt(pos) - '0') : 1;
long res = 0;
for (int d = 0; d <= limit; d++) {
res += dfs(pos + 1, ones + d, tight && (d == limit), s, targetOnes, memo);
}
if (!tight) {
memo[pos][ones] = res;
}
return res;
}
public long popcountDepth(long n, int k) {
// k = 0:只有 1 的深度为 0
if (k == 0) {
return n >= 1 ? 1 : 0;
}
// 预处理 1~60 的深度(n <= 1e15 < 2^50,popcount 最大为 50)
int[] depthMemo = new int[65];
Arrays.fill(depthMemo, -1);
depthMemo[1] = 0;
// 找出所有 popcount 值 c,使得 D(c) = k - 1
List<Integer> targetPopcounts = new ArrayList<>();
for (int i = 1; i <= 60; i++) {
if (getDepth(i, depthMemo) == k - 1) {
targetPopcounts.add(i);
}
}
if (targetPopcounts.isEmpty()) {
return 0;
}
// 将 n 转为二进制字符串
String s = Long.toBinaryString(n);
long ans = 0;
for (int c : targetPopcounts) {
// 记忆化数组:memo[pos][ones],仅非 tight 状态有效
Long[][] memo = new Long[s.length() + 1][c + 1];
ans += dfs(0, 0, true, s, c, memo);
}
// k = 1 的特殊处理:popcount = 1 的数包括 1, 2, 4, 8...
// 但 D(1) = 0,不应计入 k=1,所以减去 1
if (k == 1) {
ans -= 1;
}
return ans;
}
}
```
---
代码要点
要点 说明
深度预处理 `getDepth` 用记忆化递归计算,利用 `D(x) = 1 + D(popcount(x))`
数位 DP 按二进制位从高到低枚举,`tight` 控制是否受 `n` 限制
非 tight 记忆化 只有 `tight = false` 时结果可复用,用 `Long[][]` 存储
k = 1 修正 `popcount = 1` 包含 `x = 1`,但其深度为 0,需减去
返回值类型 用 `long`,因为 `n` 最大时答案可能很大
如需其他语言版本(Python/C++/Go)或更详细的数位 DP 推导,可以继续问我。