在数组中找重复数、只出现一次的数或丢失数的题目(Leetcode题解-Python语言)
在一維數組中的考察中,最常見的就是找出數組中的重復數、只出現一次的數或者丟失(消失)數等等。
一般來說,首先想到的就是用哈希表(集合)來記錄出現過的數,基本所有的題都可以用集合來做,而技巧性在于有時可以把原數組自身作為哈希表;
其次就是位運算,原理是相同的數做異或運算 ^ 會得到0,而一個數與0做異或會得到這個數本身;
最后,在排好序或者對空間要求為O(1)但又不能修改原數組的情況下,二分查找也是一種方法。
136. 只出現一次的數字(找出一個只出現一次的數字)
class Solution:def singleNumber(self, nums: List[int]) -> int:ans = set()for num in nums:if num in ans:ans.remove(num)else:ans.add(num)return ans.pop()雖然可以用集合解決,但是此題最優的做法是位運算,數組里面所有相同的數異或會得到0,而那個只出現一次的數再與0做異或,直接得到結果本身,代碼如下:
class Solution:def singleNumber(self, nums: List[int]) -> int:ans = nums[0]for i in range(1, len(nums)):ans = ans ^ nums[i]return ans217. 存在重復元素(是否存在重復元素)
class Solution:def containsDuplicate(self, nums: List[int]) -> bool:temp = set()for num in nums:if num in temp:return Trueelse:temp.add(num)return False劍指 Offer 03. 數組中重復的數字(找出一個重復元素)
class Solution:def findRepeatNumber(self, nums: List[int]) -> int:temp = set()for num in nums:if num in temp:return numelse:temp.add(num)return -1用集合輕松解決,但是可以不使用額外的集合,而是將原數組本身當作集合,通過交換使得數組的值與索引(下標)一一對應,若出現兩個值對應同一個索引,即為重復的元素。
class Solution:def findRepeatNumber(self, nums: [int]) -> int:i = 0while i < len(nums):if nums[i] == i: # 值與索引已經相同,跳過i += 1continueif nums[i] == nums[nums[i]]: # 值與值所指向下標的值一樣,說明是重復數return nums[i]nums[nums[i]], nums[i] = nums[i], nums[nums[i]]return -1注意: Python 中, a, b = c, d操作的原理是先暫存元組 (c,d) ,然后 “按左右順序” 賦值給 a 和 b 。因此,若寫為 nums[i], nums[nums[i]] = nums[nums[i]], nums[i],則 nums[i] 會先被賦值,之后 nums[nums[i]] 指向的元素則會出錯。
260. 只出現一次的數字 III(劍指 Offer 56 - I. 數組中數字出現的次數)(找出兩個只出現一次的數字)
回想到,對所有數字進行異或就可以得到結果,本題中其余數字也是出現兩次,區別在于有兩個數字只出現一次。在這里,我們會希望這兩個數字分別出現在兩組中,對這兩組都進行異或,這樣就能得到答案了。怎么做呢?線索在于,對所有數字進行異或后的結果,考慮其每一位取值的意義,如果為0,說明這兩個數字的這一位相同,如果為1則不相同。
找到第一位為1的,說明這兩個數在這一位上一個為1、一個為0。以此我們可以把數組分為兩部分,這兩個數各自存在于這兩部分中,劃分的依據就是這一位的取值。至于其他出現兩次的數,在分組時相同的數一定在同一組,因此對這兩組都進行全部異或,出現兩次的數會抵消,最后剩下這兩個數字。
class Solution:def singleNumber(self, nums: List[int]) -> List[int]:# 全部異或ret = reduce(lambda x, y: x ^ y, nums) # reduce(二元函數, 可迭代對象)h = 1while h & ret == 0: # 從右邊開始找第一位為1的(兩個數不同的位)h <<= 1a, b = 0, 0# 分別異或for n in nums:if n & h: # n 在這一位是 1,一個組a ^= nelse: # n 在這一位是 0,另一個組b ^= nreturn [a, b]137. 只出現一次的數字 II(劍指 Offer 56 - II. 數組中數字出現的次數 II)(劍指 Offer II 004. 只出現一次的數字 )(找出一個只出現一次的數字,然而其他數字都出現三次)
class Solution:def singleNumber(self, nums: List[int]) -> int:counter = collections.Counter(nums)ans = [num for num, val in counter.items() if val == 1]return ans[0]這一題用集合的話,還不能簡單地出現過就 pop,沒出現過就 push,因為重復數字是出現三次的,所以應該用 Counter 來解決,更加優化的思路是借鑒數字電路的:
class Solution:def singleNumber(self, nums: List[int]) -> int:a = 0b = 0for i in range(len(nums)):b = (b ^ nums[i]) & ~aa = (a ^ nums[i]) & ~breturn b思路是設置一個狀態機,有 a、b 兩個記錄器:第一次碰到數字 x 時,記錄器 b 記錄下來,記錄器 a 為 0;第二次碰到數字 x 時,記錄器 a 記錄下來,記錄器 b 為 0;第三次碰到數字 x 時,兩個記錄器都為 0。這樣遍歷所有數字之后,出現三次的為 0,出現一次的就存放在記錄器 b 中。
實現記錄器 b 的方法:第一次碰到 x 記錄,第二次碰到 x 變 0,實際上就是異或 b = b ^ x,但是第三次碰到 x 時 b 還是0,區別就只在于記錄器 a 的值為 x ,而第一二次時 a 都為0,因此是 b = (b ^ x) & ~a ,對于記錄器 a 同理。
268. 丟失的數字(劍指 Offer 53 - II. 0~n-1中缺失的數字)(找出 0 - n 范圍中沒出現的那一個數)
class Solution:def missingNumber(self, nums: List[int]) -> int:n = len(nums)ans = 0for i in range(n):ans = ans ^ nums[i] ^ ians ^= (i + 1) # 正常的數組,包括 nreturn ans方法一:正常的數組求和減去缺失的數組求和,差值就是缺失的數;
方法二:正常的數組與缺失的數組做異或,相同的數會異或為0,剩下的就是缺失的數。
448. 找到所有數組中消失的數字(找出多個 1 - n 范圍中沒出現的數)
class Solution:def findDisappearedNumbers(self, nums: List[int]) -> List[int]:n = len(nums)for num in nums:x = (num - 1) % n # 由于是表示下標,所以 num - 1nums[x] += n # 加上 n 不會改變其對 n 取余數的結果ans = [i + 1 for i, num in enumerate(nums) if num <= n] # 沒有被加上 n 的下標就是數組里沒有的return ans由于是多個數沒出現,所以不能簡單地用異或解決,用集合固然可以做,但是更優化地是把原數組本身作為集合,利用值與索引之間的映射關系來找出目標數。此題中,我們把數組中出現了的值對應的下標都加上 n,則沒有被加上 n 的下標就是數組里沒有的。
442. 數組中重復的數據(找出多個 1 - n 范圍中重復出現的數)
class Solution:def findDuplicates(self, nums: List[int]) -> List[int]:n = len(nums)for num in nums:x = (num - 1) % n # 由于是表示下標,所以 num - 1nums[x] += n # 加上 n 不會改變其對 n 取余數的結果ans = [i + 1 for i, num in enumerate(nums) if num > n * 2] # 被加上2次 n 的下標就是數組里重復的數return ans與上一題同理,利用值與索引的對應關系,找出在數組中出現兩次的值(其對應下標的值被兩次加上了 n)。
287. 尋找重復數(找出唯一的重復出現的數)
class Solution:def findDuplicate(self, nums: List[int]) -> int:left = 1right = len(nums) - 1while left < right:mid = left + (right - left) // 2 cnt = 0 # 記錄小于等于mid的元素個數for num in nums:if num <= mid:cnt += 1if cnt > mid:right = midelse:left = mid + 1return left這題比較特別,規定了不能修改數組 nums 且只用常量級 O(1) 的額外空間。給定一個包含 n + 1 個整數的數組,其數字都在 1 到 n 之間,只有一個數字是重復的。因此,對于某個數字 x 來說,正常來說小于等于 x 的數字應該有 x 個,例如有1、2、3、4共4個數字小于等于4,如果大于4了,則說明1、2、3、4其中有一個數字重復了,所以右邊界左移,反之左邊界右移。此為二分數值型。
540. 有序數組中的單一元素(劍指 Offer II 070. 排序數組中只出現一次的數字)(找出有序數組的唯一不重復的數)
class Solution:def singleNonDuplicate(self, nums: List[int]) -> int:left = 0right = len(nums) - 1while left < right:mid = left + (right - left) // 2if mid % 2 == 1: # 只考慮偶數下標mid -= 1if nums[mid] == nums[mid + 1]: # 如果它和下一個數相同,說明還正常,單一元素在右邊區間left = mid + 2else:right = midreturn nums[left]這題用集合可以做到 O(n) 的時間,但是用二分可以做到 O(logn)。注意到,由于數組中只有一個不重復的數,所以總長度一定是奇數,而首尾下標都為偶數。又因為數組是有序的,所以重復數都是兩兩一起出現,且正常的情況都是(偶數索引,奇數索引),只有當出現那一個不重復的數(偶數索引),索引才會變成(奇數,偶數)。所以用二分索引法找到每個偶數下標,如果它和下一個數相同,則說明排序還是正常的,即單一元素在右邊區間;否則,則說明排序已經不正常,單一元素在左邊區間。
41. 缺失的第一個正數(找出數組中沒有出現的最小的正整數)
class Solution:def firstMissingPositive(self, nums: List[int]) -> int:n = len(nums)for i in range(n):if nums[i] <= 0:nums[i] = n + 1for i in range(n):num = abs(nums[i])if num <= n:nums[num - 1] = -abs(nums[num - 1])for i in range(n):if nums[i] > 0:return i + 1return n + 1用集合可以做,但是題目要求時間復雜度為 O(n) 并且只使用常數級別額外空間。實際上,對于一個長度為 N 的數組,其中沒有出現的最小正整數只能在 [1, N+1] 中。這是因為如果 [1, N] 都出現了,那么答案是 N+1,否則答案是 [1, N] 中沒有出現的最小正整數。所以我們的思路就是:不考慮負數,將它們設置為 n + 1;對于在 [1, N] 中的數(此時之前的負數為 n + 1,不會被考慮),將其值對應下標的數變為負(作為已經出現過了的標志);最后找出第一個不是負數的,其對應下標就是沒出現的,即為答案。若所有都出現過,答案則為 n + 1。
總結
以上是生活随笔為你收集整理的在数组中找重复数、只出现一次的数或丢失数的题目(Leetcode题解-Python语言)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Leetcode周赛复盘——第 71 场
- 下一篇: 队列的基础概念与经典题目(Leetcod