題目描述
給你一個數組 nums 和一個值 val,你需要 原地 移除所有數值等於 val 的元素,並返回移除後數組的新長度。
不要使用額外的數組空間,你必須僅使用 O(1) 額外空間並 原地 修改輸入數組。
元素的順序可以改變。你不需要考慮數組中超出新長度後面的元素。
示例 1:
給定 nums = [3,2,2,3], val = 3,
函數應該返回新的長度 2, 並且 nums 中的前兩個元素均爲 2。
你不需要考慮數組中超出新長度後面的元素。
示例 2:
給定 nums = [0,1,2,2,3,0,4,2], val = 2,
函數應該返回新的長度 5, 並且 nums 中的前五個元素爲 0, 1, 3, 0, 4。
注意這五個元素可爲任意順序。
你不需要考慮數組中超出新長度後面的元素。
理解題意
- 原地修改數組,指的是直接對數組參數 取下標來修改數組元素,而不是通過將數組參數引用指向一個新的數組。
- 返回值爲與val不等的元素個數。
解法——快慢指針
解法關鍵詞:
- 雙指針
- 快慢指針
- 我們將算法結束後關心的前n個元素,稱爲壓實數組。因爲整個過程看起來就是在往左壓實。
- compactIndex代表即將加入壓實數組的那個元素應該賦值的索引。所以初始時,compactIndex爲0,因爲初始時一個非val元素都沒有得到確認。
- cursor即爲遍歷指針。
- 當遍歷元素非val時,將其賦值給compactIndex位置,並compactIndex增加1.
- 由於compactIndex剛好與壓實數組的長度一樣,所以函數返回compactIndex。
- 圖中最後一步,壓實數組中兩個元素顏色不同,是因爲它們可能不相等,但都不是val。
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
compactIndex = 0
for cursor in range(len(nums)):
if nums[cursor] != val:
nums[compactIndex] = nums[cursor]
compactIndex += 1
return compactIndex
解法——雙端指針
解法關鍵詞:
- 雙指針
- 雙端指針
題目中提到了元素的順序可以修改,所以可以使用這種解法,時間複雜度一樣。
- 之間的解法是通過一個指針來遍歷數組,現在則是通過兩端的指針來遍歷數組,通過待確認的元素納入兩個指針的範圍內。左指針往右移動,右指針往左移動。
- 通過left指針來判斷壓實數組的末尾,因爲left總是指向 壓實數組最大索引+1。(最大索引+1 就是長度)
- 如果數組都是val,那麼left不動,right一直移動;如果數組都非val,那麼right不動,left一直移動。
- 當left元素非val時,說明它不是想要的,則可以被覆蓋,覆蓋的值則是right元素,並right指針減1。因爲下一次檢查還是從left檢查。
- 當left元素爲val時,說明它是想要的,確認了一個壓實元素,則left指針移動。
- 圖中可見,五個元素只循環了五次。
class Solution:
def removeElement(self, nums, val):
left = 0
right = len(nums)-1
while(left <= right):
if nums[left] == val:
nums[left] = nums[right]
right -= 1
else:
left += 1
return left
- 因爲right指向最大元素所在索引,所以區間是一個
[left, right]
的左閉右閉區間(每次循環中,待確認的元素,就在這個區間之中),所以最後一次循環肯定是left = right的情況。所以循環條件爲left <= right
。
class Solution:
def removeElement(self, nums, val):
left = 0
right = len(nums)
while(left < right):
if nums[left] == val:
right -= 1
nums[left] = nums[right]
else:
left += 1
return left
- 因爲right指向最大元素所在索引+1,所以區間是一個
[left, right)
的左閉右開區間(每次循環中,待確認的元素,就在這個區間之中),所以最後一次循環肯定是left < right的情況。那麼其實每次循環的條件都是left < right了,所以循環條件爲left < right
。