2859. 计算 K 置位下标对应元素的和
给你一个下标从 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;
-num
是num
的二进制补码表示num & -num
会保留num
中最右边的1,其他位都置为0例如:
num = 01101000
(二进制),则x = 00001000
步骤2:翻转最右侧连续的1位
int t = num + x;
将
num
加上x
会翻转从最右边开始的连续的1位例如:
num = 01101000
,x = 00001000
,t = 01101000 + 00001000 = 01110000
步骤3:调整剩余位
return t ^ ((num ^ t) / x >> 2);
这部分最复杂,分解来看:
num ^ t
:计算num
和t
不同的位对于上面的例子:
01101000 ^ 01110000 = 00011000
/x
:将结果右移log2(x)
位(因为除以x
相当于右移x
的1的位置)00011000 / 00001000 = 00000011
(即右移3位)
>> 2
:再右移2位00000011 >> 2 = 00000000
t ^ ...
:最后与t
进行异或01110000 ^ 00000000 = 01110000
完整示例
以 num = 0b01101000
(104) 为例:
x = num & -num = 01101000 & 10011000 = 00001000
(8)t = num + x = 01101000 + 00001000 = 01110000
(112)num ^ t = 01101000 ^ 01110000 = 00011000
(24)(num ^ t)/x = 00011000 / 00001000 = 00000011
(3)>> 2
=00000000
(0)t ^ 0 = 01110000
(112)
所以 next(104)
返回 112
,它们的二进制表示分别是 01101000
和 01110000
,都有3个1位。