2025-12-31:位计数深度为 K 的整数数目Ⅱ。用go语言,给定一个整数数组 nums。对任意正整数 x,按下面方式构造序列: - p0 = x; - 对

13 阅读10分钟

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]。

解释:

iqueries[i]numsbinary(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。

🧠 算法过程详解

整个程序的处理流程可以分解为以下几个核心步骤:

  1. 初始化与深度计算

    • 程序首先遍历输入的整数数组 nums,对每个数字计算其“位计数深度”。
    • 深度计算 (getDepth 函数):对于一个给定的正整数 x,通过循环不断将其替换为其二进制表示中 1 的个数(即 popcount),直到 x 变为 1。这个替换过程的次数就是 x 的深度。例如,7 (111)3 (11)2 (10)1 (1),深度为 3
    • 计算 popcount 的方法是不断将数字与 1 进行按位与操作并右移,统计 1 的个数。
  2. 数据结构构建

    • 程序为每一个不同的深度值 k 创建了一棵独立的线段树 (SegTree)。线段树是一种用于高效处理区间查询和点更新的数据结构。
    • 初始化时,对于数组 nums 中的每个下标 i,其数字对应的深度为 d,程序就在深度 d 对应的线段树的位置 i 上增加 1。这样,每棵线段树就记录了当前数组中所有深度等于该树所代表深度的元素的位置分布。
  3. 处理查询 (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 位置的值。

⏱️ 复杂度分析

假设数组 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;
}

在这里插入图片描述