2025-12-31:位计数深度为 K 的整数数目Ⅱ。用go语言,给定一个整数数组 nums。对任意正整数 x,按下面方式构造序列:
-
p0 = x;
-
对于 i ≥ 0,令 pi+1 为 pi 的二进制表示中 1 的个数(即 popcount(pi))。
这个序列最终会落到 1。定义 x 的“位计数深度”为最小的非负整数 d,使得 pd = 1。举例:x = 7(二进制 111)时序列为 7 → 3 → 2 → 1,因此 7 的位计数深度为 3。
还给出一个二维数组 queries,其中每一项有两种形式之一:
-
[1, l, r, k]:询问闭区间 [l, r] 中有多少个下标 j 满足 nums[j] 的位计数深度等于 k;
-
[2, idx, val]:将 nums[idx] 的值替换为 val。
要求按 queries 中的顺序处理这些操作,并返回一个数组 answer,包含所有类型为 [1, l, r, k] 的查询结果,顺序与这些查询出现的顺序一致。
1 <= n == nums.length <= 100000。
1 <= nums[i] <= 1000000000000000。
1 <= queries.length <= 100000。
queries[i].length == 3 或 4。
queries[i] == [1, l, r, k] 或
queries[i] == [2, idx, val]。
0 <= l <= r <= n - 1。
0 <= k <= 5。
0 <= idx <= n - 1。
1 <= val <= 1000000000000000。
输入: nums = [2,4], queries = [[1,0,1,1],[2,1,1],[1,0,1,0]]。
输出: [2,1]。
解释:
| i | queries[i] | nums | binary(nums) | popcount-depth | [l, r] | k | 有效 nums[j] | 更新后的 nums | 答案 |
|---|---|---|---|---|---|---|---|---|---|
| 0 | [1,0,1,1] | [2,4] | [10, 100] | [1, 1] | [0, 1] | 1 | [0, 1] | — | 2 |
| 1 | [2,1,1] | [2,4] | [10, 100] | [1, 1] | — | — | — | [2, 1] | — |
| 2 | [1,0,1,0] | [2,1] | [10, 1] | [1, 0] | [0, 1] | 0 | [1] | — | 1 |
因此,最终 answer 为 [2, 1]。
题目来自力扣3624。
🧠 算法过程详解
整个程序的处理流程可以分解为以下几个核心步骤:
-
初始化与深度计算
- 程序首先遍历输入的整数数组
nums,对每个数字计算其“位计数深度”。 - 深度计算 (
getDepth函数):对于一个给定的正整数x,通过循环不断将其替换为其二进制表示中1的个数(即popcount),直到x变为1。这个替换过程的次数就是x的深度。例如,7 (111)→3 (11)→2 (10)→1 (1),深度为3。 - 计算
popcount的方法是不断将数字与1进行按位与操作并右移,统计1的个数。
- 程序首先遍历输入的整数数组
-
数据结构构建
- 程序为每一个不同的深度值
k创建了一棵独立的线段树 (SegTree)。线段树是一种用于高效处理区间查询和点更新的数据结构。 - 初始化时,对于数组
nums中的每个下标i,其数字对应的深度为d,程序就在深度d对应的线段树的位置i上增加1。这样,每棵线段树就记录了当前数组中所有深度等于该树所代表深度的元素的位置分布。
- 程序为每一个不同的深度值
-
处理查询 (
queries)- 程序按顺序处理查询数组中的每个请求。查询有两种类型:
- 类型 1 - 区间查询 (
[1, l, r, k]):查询在区间[l, r]内,深度等于k的元素个数。- 操作:程序找到深度
k对应的线段树(如果存在),然后在这棵线段树上查询区间[l, r]的和。这个和值就是查询结果,被加入到答案数组中。
- 操作:程序找到深度
- 类型 2 - 点更新 (
[2, idx, val]):将nums[idx]的值更新为val。- 操作:
a. 计算新值
val的深度。 b. 获取原值nums[idx]的深度d_old。 c. 如果新深度d_new与原深度不同,则需要进行更新: * 在原深度d_old对应的线段树中,将位置idx的值减1。 * 在新深度d_new对应的线段树中,将位置idx的值加1。如果尚不存在深度为d_new的线段树,则创建一棵。 d. 更新nums数组中idx位置的值。
- 操作:
a. 计算新值
- 类型 1 - 区间查询 (
- 程序按顺序处理查询数组中的每个请求。查询有两种类型:
⏱️ 复杂度分析
假设数组 nums 的长度为 n,查询的总次数为 q。
-
总的时间复杂度:O((n + q) * log n)。
- 原因:
- 深度计算:对于每个数字(初始
n个和更新最多q个),计算深度需要 O(log(max(nums))) 的时间。由于数字的深度最多为 5,这个操作可以看作是常数时间 O(1)。 - 线段树操作:线段树的每次点更新或区间查询操作的时间复杂度为 O(log n)。程序初始化时需要构建
n个点,构建过程可以视为n次插入。在处理q次查询中,每次查询(类型1或类型2)都涉及常数次(1或2次)线段树操作。因此,线段树相关的总操作次数约为 O(n + q),每次操作耗时 O(log n),总时间复杂度为 O((n + q) log n)。
- 深度计算:对于每个数字(初始
- 原因:
-
总的额外空间复杂度:O(n)。
- 原因:
- 存储原始数组
nums和深度数组dep各需要 O(n) 空间。 - 线段树是主要的空间开销。每棵线段树需要 O(n) 的空间。由于深度
k的取值范围很小 (0 ≤ k ≤ 5),最多只会创建常数棵(最多6棵)线段树。因此,所有线段树占用的总空间为 O(6 * n) = O(n)。 - 用于管理这些线段树的映射(
depthTrees)也占用常数空间。
- 存储原始数组
- 原因:
💎 总结
您的解决方案通过结合深度预处理和线段树这一数据结构,巧妙地应对了大规模数据下的区间查询和单点更新需求。线段树确保了查询和更新操作的高效性,而对深度范围的限制(k ≤ 5)则保证了系统整体空间使用的可控性。
Go完整代码如下:
package main
import (
"fmt"
)
// 计算数字的深度
func getDepth(x int64) int {
res := 0
for x > 1 {
res++
// 计算二进制中1的个数(popcount)
count := 0
for x > 0 {
count += int(x & 1)
x >>= 1
}
x = int64(count)
}
return res
}
// 线段树结构
type SegTree struct {
n int
sum []int
}
// 创建线段树
func NewSegTree(n int) *SegTree {
return &SegTree{
n: n,
sum: make([]int, n<<2),
}
}
// 更新线段树
func (st *SegTree) update(p, l, r, idx int, val int) {
if l == r {
st.sum[p] += val
return
}
mid := (l + r) / 2
if idx <= mid {
st.update(p<<1, l, mid, idx, val)
} else {
st.update((p<<1)+1, mid+1, r, idx, val)
}
st.sum[p] = st.sum[p<<1] + st.sum[(p<<1)+1]
}
// 查询线段树
func (st *SegTree) query(p, l, r, ql, qr int) int {
if ql <= l && qr >= r {
return st.sum[p]
}
if ql > r || qr < l {
return 0
}
mid := (l + r) / 2
return st.query(p<<1, l, mid, ql, qr) + st.query((p<<1)+1, mid+1, r, ql, qr)
}
// 更新接口
func (st *SegTree) Update(idx, val int) {
st.update(1, 0, st.n-1, idx, val)
}
// 查询接口
func (st *SegTree) Query(l, r int) int {
return st.query(1, 0, st.n-1, l, r)
}
// 主函数
func popcountDepth(nums []int64, queries [][]int64) []int {
n := len(nums)
// 计算每个数字的深度
dep := make([]int, n)
for i, num := range nums {
dep[i] = getDepth(num)
}
// 为每个深度创建线段树
depthTrees := make(map[int]*SegTree)
for i, d := range dep {
if _, exists := depthTrees[d]; !exists {
depthTrees[d] = NewSegTree(n)
}
depthTrees[d].Update(i, 1)
}
// 处理查询
ans := []int{}
for _, q := range queries {
if q[0] == 1 { // 查询操作
l, r, k := int(q[1]), int(q[2]), int(q[3])
if tree, exists := depthTrees[k]; exists {
ans = append(ans, tree.Query(l, r))
} else {
ans = append(ans, 0)
}
} else if q[0] == 2 { // 更新操作
idx, val := int(q[1]), q[2]
d := getDepth(val)
if d != dep[idx] {
// 从原深度树中移除
if tree, exists := depthTrees[dep[idx]]; exists {
tree.Update(idx, -1)
}
// 添加到新深度树
if _, exists := depthTrees[d]; !exists {
depthTrees[d] = NewSegTree(n)
}
depthTrees[d].Update(idx, 1)
dep[idx] = d
}
}
}
return ans
}
func main() {
nums := []int64{2, 4}
queries := [][]int64{
{1, 0, 1, 1},
{2, 1, 1},
{1, 0, 1, 0},
}
result := popcountDepth(nums, queries)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
def get_depth(x: int) -> int:
"""计算数字的深度"""
res = 0
while x > 1:
res += 1
# 计算二进制中1的个数(popcount)
count = 0
while x > 0:
count += x & 1
x >>= 1
x = count
return res
class SegTree:
"""线段树类"""
def __init__(self, n: int):
self.n = n
self.sum = [0] * (n << 2)
def _update(self, p: int, l: int, r: int, idx: int, val: int) -> None:
"""内部更新函数"""
if l == r:
self.sum[p] += val
return
mid = (l + r) // 2
if idx <= mid:
self._update(p << 1, l, mid, idx, val)
else:
self._update((p << 1) + 1, mid + 1, r, idx, val)
self.sum[p] = self.sum[p << 1] + self.sum[(p << 1) + 1]
def _query(self, p: int, l: int, r: int, ql: int, qr: int) -> int:
"""内部查询函数"""
if ql <= l and qr >= r:
return self.sum[p]
if ql > r or qr < l:
return 0
mid = (l + r) // 2
return (self._query(p << 1, l, mid, ql, qr) +
self._query((p << 1) + 1, mid + 1, r, ql, qr))
def update(self, idx: int, val: int) -> None:
"""更新接口"""
self._update(1, 0, self.n - 1, idx, val)
def query(self, l: int, r: int) -> int:
"""查询接口"""
return self._query(1, 0, self.n - 1, l, r)
def popcount_depth(nums, queries):
"""
主函数:处理查询
Args:
nums: List[int] - 初始数字数组
queries: List[List[int]] - 查询数组
Returns:
List[int] - 查询结果
"""
n = len(nums)
# 计算每个数字的深度
dep = [get_depth(x) for x in nums]
# 为每个深度创建线段树
depth_trees = {}
for i, d in enumerate(dep):
if d not in depth_trees:
depth_trees[d] = SegTree(n)
depth_trees[d].update(i, 1)
# 处理查询
ans = []
for q in queries:
if q[0] == 1: # 查询操作
l, r, k = q[1], q[2], q[3]
if k in depth_trees:
ans.append(depth_trees[k].query(l, r))
else:
ans.append(0)
elif q[0] == 2: # 更新操作
idx, val = q[1], q[2]
d = get_depth(val)
if d != dep[idx]:
# 从原深度树中移除
if dep[idx] in depth_trees:
depth_trees[dep[idx]].update(idx, -1)
# 添加到新深度树
if d not in depth_trees:
depth_trees[d] = SegTree(n)
depth_trees[d].update(idx, 1)
dep[idx] = d
return ans
def main():
"""测试函数"""
nums = [2, 4]
queries = [
[1, 0, 1, 1], # 查询深度为1的元素在[0,1]区间内的数量
[2, 1, 1], # 将索引1的元素更新为1
[1, 0, 1, 0], # 查询深度为0的元素在[0,1]区间内的数量
]
# 调用函数
result = popcount_depth(nums, queries)
# 打印结果
print(f"输入: nums = {nums}")
print(f" queries = {queries}")
print(f"输出: {result}")
# 验证输出
expected = [2, 1]
if result == expected:
print("✓ 输出与预期一致")
else:
print(f"✗ 输出与预期不一致,预期: {expected}")
if __name__ == "__main__":
main()
C++完整代码如下:
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
// 计算数字的深度
int getDepth(long long x) {
int res = 0;
while (x > 1) {
res++;
// 计算二进制中1的个数(popcount)
int count = 0;
while (x > 0) {
count += (x & 1);
x >>= 1;
}
x = count;
}
return res;
}
// 线段树类
class SegTree {
private:
int n;
vector<int> sum;
void update(int p, int l, int r, int idx, int val) {
if (l == r) {
sum[p] += val;
return;
}
int mid = (l + r) / 2;
if (idx <= mid) {
update(p << 1, l, mid, idx, val);
} else {
update((p << 1) + 1, mid + 1, r, idx, val);
}
sum[p] = sum[p << 1] + sum[(p << 1) + 1];
}
int query(int p, int l, int r, int ql, int qr) {
if (ql <= l && qr >= r) {
return sum[p];
}
if (ql > r || qr < l) {
return 0;
}
int mid = (l + r) / 2;
return query(p << 1, l, mid, ql, qr) +
query((p << 1) + 1, mid + 1, r, ql, qr);
}
public:
SegTree(int n) : n(n), sum(n << 2, 0) {}
void Update(int idx, int val) {
update(1, 0, n - 1, idx, val);
}
int Query(int l, int r) {
return query(1, 0, n - 1, l, r);
}
};
// 主函数
vector<int> popcountDepth(vector<long long>& nums, vector<vector<long long>>& queries) {
int n = nums.size();
// 计算每个数字的深度
vector<int> dep(n);
for (int i = 0; i < n; i++) {
dep[i] = getDepth(nums[i]);
}
// 为每个深度创建线段树
unordered_map<int, SegTree*> depthTrees;
for (int i = 0; i < n; i++) {
int d = dep[i];
if (depthTrees.find(d) == depthTrees.end()) {
depthTrees[d] = new SegTree(n);
}
depthTrees[d]->Update(i, 1);
}
// 处理查询
vector<int> ans;
for (auto& q : queries) {
if (q[0] == 1) { // 查询操作
int l = q[1], r = q[2], k = q[3];
if (depthTrees.find(k) != depthTrees.end()) {
ans.push_back(depthTrees[k]->Query(l, r));
} else {
ans.push_back(0);
}
} else if (q[0] == 2) { // 更新操作
int idx = q[1];
long long val = q[2];
int d = getDepth(val);
if (d != dep[idx]) {
// 从原深度树中移除
if (depthTrees.find(dep[idx]) != depthTrees.end()) {
depthTrees[dep[idx]]->Update(idx, -1);
}
// 添加到新深度树
if (depthTrees.find(d) == depthTrees.end()) {
depthTrees[d] = new SegTree(n);
}
depthTrees[d]->Update(idx, 1);
dep[idx] = d;
}
}
}
// 清理动态分配的内存
for (auto& [depth, tree] : depthTrees) {
delete tree;
}
return ans;
}
int main() {
// 测试数据
vector<long long> nums = {2, 4};
vector<vector<long long>> queries = {
{1, 0, 1, 1}, // 查询深度为1的元素在[0,1]区间内的数量
{2, 1, 1}, // 将索引1的元素更新为1
{1, 0, 1, 0}, // 查询深度为0的元素在[0,1]区间内的数量
};
// 调用函数
vector<int> result = popcountDepth(nums, queries);
// 打印结果
cout << "输入: nums = [";
for (int i = 0; i < nums.size(); i++) {
cout << nums[i];
if (i < nums.size() - 1) cout << ", ";
}
cout << "]" << endl;
cout << " queries = [";
for (int i = 0; i < queries.size(); i++) {
cout << "[";
for (int j = 0; j < queries[i].size(); j++) {
cout << queries[i][j];
if (j < queries[i].size() - 1) cout << ", ";
}
cout << "]";
if (i < queries.size() - 1) cout << ", ";
}
cout << "]" << endl;
cout << "输出: [";
for (int i = 0; i < result.size(); i++) {
cout << result[i];
if (i < result.size() - 1) cout << ", ";
}
cout << "]" << endl;
// 验证输出
vector<int> expected = {2, 1};
if (result == expected) {
cout << "✓ 输出与预期一致" << endl;
} else {
cout << "✗ 输出与预期不一致" << endl;
}
return 0;
}