1. 程式人生 > >Leetcode 260:只出現一次的數字 III(最詳細的解法!!!)

Leetcode 260:只出現一次的數字 III(最詳細的解法!!!)

給定一個整數陣列 nums,其中恰好有兩個元素只出現一次,其餘所有元素均出現兩次。 找出只出現一次的那兩個元素。

示例 :

輸入: [1,2,1,3,2,5]
輸出: [3,5]

注意:

  1. 結果輸出的順序並不重要,對於上面的例子, [5, 3] 也是正確答案。
  2. 你的演算法應該具有線性時間複雜度。你能否僅使用常數空間複雜度來實現?

解題思路

類似問題

Leetcode 136:只出現一次的數字(最詳細的解法!!!)

Leetcode 137:只出現一次的數字 II(最詳細的解法!!!)

首先想到的解法就是像137問題一開始提出的那樣,使用dict

class Solution:
    def singleNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        nums_dict = {}
        for num in nums:
            nums_dict[num] = nums_dict.get(num, 0) + 1
		
        result = list()
        for key, val in nums_dict.items():
            if
val == 1: result.append(key) return result

但是如果使用O(1)的空間複雜度要怎麼做呢?我們很快想到通過位運算。一個非常簡單的思路就是我們按照136問題中的方法,直接對每個nums的元素做xor,最後我們得到的結果就是兩個單一元素abxor。因為ab不相同,所以它們之間必定會存在至少一個bit不同,也就是說a xor b的結果中至少有一個bit1。我們從這麼多的bit中挑選出一個,然後其餘位置為0,那我們就構成了這樣的一種mask。例如

00...100

這樣的mask

ab中元素與元素的話,必定有一個結果是0,另外一個結果是mask,這樣我們就將ab給分開了。那麼問題就變成了,怎麼構建這樣的mask?我們使用一個簡單的策略就是a xor b的最右邊的1作為flag。那要怎麼得到最右邊的1呢?這就涉及到補碼的概念,我們知道負數在計算機中使用補碼錶示的,也就是反碼加1

num:5
原碼:0101
反碼:1010
---------
num:-5
補碼:1011

那麼我們通過num&-num就可以取出最右邊的1了。現在我們就可以遍歷nums然後,通過mask就可以判斷nums中的那些元素的右邊第一位是1(根據上面例子),我們將這些數分成一類,將右邊第一位是1的數分成為另外一類,並且我們的ab也就被分到不同的組中。這兩組數字的個數不一定相同,但是最後一定是可以相互通過xor消除,最後只剩ab

from functools import reduce
from operator import xor
class Solution:
    def singleNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        mask = reduce(xor, nums)
        mask &= -mask
        result = [0]*2
        for num in nums:
            if num & mask:
                result[0] ^= num
            else:
                result[1] ^= num

        return result

我將該問題的其他語言版本新增到了我的GitHub Leetcode

如有問題,希望大家指出!!!