力扣每日打卡 3226. 使两个整数相等的位更改次数 (简单)


前言

这是刷算法题的第五天,用到的语言是JS
题目:力扣 3226. 使两个整数相等的位更改次数 (简单)


一、题目内容

给你两个正整数 n 和 k。
你可以选择 n 的 二进制表示 中任意一个值为 1 的位,并将其改为 0。
返回使得 n 等于 k 所需要的更改次数。如果无法实现,返回 -1。

示例 1:
输入: n = 13, k = 4
输出: 2
解释:
最初,n 和 k 的二进制表示分别为 n = (1101)2 和 k = (0100)2,
我们可以改变 n 的第一位和第四位。结果整数为 n = (0100)2 = k。

示例 2:
输入: n = 21, k = 21
输出: 0
解释:
n 和 k 已经相等,因此不需要更改。

示例 3:
输入: n = 14, k = 13
输出: -1
解释:
无法使 n 等于 k。

提示:

1 <= n, k <= 106

二、解题方法

1.我的初始解法——并非正确答案,是错的(不看)

代码如下(示例):

/**
 * @param {number} n
 * @param {number} k
 * @return {number}
 */
// var minChanges = function(n, k) {
//     // 我的难点:将十进制改为二进制 除积取余法
//     // 思路:先判断两个整数是否相等  →  返回0
//     // 不等:n的二进制数变0后不可能相等  →  返回-1
//     // 我的难点2:怎么判断两个二进制不可能使其相等
//     // 接着就是正常判断次数 +1 
//     let count = 0
//     if(n === k) return 0
// 
//     let nEr = ''
//     let kEr = ''
//     // 定义转二进制函数
//     const change = (num) => {
//         num % 2
//     }
// };

2. 搜索后我的答案

关键:按位与运算的使用!!!

代码如下(示例):

/**
 * @param {number} n
 * @param {number} k
 * @return {number}
 */
var minChanges = function(n, k) {
    // 使用按位与运算符 & 来比较二进制
    // 思路:
    // 如果数字本身相等,返回 -1
    // 如果 k > n,返回 -1,因为小数无法变大
    // 接下来是二进制的比较
    // ① kBit有的nBit必须有,否则返回 -1;eg: 1101  0110  n的第三位0无法变成1,没办法使 n===k
    // ② 因为关于 "1"的特殊情况已经排除掉了,剩下的都是可以比较的"1"

    if( n < k ) return -1
    if( n === k ) return 0
    // 下面此处是初学者难点1 一个坑则为 括号没添加 (n & k)
    if( (n & k) !== k ) return -1
    // 难点2
    const countOne = (num) => {
        let count = 0
        while(num > 0) {
            // 进行按位与运算,计算出 "1"的个数
            num &= num - 1
            count++
        }
        return count
    }

    // 获取 n、k的"1"的个数
    const nOne = countOne(n)
    const kOne = countOne(k)
    return nOne - kOne
};

代码解释
初步判断:首先检查 k 是否大于 n 或者是否等于 n,分别返回 -1 或 0。
子集检查:使用位运算 (n & k) === k 来确保 k 的所有 1 位在 n 的对应位置也为 1。
计算 1 的个数:使用 Brian Kernighan 算法高效地计算二进制中 1 的个数,
              该方法通过不断清除最低位的 1 来计数。
返回结果:计算 n 和 k 中 1 的个数之差,即为所需的操作次数。
该方法确保在最优时间复杂度内解决问题,主要操作的时间复杂度为 O(log n),适用于大数处理。

3.官方题解

3.1 方法一:模拟

从低位到高位枚举 n 和 k 的二进制位,分别记为 bnbk
如果 bn = 0bk = 1,那么说明无法将 n 改成 k,返回 −1。
如果 bn = 1bk = 0,那么需要将对应的 bn 改成 0,并且将更改次数计入总更改次数。
最后返回总更改次数。

代码如下(示例):

var minChanges = function(n, k) {
    let res = 0;
    while (n > 0 || k > 0) {
        if ((n & 1) == 0 && (k & 1) == 1) {
            return -1;
        }
        if ((n & 1) == 1 && (k & 1) == 0) {
            res++;
        }
        n >>= 1;
        k >>= 1;
    }
    return res;
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/number-of-bit-changes-to-make-two-integers-equal/solutions/2969568/shi-liang-ge-zheng-shu-xiang-deng-de-wei-bzfv/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

复杂度分析:
时间复杂度:O(logmax(n,k))
空间复杂度:O(1)

3.2 方法二:位运算

如果 n 将一些值为 1 的二进制位改成 0 得到的整数等于 k,那么从集合的角度来看(值为 1 的二进制位为集合的元素),k 是 n 的子集,用位运算来表示:

                             n & k = k

如果 n & k != k ,那么说明无法实现,直接返回 −1。我们获取存在于 n 中但不存在于 k 的元素集,位运算表示为 n⊕k,然后我们统计 n⊕k 中位为 1 的个数。

大部分语言都自带统计二进制数中 1 的数目的库函数,没有库函数的语言可以参考力扣题解「1342. 将数字变成 0 的操作次数」。

var minChanges = function(n, k) {
    if ((n & k) == k) {
        return bitCount(n ^ k);
    } else {
        return -1;
    }
};

var bitCount = function(x) {
    x = (x & 0x55555555) + ((x >> 1) & 0x55555555);
    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
    x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F);
    x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF);
    x = (x & 0x0000FFFF) + ((x >> 16) & 0x0000FFFF);
    return x;
}

作者:力扣官方题解
链接:https://leetcode.cn/problems/number-of-bit-changes-to-make-two-integers-equal/solutions/2969568/shi-liang-ge-zheng-shu-xiang-deng-de-wei-bzfv/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

