首页 > 其他分享 >0137_Single-Number-II【M】

0137_Single-Number-II【M】

时间:2024-07-03 13:56:52浏览次数:10  
标签:0137 jy nums 二进制位 two 数值 II Single num

1、利用数值二进制位的特性(执行效率并不高)

  • 题目中明确了数字的范围是 32 位整型(-2^31 <= nums[i] <= 2^31 - 1),所以从低位遍历到高位,将所有数字的二进制对应位相加,由于除了一个数字外其余数字都出现了 3 次,故所有数字的同一个二进制位之和与 3 求余所得数就是目标数字对应所在位的二进制。
  • 时间复杂度O(n * log⁡C)
    • n是数组的长度,C是元素的数据范围
    • 本题中log C = log 2^32 = 32(需遍历第 0 - 31 个二进制位)
  • 空间复杂度O(1)
from typing import List


class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        result = 0

        # jy: 记录目标数值的二进制表示形式
        bin_num = ""

        # jy: 数值总共有 32 个二进制位 (包含最高位的符号位)
        for i in range(32):
            # jy: 统计所有数值的第 i 位上的二进制数之和
            sum_ = 0
            for num in nums:
                # jy: 右移 i 位后 (最开始 i 为 0) 并和 1 进行与运算即可获
                #     取 num 的二进制形式的第 i 位 (最低位为第 0 位) 的数值
                # jy: 注意: 负数的最高位数值位为 1
                sum_ += ((num >> i) & 1)

            # jy: 构造目标数值的二进制表示形式
            bin_num = str(sum_ % 3) + bin_num
            
            # jy: 如果所有数值的第 i 位的数值之和与 3 求余数为 1, 表明目标
            #     数值的第 i 位为 1, 此时将 1 左移 i 位后 (即 2 ** i) 加上
            #     上一轮 result 结果 (即之前的二进制位对应的数值结果)
            if sum_ % 3 == 1:
                # jy: 最高位符号位 (i == 31) 为 1 时, 需进行特殊处理: 因为计算机
                #     中数值是以补码的形式存储, 因此不能直接 ``result = -result``,
                #     以 -4 为例:
                #     原码: 10000000000000000000000000000100
                #     反码: 11111111111111111111111111111011
                #     补码: 11111111111111111111111111111100
                #     此时 ``result - 2 ** 31`` 才是目标数值 -4
                if i == 31:
                    # jy: 1 << 31 表示 1 左移 31 位 (即 2 ** 31)
                    result -= (1 << 31)
                else:
                    # jy: 以下两个语句等价 (注意: 只有当 result 对应的数值
                    #     与 1 << i 对应的数值的二进制形式没有重叠的值为 1
                    #     的位时才能采用下面的第一种写法)
                    result |= 1 << i
                    #result += 1 << i

        # jy: 查看目标数值的二进制表示形式; 可发现, 数值的二进制形式是以补码的形式在计算机中体现;
        #     (正数的补码为其原码本身, 负数的补码为: 除符号位外的原码的反码 + 1); 如 -4:
        #     原码: 10000000000000000000000000000100
        #     反码: 11111111111111111111111111111011
        #     补码: 11111111111111111111111111111100
        print(bin_num)
        
        return result


nums = [-4]
res = Solution().singleNumber(nums)
# jy: -4
print(res)

2、改写解法 1

  • 用一个长度为 32 的列表记录目标数值的每一个二进制位
from typing import List


class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        ls_bit = [0] * 32
        # jy: 将所有数值的每一个二进制位的数值均相加求和
        for num in nums:
            for j in range(32):
                ls_bit[j] += num & 1
                num >>= 1

        result, m = 0, 3
        # jy: 从最高位 (第 31 位) 开始, 不断补齐目标数值的二进制位
        # jy: 如果目标值为 -4, 则:
        #     1) 以下 for 循环结束后得到的 result 为:
        #        11111111 11111111 11111111 11111100
        #     2) 与 0xffffffff 进行异或运算后结果为 (即为 3):
        #        00000000 00000000 00000000 00000011
        #     3) ~3 的结果 (即补码的形式) 即为 -4  
        for i in range(32):
            result <<= 1
            # jy: ls_bit[31 - i] % m 得到的结果即目标数值在第 31-i
            #     位的二进制结果值 (二进制的最右侧为第 0 位)
            result |= ls_bit[31 - i] % m

        return result if ls_bit[31] % m == 0 else ~(result ^ 0xffffffff)


