位運算(一)陣列中數字出現的次數 Ⅱ
問題描述
在一個數組 \(nums\) 中,有一個數字只出現了一次,但是其它的數字都出現了三次,請找出那個只出現了一次的數字
說明:
-
陣列的長度 \(n\) 滿足 \(1\leq n \leq 1000\)
-
陣列中每個元素的資料範圍 \(1 \leq nums[i] \leq 2^{31}\)
解決思路
-
HashMap
通過
HashMap
統計每個元素的出現次數,最後再遍歷所有的 \(key\) 即可 -
位運算
由於每個元素都是 \(32\) 位的正整數,因此可以考慮通過位運算的方式來統計每個位上 \(1\) 的出現次數,由於多餘的元素都出現了三次,那麼只要該位出現 \(1\)
-
有限自動機
對於一個整數的每個 \(bit\) ,進行遍歷的過程中存在以下三種狀態:對 \(3\) 取餘為 \(0\)、\(1\)、\(2\) 三種情況。
考慮如下的狀態轉換:
-
如果當前處理位的值為 \(1\),則會按照如下的狀態機進行轉換
-
如果處理的位的值為 \(0\),那麼狀態不會發生改變
為了維護這個狀態的變化,一般情況下回考慮通過構建圖的方式來構造這個狀態機,但是在這裡可以通過引入兩個整形變數結合為運算來維護狀態的變化。
具體地,引入兩個整形變數 \(a\) 和 \(b\)
對於 \(a\) 的計算,虛擬碼如下:
if (b == 0): if (n == 0): a = a else if (n == 1): a = ~a else if (b == 1): a = 0
其中,\(n\) 表示當前處理的 \(bit\) 位對應的值,如果引入異或運算,可以轉換為:
if (b == 0): a ^= n else if (b == 1): a = 0
繼續引入與運算,可以轉換為:
a = a ^ n & ~b
對於 \(b\) 的計算:
由於是首先計算 \(a\) ,因此需要在 \(a\) 的基礎上計算 \(b\),和計算 \(a\) 的過程類似,最終計算 \(b\) 的方式為:
b = b ^ n & ~a
-
對於每個元素的每一個位都可以按照如上的方式進行處理,最終 \(a\) 即為需要尋找的目標元素(表示每個位的有效值)
實現
-
HashMap
class Solution { public int singleNumber(int[] nums) { Map<Integer, Integer> map = new HashMap<>(); for (int val : nums) map.put(val, map.getOrDefault(val, 0) + 1); for (int key : map.keySet()) if (map.get(key) == 1) return key; return -1; } }
複雜度分析:
-
時間複雜度:\(O(n)\)
-
空間複雜度:\(O(n)\)
-
-
位運算
class Solution { public int singleNumber(int[] nums) { int[] bits = new int[32]; for (int val : nums) { for (int i = 0; i < 31; ++i) { if (((val >> i) & 1) == 1) bits[i]++; } } int ans = 0; for (int i = 0; i < bits.length; ++i) { if (bits[i] % 3 == 0) continue; ans |= (1 << i); } return ans; } }
複雜度分析:
-
時間複雜度:O(n)
-
空間複雜度:O(1)
-
-
有限自動機
class Solution { public int singleNumber(int[] nums) { int a = 0, b = 0; for(int num : nums){ a = a ^ num & ~b; b = b ^ num & ~a; } return a; } }
複雜度分析:
-
時間複雜度:O(n)
-
空間複雜度:O(1)
-
參考: