LeetCode 第136、137、260剛好都是同一類型的題目:只出現一次的數字,放在一起進行整理
136. 只出現一次的數字Ⅰ
給定一個非空整數數組,除了某個元素只出現一次以外,其餘每個元素均出現兩次。找出那個只出現了一次的元素。
說明:
你的算法應該具有線性時間複雜度。 你可以不使用額外空間來實現嗎?
- 示例 1:
輸入: [2,2,1]
輸出: 1 - 示例 2:
輸入: [4,1,2,1,2]
輸出: 4
思路:這道題比較簡單,相同的數可以用異或的方式處理掉,比如 a ^ a = 0,所以我們可以對數組內的所有數進行異或操作,每對相同的數被抵消,只剩下唯一那個只出現一次的元素
public int singleNumber(int[] nums) {
// 對輸入進行判斷
if (nums == null) {
return 0;
}
int output = 0;
// 遍歷整個數組,進行異或處理
for (int a : nums
) {
output ^= a;
}
return output;
}
137. 只出現一次的數字Ⅱ
給定一個非空整數數組,除了某個元素只出現一次以外,其餘每個元素均出現了三次。找出那個只出現了一次的元素。
說明:
你的算法應該具有線性時間複雜度。 你可以不使用額外空間來實現嗎?
- 示例 1:
輸入: [2,2,3,2]
輸出: 3 - 示例 2:
輸入: [0,1,0,1,0,1,99]
輸出: 99
思路:這裏提供三種思路
① 利用HashSet,遍歷整個數組,將整個數組存入HashSet中,相當於重複的部分被刪除了,計算有setSum(一套重複數字+只出現了一次的數),再計算整個數組所有元素的和totalSum(三套重複數字+只出現了一次的數),通過(setSum*3-totalSum)/2。這種思路的代碼:
private static int singleNubmer1(int[] A) {
if (A == null) {
return 0;
}
Set<Integer> set = new HashSet<>(A.length);
int totalSum = 0, setSum = 0;
for (int a : A
) {
totalSum += a;
if (!set.contains(a)) {
set.add(a);
setSum += a;
}
}
return (setSum * 3 - totalSum) / 2;
}
但這種方法不適合於JAVA,因爲在求和的過程中,可能因爲數字太大,超過了Integer.MAX_VALUE,而出現異常。這種思路可以用於python語言進行實現。而在一開始,指定了hashset的初始容量後,可以將整個算法的複雜度約爲O(n),但空間複雜度較大
② 將所有數的同一位進行相加,再對3進行取餘,重複的數字會因爲對3取餘而被抵消掉,剩下的數即爲不重複的數
private static int singleNumber(int[] A) {
int result = 0;
// 遍歷Integer的每一位
for (int i = 0; i < 32; i++){
int temp = 0;
// 遍歷每個數的同一位
for (int a : A) {
temp += (a>>i & 1);
}
// 對3取餘
result |= ((temp % 3) << i);
}
return result;
}
這種做法時間複雜度爲O(n),下面是在leetcode官網提交的結果,還算是比較好的一種解法
③ 後面突然搜到了別人的另外一種做法:
https://blog.csdn.net/i_am_bird/article/details/78160627
private static int singleNumber2(int[] A) {
//記錄只出現過1次的bits
int ones = 0;
//記錄只出現過2次的bits
int twos = 0;
// 記錄出現過3次的bits
int threes;
// 遍歷所有數
for (int i : A
) {
// 通過現在的數跟出現一次的數進行並運算,找出出現了兩次的數
twos |= ones & i;
// 除去出現了一次的數中當前的數
ones ^= i;
// 如果在出現兩次中出現了,還在出現一次中出現,則說明出現了三次
threes = ones & twos;
// 將出現三次的情況在記錄只出現過1次的bits中除去
ones &= ~threes;
// 將出現三次的情況在記錄只出現過2次的bits中除去
twos &= ~threes;
}
// 返回只出現一次的bits,即沒有重複的數
return ones;
}
在leetcode官網提交的結果:
260. 只出現一次的數字Ⅲ
給定一個整數數組 nums,其中恰好有兩個元素只出現一次,其餘所有元素均出現兩次。 找出只出現一次的那兩個元素。
- 示例 :
輸入: [1,2,1,3,2,5]
輸出: [3,5]
注意:
結果輸出的順序並不重要,對於上面的例子, [5, 3] 也是正確答案。
你的算法應該具有線性時間複雜度。你能否僅使用常數空間複雜度來實現?
思路:
參考只出現一次的數字Ⅰ,對所有數進行異或操作,可以獲得兩個沒有重複的數a、b的異或操作的結果。
因爲a、b是不同的數,則他們異或的結果一定至少存在一個1,找出其中的一個1,記爲c。
重新遍歷數組,如果每個數跟c取與操作,如果相同,則歸入集合A,如果不同則歸入集合B,我們可知,集合A裏面有一個不重複的數,和成對的其他重複的數,集合B裏有另外一個重複的數,和成對的其他重複的數。相當於問題轉爲兩個求只出現一次的數字Ⅰ的問題
這裏舉一個具體的例子解釋一下:{1,1,2,2,3,5,8,5,8,10}
其中不同的元素爲:3(0011),10(1010)
對整個數組進行異或操作,最後只剩下3跟10進行異或操作:1001
我們可以通過獲取其中的一個1,例如 c = 0001
我們重新遍歷整個數組,每個數與c進行與操作,判斷是否爲0,可以將數組分成:
與爲0:{2,2,8,8,10} 與爲1:{1,1,3,5,5}
剛好將兩個不同的數字分開,且將重複的數字分到了相同的集合中,再對每個集合進行異或操作,即可以獲得不同的數字
public int[] singleNumber(int[] nums) {
int a = 0;
// 對整個數組進行異或操作
for (int i : nums
) {
a ^= i;
}
// 取一位1
a &= -a;
int[] result = new int[2];
// 重新遍歷數組,將數組進行分組
for (int i : nums
) {
if ((i & a) == 0) {
result[0] ^= i;
} else {0
result[1] ^= i;
}
}
return result;
}
Leetcode官網運行結果: