给你一个下标从 0 开始的整数数组 nums 和一个整数 k

请你用整数形式返回 nums 中的特定元素之 ,这些特定元素满足:其对应下标的二进制表示中恰存在 k 个置位。

整数的二进制表示中的 1 就是这个整数的 置位

例如,21 的二进制表示为 10101 ,其中有 3 个置位。

示例 1:

输入:nums = [5,10,1,5,2], k = 1
输出:13
解释:下标的二进制表示是: 
0 = 0002
1 = 0012
2 = 0102
3 = 0112
4 = 1002 
下标 1、2 和 4 在其二进制表示中都存在 k = 1 个置位。
因此,答案为 nums[1] + nums[2] + nums[4] = 13 。

示例 2:

输入:nums = [4,3,2,1], k = 2
输出:1
解释:下标的二进制表示是: 
0 = 002
1 = 012
2 = 102
3 = 112
只有下标 3 的二进制表示中存在 k = 2 个置位。
因此,答案为 nums[3] = 1 。

 提示:

  • 1 <= nums.length <= 1000

  • 1 <= nums[i] <= 105

  • 0 <= k <= 10

思路

  • 常规遍历数组下标判断

  • 位操作起飞

代码

  • 常规遍历数组下标判断

class Solution {
    public int sumIndicesWithKSetBits(List<Integer> nums, int k) {
        int result = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (Integer.bitCount(i) == k) {
                result += nums.get(i);
            }
        }
        return result;
    }
}
  • 位操作起飞

class Solution {
    public int sumIndicesWithKSetBits(List<Integer> nums, int k) {
        if (k == 0) {
            return nums.getFirst();
        }
        int n = nums.size();
        int answer = 0;
        int i = (1 << k) - 1;
        while (i < n) {
            answer += nums.get(i);
            i = next(i);
        }
        return answer;
    }

    // 获取二进制中 1 的数量和 num 相同且大于 num 的最小数字
    private int next(int num) {
        int x = num & -num;
        int t = num + x;
        return t ^ ((num ^ t) / x >> 2);
    }
}

方法步骤详解

核心

private int next(int num) {
    int x = num & -num;        // 步骤1:找到最右侧的1位
    int t = num + x;           // 步骤2:翻转最右侧连续的1位
    return t ^ ((num ^ t) / x >> 2); // 步骤3:调整剩余位
}

这个方法实现了一个巧妙的位操作算法,用于找到给定整数 num 的下一个具有相同数量1位的更大数字。这种操作在组合数学和位操作中很有用。

步骤1:找到最右侧的1位

int x = num & -num;
  • -numnum 的二进制补码表示

  • num & -num 会保留 num 中最右边的1,其他位都置为0

  • 例如:num = 01101000 (二进制),则 x = 00001000

步骤2:翻转最右侧连续的1位

int t = num + x;
  • num 加上 x 会翻转从最右边开始的连续的1位

  • 例如:num = 01101000x = 00001000t = 01101000 + 00001000 = 01110000

步骤3:调整剩余位

return t ^ ((num ^ t) / x >> 2);

这部分最复杂,分解来看:

  1. num ^ t:计算 numt 不同的位

    • 对于上面的例子:01101000 ^ 01110000 = 00011000

  2. /x:将结果右移 log2(x) 位(因为除以 x 相当于右移 x 的1的位置)

    • 00011000 / 00001000 = 00000011 (即右移3位)

  3. >> 2:再右移2位

    • 00000011 >> 2 = 00000000

  4. t ^ ...:最后与 t 进行异或

    • 01110000 ^ 00000000 = 01110000

完整示例

num = 0b01101000 (104) 为例:

  1. x = num & -num = 01101000 & 10011000 = 00001000 (8)

  2. t = num + x = 01101000 + 00001000 = 01110000 (112)

  3. num ^ t = 01101000 ^ 01110000 = 00011000 (24)

  4. (num ^ t)/x = 00011000 / 00001000 = 00000011 (3)

  5. >> 2 = 00000000 (0)

  6. t ^ 0 = 01110000 (112)

所以 next(104) 返回 112,它们的二进制表示分别是 0110100001110000,都有3个1位。