题目难度: 中等

原题链接

今天继续更新程序员面试金典系列, 大家在公众号 算法精选 里回复 面试金典 就能看到该系列当前连载的所有文章了, 记得关注哦~

题目描述

下一个数。给定一个正整数,找出与其二进制表达式中 1 的个数相同且大小最接近的那两个数(一个略大,一个略小)。

示例 1:

  • 输入:num = 2(或者 0b10)
  • 输出:[4, 1] 或者([0b100, 0b1])

示例 2:

  • 输入:num = 1
  • 输出:[2, -1]

提示:

  • num 的范围在[1, 2147483647]之间;
  • 如果找不到前一个或者后一个满足条件的正数,那么输出 -1。

题目思考

  1. 与原数最接近且 1 的个数相同的数有什么特点和规律?

解决方案

思路

  • 根据题目描述, 很自然想到可以分别求略大和略小的数
  • 求略大数:
    • 例如 11001100 => 11010001
    • 直观想法是, 我们可以从右向左遍历, 把最右边的 1 (从右数第 3 个数)填充到最接近且在其左边的 0 (从右数第 5 个数)那里, 然后遍历过程中遇到的 1 都给放到新的数的最低位上, 正如上面的转换那样
    • 这样保证了新的数与原数字的 1 的个数相同, 且是大于原数字的最接近的数, 这是因为两者之间的数字都会将右侧的某些 0 变成 1, 不满足 1 个数相同这一条件
    • 具体代码实现参考下面的 findLarger 函数, 每一步都有详细注释
  • 求略小数:
    • 例如 110011 => 101110
    • 直观想法是, 我们可以从右向左遍历, 把右边存在 0 的最右边的 1 (从右数第 5 个数)与它右边的 0 (从右数第 4 个数)交换位置, 然后遍历过程中遇到的 1 (最右边的两个 1)都给放到新的 1 的右边, 正如上面的转换那样
    • 这样保证了新的数与原数字的 1 的个数相同, 且是小于原数字的最接近的数, 这是因为两者之间的数字都会将右侧的某些 1 变成 0, 不满足 1 个数相同这一条件
    • 具体代码实现参考下面的 findSmaller 函数, 每一步都有详细注释

复杂度

  • 时间复杂度 O(logN): 只需遍历两次 num 的每一位
  • 空间复杂度 O(1): 只使用了几个变量

代码

Python 3
class Solution:
    def findClosedNumbers(self, num: int) -> List[int]:
        # 找规律+位运算
        mn, mx = 1, 2147483647

        def findLarger(n):
            # 从最低位开始找第1个1
            # 然后继续向高位找, 记录1的个数ones, 直到再遇到0或到最高位
            #   1. 如果达到最高位, 则说明找不到更大的数, 返回-1
            #   2. 否则将遇到的0变成1, 然后之前遍历过的低位用000...111(ones-1个1, 因为改变的位已经将1个0转成1了, 所以这里ones要减1)代替
            # 这样保证了得到的数与num的1的个数相同, 且是大于当前num的最接近的数
            rightBits = 0  # 遍历的低位位数, 用于后面的左移恢复
            # 从低位开始遍历, 直到找到第一个1
            while n and n & 1 == 0:
                n >>= 1
                rightBits += 1
            ones = 0  # 遍历的1的个数
            # 继续遍历, 直到再次遇到0
            while n and n & 1:
                n >>= 1
                rightBits += 1
                ones += 1
            # 将当前最低位的0变成1
            n += 1
            # 将右边部分清0, 并填充上000...111 (ones-1个1)
            n = (n << rightBits) | ((1 << (ones - 1)) - 1)
            # 如果得到的数字在32位正数范围内, 返回自身, 否则返回-1, 表示超出上限
            return n if mn <= n <= mx else -1

        def findSmaller(n):
            # 从最低位开始找第1个0, 并记录此过程1的个数ones (如果没找到0, 则不可能存在更小的与num的1的个数相同的数, 直接返回-1)
            # 然后继续向高位找, 直到再遇到1
            # 将这个1变成0, 然后之前遍历过的低位用111...000(ones+1个1, 因为改变的位已经将1个1转成0了, 所以这里ones要加1)代替
            # 这样保证了得到的数与num的1的个数相同, 且是小于当前num的最接近的数
            rightBits = 0  # 遍历的低位位数, 用于后面的左移恢复
            ones = 0
            # 从低位开始遍历, 直到找到第一个0
            while n and n & 1:
                n >>= 1
                rightBits += 1
                ones += 1
            if n == 0:
                # 说明没找到0, 不可能存在更小的与num的1的个数相同的数, 直接返回-1
                return -1
            # 继续遍历, 直到再次遇到1
            while n and n & 1 == 0:
                n >>= 1
                rightBits += 1
            # 将当前最低位的1变成0
            n -= 1
            # 将右边部分清0, 并填充上111...000 (ones+1个1)
            # 111...000可以通过先得到111, 然后左移剩余位数(即rightBits - ones - 1)得到
            n = (n << rightBits) | (((1 << (ones + 1)) - 1) <<
                                    (rightBits - ones - 1))
            # 如果得到的数字在32位正数范围内, 返回自身, 否则返回-1, 表示超出上限
            return n if mn <= n <= mx else -1

        return [findLarger(num), findSmaller(num)]

大家可以在下面这些地方找到我~😊

我的 GitHub

我的 Leetcode

我的 CSDN

我的知乎专栏

我的头条号

我的牛客网博客

我的公众号: 算法精选, 欢迎大家扫码关注~😊

算法精选 - 微信扫一扫关注我