nums = [2, 2, 3, 2]
res = Solution().singleNumber_v2(nums)
# jy: 3
print(res)

3、改写解法 1

from typing import List


class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        result = 0
        for i in range(32):
            sum_ = sum((num >> i) & 1 for num in nums)
            if sum_ % 3:
                if i == 31:
                    result -= (1 << i)
                else:
                    #result += 1 << i 
                    result |= (1 << i)
        return result



nums = [0, 1, 0, 1, 0, 1, 99]
res = Solution().singleNumber(nums)
# jy: 99
print(res)

4、有限状态自动机(性能优)

  • 时间复杂度O(n)
    • 遍历数组占用O(n)
    • 每轮中的常数个位运算操作占用O(32 * 3 * 2) = O(1)
  • 空间复杂度O(1)
  • 各二进制位的位运算规则相同 ,因此只需考虑一位即可。
  • 所有数值的指定二进制位中 1 的个数除 3 求余数有 3 种结果:0、1、2(尽管其它数都出现 3 次,但遍历时是逐个遍历,当第二次遍历相同数值时,就可能存在余数为 2 的情况)。对应的状态转换规则如下:
    • 余数的三种结果(对应三种状态):0、1、2
    • 若输入位为 0,则状态保持不变
    • 若输入位为 1,则转至下一个状态

  • 由于一个二进制位只能表示 0、1 两种状态,因此需使用两个二进制位来表示 3 个状态(用 0、1、2 对应的二进制形式即可),因此状态转换变为:

00 → 01 → 10 → 00 → ⋯

  • 记两位分别为two one(状态的两个二进制位)
  • 接下来需通过状态转换表推导出状态转换的计算公式。
  • 位运算回顾(以下的x为二进制数值 0 或 1):
    • 异或运算:x ^ 0 = xx ^ 1 = ~x
    • 与运算: x & 0 = 0x & 1 = x
  • 状态转换表(设当前状态为two one,输入二进制位n):

(1)计算one的方法(状态转换计算公式)

  • 以下是结合当前n,在原two的基础上计算one

  • 难点:需逐步理解以上不同情况下one的计算(异或运算、与运算),以及两者的整合过程;可参考以下代码逻辑进行理解(one的计算方法):
# jy: 最朴素的拆分计算方法:
if two == 0:
  if n == 0:
    one = one
  if n == 1:
    one = ~one
if two == 1:
    one = 0

# jy: 使用异或运算将以上拆分简化:
if two == 0:
    one = one ^ n
if two == 1:
    one = 0

# jy: 使用与运算可继续简化为:
one = one ^ n & ~two

(2)计算two的方法

  • 由于先计算one,因此应在新one的基础上计算two
  • 因此,在新one的基础上,结合当前n,计算two(根据状态转换表进行推导计算)

    • 当新one为 0 时:
      • n == 0two = two
      • n == 1two = ~two
    • 当新one为 1 时:two = 0
    • 因此,可以用相同的公式计算twotwo = two ^ n & ~one
  • 修改为新one后,得到了新的状态图【以下新状态图的转换比较抽象,参考以上理解过程进行理解即可】:

  • 因此可使用同样的方法计算two
two = two ^ n & ~one
  • 返回值:
    • 以上是对数值二进制位中的 1 位的分析,而 int 类型的 32 位具有相同的运算规则,因此可将以上公式直接套用在 32 位上。
    • 遍历完所有数字后,各二进制位都处于状态00和状态01(取决于 “只出现一次的数字” 的各二进制位是 1 还是 0 ),而此两状态是由one来记录的(此两状态下twos恒为 0 ),因此返回ones即可。

