【LeetCode 136 137 260】 只出現一次的數字Ⅰ、Ⅱ、Ⅲ (Single Number)

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官網運行結果:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章