复杂度分析:
时间复杂度:O(1)
空间复杂度:O(1)

链接: 力扣本题官方题解
来源:力扣(LeetCode)

三、补充——按位与& 运算

3.1 基本语法

按位与运算符 & 是 JavaScript 中的一种位运算符,它对两个数的二进制表示的每一位进行逻辑“与”操作。以下是详细解释:

3.1.1. 基本规则

定义:对两个数的二进制表示的每一位进行比较,当且仅当两个对应位均为 1 时,结果位为 1,否则为 0。
语法:a & b,其中 a 和 b 可以是任意整数或可转换为整数的值。

示例:

5 & 3;  // 结果:1

二进制计算过程:
Text
5 → 0101
3 → 0011
---------
& → 0001 (即十进制的 1)

3.1.2. 操作数转换规则

JavaScript 中的数字以 64 位双精度浮点数存储,但按位运算会先转换为 32 位有符号整数,运算完成后再转回 64 位:

  • 正数:直接截断到 32 位。
  • 负数:补码表示。
  • 非整数:小数部分直接丢弃(非四舍五入)。

示例:

5.9 & 3.1; // 5 & 3 → 1
-5 & -3;   // 补码运算 → 结果:-7

3.1.3. 常见应用场景

(1) 判断奇偶性
function isEven(n) {
  return (n & 1) === 0;
}
isEven(6); // true
原理:二进制最后一位为 0 则是偶数,为 1 则是奇数。
(2) 权限系统(位掩码)

用不同位表示不同权限,通过 & 检查是否拥有某权限:

const READ = 1;    // 0001
const WRITE = 2;   // 0010
const EXECUTE = 4; // 0100

let userPermissions = READ | WRITE; // 0011

// 检查是否有写权限
if (userPermissions & WRITE) { // 0011 & 0010 → 0010 ≠ 0
  console.log("可写");
}
(3) 快速取整
Math.floor(3.7) === (3.7 & -1); // true
注意:仅适用于 32 位范围内的整数(即 -2^31 到 2^31-1)。
(4) 消除二进制最后一个 1 → 即本题解法之一
let x = 12; // 1100
x = x & (x - 1); // 1100 & 1011 → 1000
应用:统计二进制中 1 的个数(见下方代码 || 本题力扣我的解法)。

3.1.4. 实用代码示例

(1) 统计二进制中 1 的个数
function countOnes(x) {
  let count = 0;
  while (x !== 0) {
    x &= x - 1; // 消除最后一个 1
    count++;
  }
  return count;
}
countOnes(7); // 3(二进制 111)
(2) 检查是否为 2 的幂
function isPowerOfTwo(n) {
  return n > 0 && (n & (n - 1)) === 0;
}
isPowerOfTwo(8); // true
原理:2 的幂的二进制只有最高位是 1,n & (n - 1) 会消除最后一个 1,结果为 0。

3.1.5. 注意事项

(1) 大数截断:

超出 32 位的数会被截断,例如:

(Math.pow(2, 32) + 1) & 1; // 0(原数为 4294967297 → 截断为 1)
(2) 运算符优先级:

& 优先级低于比较运算符(如 ==、>),建议用括号明确逻辑:

(a & b) === c; // 正确写法
a & b === c;    // 错误!等价于 a & (b === c)
(3) 非整数处理:

小数会被直接截断,例如:

3.9 & 5.1; // 3 & 5 → 1

3.1.6 本题的 x &= x - 1 具体解释及例子

x &= x - 1是一个位运算技巧,用于将二进制数 x 最右边的 1 变成 0。
这个操作在算法中常用于快速统计二进制中 1 的个数

具体原理:
二进制减 1 的规则:
当 x 是二进制数时,x - 1 会将 x 最右边的 1 变成 0,并且将该位右侧所有的 0 变成 1。

例如:
x = 12(二进制 1100),则 x - 1 = 11(二进制 1011)。

按位与操作 & 的作用:
x & (x - 1) 会将 x 最右边的 1 变成 0,同时保留其他位的值。

例如:
x = 12(1100),x & (x - 1) = 1100 & 1011 = 1000(结果为 8)。

示例分析
假设 x = 14(二进制 1110):

第一次操作:
x = 14(1110)
x - 1 = 13(1101)
x &= x - 1 → x = 1110 & 1101 = 1100(即 12)
 此时 x 的二进制从 1110 变成了 1100,最右边的 1 被消除。
 
 
第二次操作:
x = 12(1100)
x - 1 = 11(1011)
x &= x - 1 → x = 1100 & 1011 = 1000(即 8) 最右边的 1 再次被消除。


第三次操作:
x = 8(1000)
x - 1 = 7(0111)
x &= x - 1 → x = 1000 & 0111 = 0000(即 0) 所有 1 都被消除,循环结束。

为什么要用这个技巧?

高效统计 1 的个数:
每次操作消除一个 1,循环次数等于二进制中 1 的个数。

例如:
x = 14(1110,3 个 1)需要 3 次操作。 时间复杂度为 O(k)(k 是 1 的个数),比逐位检查的 O(32) 更高效。

应用场景:
常用于需要快速操作二进制位的场景,如统计 1 的个数、判断是否是 2 的幂等。

总结
x &= x - 1 的作用是消除二进制数 x 最右边的 1。通过不断执行这个操作,直到 x 变为 0,可以快速统计出二进制中 1 的个数。

3.1.7. 总结

按位与 & 是一种高效的二进制操作工具,适用于权限控制、数值优化、位掩码等场景。使用时需注意其转换规则和运算符优先级,避免因截断或逻辑错误导致意外结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值