此处为语雀图册卡片,点击链接查看:https://www.yuque.com/it-coach/leetcode/xkogct#sSdmd

from typing import List


class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        # jy: ones 和 twos 分别表示 32 个状态位的对应的标签
        ones, twos = 0, 0
        
        # jy: 对每个数值 num 的 32 个二进制位同时进行操作
        for num in nums:
            # jy: 以下两行代码的优先顺序可互换, 优先算哪个, 则返回哪个 (如果调整为优先
            #     计算 twos, 则最终返回结果也应该为 twos; 可以理解为互换状态位的含义)
            ones = ones ^ num & ~twos
            twos = twos ^ num & ~ones

        # jy: 输入数值列表的特性会使得最终结果 twos 恒为 0
        print(twos)
        
        return ones


nums = [-2, -2, 1, 1, 4, 1, 4, 4, -4, -2]
res = Solution().singleNumber(nums)
# jy: -4
print(res)

5、改写解法 1(用二进制字符串进行中间过程处理)

from typing import List


class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        str_ = ""
        for i in range(32):
            sum_ = 0
            for num in nums:
                # jy: 右移 i 位后 (最开始 i 为 0) 并和 1 进行与运算即可获取
                #     num 的二进制形式的第 i 位的数值
                sum_ += ((num >> i) & 1)

            # jy: 将二进制位不断往左侧拼接
            if sum_ % 3 == 1:
                str_ = "1" + str_
            else:
                str_ = "0" + str_

        # jy: 如果二进制结果最高位是 1, 则为负数 (对应的二进制为补码形式),
        #     将 str_ 的二进制表示取反加 1 后即为该负数的绝对值 (即为该负
        #     数去掉最高位的符号位后的原码表示)
        if str_[0] == "1":
            tmp_str = ""
            for i in str_:
                if i == "0":
                    tmp_str += "1"
                else:
                    tmp_str += "0"
            # jy: 二进制取反加 1 对应的数值等于先求得二进制对应的数值后再加 1
            return - (int(tmp_str, 2) + 1)
        else:
            return int(str_, 2)


nums = [-2, -2, 1, 1, 4, 1, 4, 4, -4, -2]
res = Solution().singleNumber(nums)
# jy: -4
print(res)

6、哈希表(不满足题意要求)

  • 时间复杂度 O(n),n 是数组的长度;空间复杂度 O(n)
  • 哈希映射中包含最多floor(n/3) + 1个元素,即需要的空间为 O(n)
  • 使用哈希映射统计数组中每个元素的出现次数,对于哈希映射中的每个键值对,键表示一个元素,值表示其出现的次数;统计完成后,遍历哈希映射即可找出只出现一次的元素
from typing import List


class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        import collections 
        freq = collections.Counter(nums)
        ans = [num for num, occ in freq.items() if occ == 1][0]
        return ans


nums = [-2, -2, 1, 1, 4, 1, 4, 4, -4, -2]
res = Solution().singleNumber(nums)
# jy: -4
print(res)

标签:0137,jy,nums,二进制位,two,数值,II,Single,num
From: https://blog.csdn.net/m0_66491750/article/details/140147533

