2/19 的 Daily 是 Eazy 題: 268. Missing Number by Math, Graph Theory and Bit Manipulation
目錄
題目分析
題目
Given an array nums
containing n
distinct numbers in the range [0, n]
, return the only number in the range that is missing from the array.
Example 1:
Input: nums = [3,0,1]
Output: 2
Explanation: n = 3 since there are 3 numbers, so all numbers are in the range [0,3]. 2 is the missing number in the range since it does not appear in nums.
Example 2:
Input: nums = [0,1]
Output: 2
Explanation: n = 2 since there are 2 numbers, so all numbers are in the range [0,2]. 2 is the missing number in the range since it does not appear in nums.
Example 3:
Input: nums = [9,6,4,2,3,5,7,0,1]
Output: 8
Explanation: n = 9 since there are 9 numbers, so all numbers are in the range [0,9]. 8 is the missing number in the range since it does not appear in nums.
Constraints:
n == nums.length
1 <= n <= 10^4
0 <= nums[i] <= n
All the numbers of nums are unique.
Follow up: Could you implement a solution using only
O(1)
extra space complexity andO(n)
runtime complexity?
分析
找出在 0~n
之中缺少的那個數。問題本身相當簡單,不過要達成附加題的條件 S = O(1)
,發現了許多有趣的作法。
解題思路
Counting (Hashmap)
最直覺的作法肯定是counting了。
比如說利用一個陣列來記錄每個數有沒有出現過
class Solution:
def missingNumber(self, nums: List[int]) -> int:
missing = [True]*(len(nums)+1)
for i in nums:
missing[i] = False
for i, appear in enumerate(missing):
if appear: return i
或者使用hashmap達到一樣的效果:
class Solution:
def missingNumber(self, nums: List[int]) -> int:
appeared = set(nums)
return [i for i in range(len(nums)+1) if i not in appeared][0]
不過這種作法,因為需要建立一個記錄用的array(list)或hashmap(set),空間複雜度為S = O(n)
,並無法達到追加題目要求的S = O(1)
。
Graph(Linked List)
建立記錄用的表需要消耗O(n)空間,那假如我們直接利用nums本身,透過修改其值來記錄,就不需要額外的空間了!
主要的思路是這樣的:
nums
中出現i
,就把nums[i]
改為-1
。最後沒有變成-1
的位置,其index就是沒出現過的那個數。
以例題的Ex1為例:
nums = [3,0,1]
-
在
nums
裡面加入一個在[-1,n)
區間以外的值,讓陣列的長度為n+1
,才能紀錄0 ~ n
nums = [3,0,1,3]
-
遍歷
nums[0:3]
,也就是原本的前三格-
第
0
格的數字是3
,因此查看nums[3]
,記住其數值為3
,並將nums[3]
改為-1
,紀錄3
出現過了nums = [3,0,1,-1]
下一個要記錄的數:
3
查看
nums[3]
,發現其值已經是-1
了,代表3
已經出現過。 -
繼續遍歷
nums
,來到第1
格,數字是0
因此檢查
nums[0]
,記住其值為3
,並將nums[0]改成-1
,紀錄0
出現過了nums = [-1,0,1,-1]
下一個要記錄的數:
3
查看
nums[3]
,發現其值已經是-1
了,代表3
已經出現過。 -
繼續遍歷
nums
,來到第2
格,數字是1
因此檢查
nums[1]
,記住其值為0
,並將nums[1]
改成-1
,紀錄1
出現過了nums = [-1,-1,1,-1]
查看
nums[0]
,發現其值已經是-1
了,代表不需要繼續檢查下去。
-
-
結束檢查,此時的
nums
中,只剩下一個位置不是1
:nums[2]
因此,原本的
nums
中缺少的數就是2
。
範例程式碼:
class Solution:
def missingNumber(self, nums: List[int]) -> int:
nums.append(len(nums))
for n in nums[:len(nums)-1]:
i = n
while i!=-1:
j = nums[i]
nums[i] = -1
i = j
return [i for i, n in enumerate(nums) if n!=-1][0]
由於這個作法過程中的紀錄都靠直接修改nums的值來完成,所以不需要額外的空間,符合追加題所要求的 S = O(1)
Math
由於題目的nums裡面含有0~n中的所有數、只缺少其中的一個數,所以我們可以利用等差級數和的公式直接得知少哪一個數。
class Solution:
def missingNumber(self, nums: List[int]) -> int:
return len(nums)*(len(nums)+1)//2 - sum(nums)
因為需要求sum(nums),時間複雜度仍為T = O(n)
直接代公式不需要紀錄用的陣列,因此空間複雜度符合追加題所要求的S = O(1)
Bit XOR Sum (Bit Manipulation)
雖然有點拿牛刀殺雞的感覺,不過以下利用bit xor操作的特性的作法是本文解法中我最喜歡的。因為很酷(X)
引理:
a xor a == 0
a xor 0 == a
XOR運算滿足結合律 (順序互換答案相同),即
a xor b xor c == a xor c xor b
因此,若把0~n
中的所有數,和nums
中的所有數全部連著作 xor
運算
在nums中有出現的數都會成雙成對,然後經過xor變成0。因此最終剩下來的答案就是nums中未出現的數。
例如:
nums = [3,0,1] , n = len(nums) = 3
(3 xor 0 xor 1) xor (0 xor 1 xor 2 xor 3)
= (0 xor 0) xor (1 xor 1) xor 2 xor (3 xor 3)
= 0 xor 0 xor 2 xor 0
= 2
範例程式碼:
class Solution:
def missingNumber(self, nums: List[int]) -> int:
return (reduce(lambda p, i: p^i^nums[i],(i for i in range(len(nums))),len(nums)))
時間複雜度仍為T = O(n)
由於只需要一個變數來持續記錄每次xor運算之後的結果,空間複雜度符合追加題所要求的S = O(1)
。
結語
Leetcode 的 Easy 和 Medium 題有時候會出現這種Follow up(追加題),往往都能從中獲得有趣的知識!
感謝你的閱讀,如果有不同的想法或心得,歡迎留言一同討論!