[LeetCode] Single Element in a Sorted Array 有序數組中的單獨元素


 

Given a sorted array consisting of only integers where every element appears twice except for one element which appears once. Find this single element that appears only once.

Example 1:

Input: [1,1,2,3,3,4,4,8,8]
Output: 2

 

Example 2:

Input: [3,3,7,7,10,11,11]
Output: 10

 

Note: Your solution should run in O(log n) time and O(1) space.

 

這道題給我們了一個有序數組,說是所有的元素都出現了兩次,除了一個元素,讓我們找到這個元素。如果沒有時間復雜度的限制,我們可以用多種方法來做,最straightforward的解法就是用個雙指針,每次檢驗兩個,就能找出落單的。也可以像Single Number里的方法那樣,將所有數字亦或起來,相同的數字都會亦或成0,剩下就是那個落單的數字。那么由於有了時間復雜度的限制,需要為O(logn),而數組又是有序的,不難想到要用二分搜索法來做。二分搜索法的難點在於折半了以后,如何判斷將要去哪個分支繼續搜索,而這道題確實判斷條件不明顯,比如下面兩個例子:

1  1  2  2  3

1  2  2  3  3

這兩個例子初始化的時候left=0, right=4一樣,mid算出來也一樣為2,但是他們要去的方向不同,如何區分出來呢?仔細觀察我們可以發現,如果當前數字出現兩次的話,我們可以通過數組的長度跟當前位置的關系,計算出右邊和當前數字不同的數字的總個數,如果是偶數個,說明落單數左半邊,反之則在右半邊。有了這個規律就可以寫代碼了,為啥我們直接就能跟mid+1比呢,不怕越界嗎?當然不會,因為left如何跟right相等,就不會進入循環,所以mid一定會比right小,一定會有mid+1存在。當然mid是有可能為0的,所以此時當mid和mid+1的數字不等時,我們直接返回mid的數字就可以了,參見代碼如下:

 

解法一:

class Solution {
public:
    int singleNonDuplicate(vector<int>& nums) {
        int left = 0, right = nums.size() - 1, n = nums.size();
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == nums[mid + 1]) {
                if ((n - 1 - mid) % 2 == 1) right = mid;
                else left = mid + 1;
            } else {
                if (mid == 0 || nums[mid] != nums[mid - 1]) return nums[mid];
                if ((n - 1 - mid) % 2 == 0) right = mid;
                else left = mid + 1;
            }
        }
        return nums[left];
    }
};

 

下面這種解法是對上面的分支進行合並,使得代碼非常的簡潔。使用到了亦或1這個小技巧,為什么要亦或1呢,原來我們可以將坐標兩兩歸為一對,比如0和1,2和3,4和5等等。而亦或1可以直接找到你的小伙伴,比如對於2,亦或1就是3,對於3,亦或1就是2。如果你和你的小伙伴相等了,說明落單數在右邊,如果不等,說明在左邊,這方法,太叼了有木有,參見代碼如下:

 

解法二:

class Solution {
public:
    int singleNonDuplicate(vector<int>& nums) {
        int left = 0, right = nums.size() - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == nums[mid ^ 1]) left = mid + 1;
            else right = mid;
        }
        return nums[left];
    }
};

 

下面這種解法其實跟上面的方法其實有些類似,雖然沒有亦或1,但是將right縮小了一倍,但是在比較的時候,是比較mid*2和mid*2+1的關系的,這樣還是能正確的比較原本應該相等的兩個小伙伴的值的,其實核心思路和上面一樣,參見代碼如下:

 

解法三:

class Solution {
public:
    int singleNonDuplicate(vector<int>& nums) {
        int left = 0, right = nums.size() / 2;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid * 2] == nums[mid * 2 + 1]) left = mid + 1;
            else right = mid;
        }
        return nums[left * 2];
    }
};

 

下面這種方法其實跟解法二很像,沒有用亦或1,但是對mid進行了處理,強制使其成為小伙伴對兒中的第一個位置,然后跟另一個小伙伴比較大小,參見代碼如下:

 

解法四:

class Solution {
public:
    int singleNonDuplicate(vector<int>& nums) {
        int left = 0, right = nums.size() - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (mid % 2 == 1) --mid;
            if (nums[mid] == nums[mid + 1]) left = mid + 2;
            else right = mid;
        }
        return nums[left];
    }
};

 

參考資料:

https://discuss.leetcode.com/topic/83310/short-compare-nums-i-with-nums-i-1

https://discuss.leetcode.com/topic/82235/java-code-by-using-binary-search-o-log-n

https://discuss.leetcode.com/topic/82332/java-binary-search-o-log-n-shorter-than-others

https://discuss.leetcode.com/topic/87424/java-binary-search-short-7l-o-log-n-w-explanations

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM