利用位操作进行特殊数组的查找-leetcode

只出现一次的数字

原题:136. 只出现一次的数字

题目描述

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

分析

利用两个相同正数异或抵消,以及异或具有交换律的性质,数组里所有数字全部异或,单一的数字便会被剩下。时间复杂度为O(n),空间复杂度为O(1)

代码

class Solution(object):
    def singleNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        res = 0
        for i in nums:
            res ^= i
        return res

只出现一次的数字 II \star\star

原题:137. 只出现一次的数字II

题目描述

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。

说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

分析及代码

(算法借鉴于:)

  • 二进制下不考虑进位的加法:本题为136的拓展,136 题中我们用到了异或运算。实际上,异或运算的含义二进制下不考虑进位的加法,即:0 xor 0=0+0=0, 0 xor 1=0+1=1, 1 xor 0=1+0=1, 1 xor 1=1+1=0(不进位)
  • 三进制下不考虑进位的加法:通过定义某种运算 #,使得 0 # 1 = 11 # 1 = 22 # 1 = 0。在此运算规则下,出现了 3 次的数字的二进制所有位全部抵消为 0,而留下只出现 1 次的数字二进制对应位为 1。因此,在此运算规则下将整个 arr 中数字遍历加和,留下来的结果则为只出现 1 次的数字。
  • 代码分析:
    • ones ^= num:记录至目前元素num,二进制某位出现 1 次(当某位出现 3 次时有 ones=1 ,与 twos = 1 共同表示“出现 3 次”);
    • twos |= ones & num:记录至目前元素num,二进制某位出现 2 次 (当某位出现 2 次时,twos=1ones=0 );
    • threes = ones & twos:记录至目前元素num,二进制某位出现 3 次(即当 onestwos 对应位同时为 1 时 three=1 )。
    • one &= ~threes, two &= ~threes:将 ones, twos 中出现了 3 次的对应位清零,实现 “不考虑进位的三进制加法” 。
class Solution:
    def singleNumber(self, nums: [int]) -> int:
        ones, twos, threes = 0, 0, 0
        for num in nums:
            twos |= ones & num # 二进制某位出现1次时twos = 0,出现2, 3次时twos = 1;
            ones ^= num  # 二进制某位出现2次时ones = 0,出现1, 3次时ones = 1;
            threes = ones & twos # 二进制某位出现3次时(即twos = ones = 1时)three = 1,其余即出现1, 2次时three = 0;
            ones &= ~threes # 将二进制下出现3次的位置零,实现`三进制下不考虑进位的加法`;
            twos &= ~threes
        return ones
  • 进一步简化:以上过程本质上是通过构建 3 个变量的状态转换表来表示对应位的出现次数:使所有数字“相加”后出现 3N+1 次的位 ones = 1,出现 3N3N+2次的位为 ones=0。由于 three 其实是 ones & twos 的结果,因此我们可以舍弃 threes,仅使用 onestwos 来记录出现次数。
某位出现 1次 2次 3次 4次 5次 6次
ones 1 0 0 1 0 0
twos 0 1 0 0 1 0
threes 0 0 1 0 0 1
  • 代码分析:
    • ones = ones ^ num & ~twos:
      • 当 num = 1 时,只当 ones=twos=0 时将 ones 置 1,代表出现 3N+1 次;其余置 0,根据 twos 值分别代表出现 3N 次和 3N+2 次;
      • 当 num=0 时,ones 不变;
    • twos = twos ^ num & ~ones:
      • 当 num = 1 时,只当 ones = twos = 0 时将twos 置 1,代表出现 3N+2 次;其余置 0,根据 ones 值分别代表出现 3N 次和 3N+1 次。
      • 当 num=0 时,twos 不变。
class Solution:
    def singleNumber(self, nums: [int]) -> int:
        ones, twos = 0, 0 #出现一次的位,和两次的位
        for num in nums:
            ones = ones ^ num & ~twos #既不在出现一次的ones,也不在出现两次的twos里面,我们就记录下来,出现了一次,再次出现则会抵消
            twos = twos ^ num & ~ones #既不在出现两次的twos里面,也不再出现一次的ones里面(不止一次了),记录出现两次,第三次则会抵消
        return ones

只出现一次的数字 III

原题:260. 只出现一次的数字 III

题目描述

给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。

示例 : 输入: [1,2,1,3,2,5] 输出: [3,5]

注意:
结果输出的顺序并不重要,对于上面的例子, [5, 3] 也是正确答案。
你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?

分析

我们可以利用双层循环以O( n 2 n^2 )的时间复杂度,O(1)的空间复杂度来解决,但是不符合要求;当然我们也可以利用Hash字典数据结构在O(n)的时间复杂度,O(n)的空间复杂度来解决,但是依然不符合要求;

于是我们根据163题中异或的思想来解决:

  1. 全部元素异或消掉出现两次的数字. 异或的结果为s.
  2. 寻找slowbit值. lowbit(s)s二进制表达式中最右边的1所对应的值. 因此lowbit(s)二进制表达式中只有一个bit 1.
    lowbit(s) = s & -s
    例如: s=1010
    lowbit(s) = 1010 & 0110 = 0010 = 2
  3. lowbit(s)将数组分成两组. 一组中,元素A[i] & lowbit(s) == lowbit(s), 即包含lowbit(s)的bit 1. 剩余的是另一组.
    而且,两个不同数也一定分在不同组. 因为异或值s中的bit1就是因为两个数字的不同而贡献的.
  4. 同一组的元素再异或求出不同数字. 出现两次的数字, 肯定出现同一组, 异或后消除掉.也就是说结果二进制只存在一个1,就是n最低位的1.

代码

class Solution(object):
    def singleNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """
        s = 0
        for i in nums:
            s ^= i
        
        mask = s&(-s)

        res1 = 0
        res2 = 0
        for i in nums:
            if i&mask == mask:
                res1 ^= i
            else:
                res2 ^= i
        
        return res1,res2

发布了219 篇原创文章 · 获赞 85 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/z_feng12489/article/details/103366019