相关文章

  • 【STM32F1例程10】UCOSII系统实验
      那么这个实验,从项目的工程结构来看,其实稍微稍微有一丢丢,有一丢丢比之前几个实验复杂,但是还是老话,既然能读到这篇文章,证明能力还是得到认可的。实验简介  那么在STM32上进行uC/OS-II系统实验是一种常见的实践,可以帮助大家了解和应用实时操作系统(RTOS)在嵌入式系统开......
  • 两数之和 II - 输入有序数组-双指针
    题目描述:个人题解:        初始时两个指针分别指向第一个元素位置和最后一个元素的位置。每次计算两个指针指向的两个元素之和,并和目标值比较。如果两个元素之和等于目标值,则发现了唯一解。如果两个元素之和小于目标值,则将左侧指针右移一位。如果两个元素之和大于目......
  • 第二十六天 第七章 回溯算法 part04 491.递增子序列 46.全排列 47.全排列 II
    491.递增子序列将其看作一个二叉树,可以知道,在二叉树每层中,不能取相同的元素。这题最主要要理解这个点。使用unordered_set对其进行降重。classSolution{public:vector<vector<int>>res;vector<int>cur;voidbacktracking(vector<int>&nums,intindex){......
  • 复旦大学2023--2024学年第二学期高等代数II期末考试情况分析
    一、期末考试成绩班级前几名的同学施想(95)、侯煜天(94)、刘升(92)、洪临依(92)、王龙晨(92)、文俊(90)、徐亦闵(89)、邓海斌(89)、褚乐一(89)二、总评成绩计算方法作业成绩根据交作业的次数决定。本学期提交作业共13次,10次100分,少1次扣10分。总评成绩=作业成绩*15%+期中成绩*......
  • UCOS-III 系统配置
    1.µC/OS-III功能配置(os_cfg.h)os_cfg.h用于确定应用程序所需的µC/OS-III功能,详细如下: 1.1杂项OS_CFG_APP_HOOKS_EN:启用/禁用应用程序特定的钩子。OS_CFG_ARG_CHK_EN:启用/禁用参数检查。OS_CFG_CALLED_FROM_ISR_CHK_EN:启用/禁用中断服务程序(ISR)检查。OS_CFG_DB......
  • 复旦大学2023--2024学年第二学期(23级)高等代数II期末考试第七大题解答
    七、(10分) 设$V$是$n$阶实矩阵全体构成的实线性空间, $A$是$n$阶正定实对称阵.对任意的$X,Y\inV$,定义二元函数$(X,Y)=\mathrm{tr}(XAY')$.(1)求证:$(-,-)$是$V$上的一个内积.(2)在上述内积下,$V$成为一个欧氏空间. 设$P,Q\inV$,$V$上的线性算子$......
  • (nice!!!)LeetCode 3164. 优质数对的总数 II(数组、哈希表)
    3164.优质数对的总数II思路:先找出可以被k整除的nums[i].方法一:统计因子。1、找出数组nums1每个元素的因子,用哈希表来记录每个因子出现的次数。然后再遍历数组nums2进行累加即可。classSolution{public:constintN=1e6+10;longlongnumberOfPairs(vec......
  • 代码随想录算法训练营第四十三天 | 52.携带研究材料 518.零钱总和II 377.组合总和IV 7
    完全背包有N件物品和一个最多能被重量为W的背包,第i间物品的重量为weights[i],价值为value[i],每件物品都有无限个,求解将哪些物品装入背包里,物品价值总和最大遍历顺序:纯完全背包问题(即求装满背包后的最大价值)先遍历背包先遍历物品都是可以的和零一背包求解的最大不同就是遍历顺序......
  • 修复《Call of Duty: Black Ops III(使命召唤3)》DLL损坏问题:确保游戏体验顺畅的详尽方
    《CallofDuty:BlackOpsIII》(使命召唤:黑色行动3)是一款由Treyarch开发、动视发行的未来战争题材第一人称射击游戏,设定在2065年的近未来,玩家扮演高科技装备的超级士兵,参与紧张激烈的单人战役与多人对战,还包括标志性的丧尸模式。如果你遇到《CallofDuty:BlackOpsIII》......
  • 代码随想录算法训练营第四十二天 | 1049最后一块石头的重量II 494.目标和 474.一和零
    1049.最后一块石头的重量题目链接文章讲解视频讲解解题思路:  将石头尽量分为相等的两堆,两堆最差即为所求结果  石头的重量就是石头的价值动规五部曲:dp[j]:表示背包容量为j时可以装的石头的总价值递推公式:dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]初始化:均......