Leetcode-雙指針系列1

15. 三數之和
16. 最接近的三數之和
18. 四數之和
26. 刪除排序數組中的重複項
27. 移除元素
75. 顏色分類
88. 合併兩個有序數組
21. 合併兩個有序鏈表
劍指 Offer 21. 調整數組順序使奇數位於偶數前面python

15. 三數之和

給你一個包含 n 個整數的數組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?請你找出全部知足條件且不重複的三元組。算法

注意:答案中不能夠包含重複的三元組。數組

示例:app

給定數組 nums = [-1, 0, 1, 2, -1, -4],

知足要求的三元組集合爲:
[
[-1, 0, 1],
[-1, -1, 2]
]函數

咱們依然使用與兩數之和相似的想法, 將三數之和轉換爲兩數之和, 即首先須要固定第一個數, 而後去移動其他兩個數, 而且爲了方便, 首先將數組進行排序.指針

具體思路以下rest

  • 排序
  • 固定第一個數, 用p0表示. 使用另外兩個指針p1, p2, 分別指向第一個數後面的元素和最後一個元素.
  • 當p0, p1, p2指向的元素之和大於0時, p2左移, 小於0時, p1右移, 不然, 將當前的3個元素添加到結果中. 當p1 = p2時, 結束循環. 回到第2步.

按照上述的思路, 能夠寫出以下的代碼code

def ThreeSum(nums):
    if not sums or len(sums) < 3:
        return []
    
    nums.sort()
    n = len(nums)
    res = []
    for i in range(n-2):
        p1 = i + 1
        p2 = n - 1
        
        while p1 < p2:
            
            if nums[p1] + nums[p2] < -nums[i]:
                p1 += 1
            elif nums[p1] + nums[p2] > -nums[i]:
                p2 -= 1
            else:
                res.append([nums[i], nums[p1], nums[p2]])
                p1 += 1
                p2 -= 1
    return res

額, 看上去代碼並無什麼問題, 結構也還算清楚, 但其實存在一個較大的問題, 上述代碼會獲得重複的結果排序

好比一個排序數組[-4, -1, -1, 0, 1, 2],上述代碼會獲得
[[-1,-1,2],[-1,0,1],[-1,0,1]]的結果, 所以咱們須要跳過第二個及之後的重複元素.three

def ThreeSum(nums):
    if not sums or len(sums) < 3:
        return []
    
    nums.sort()
    n = len(nums)
    res = []
    """
    若是第一個元素就大於0或者最後一個元素還小於0直接返回.
    """
    if nums[0] > 0 or nums[-1] < 0:
        return res
        
    for i in range(n-2):
        p1 = i + 1
        p2 = n - 1
        
        """ 
        去重
        """
        if i > 0 and nums[i] == nums[i-1]:
            continue
            
        while p1 < p2:
            
            if nums[p1] + nums[p2] < -nums[i]:
                p1 += 1
            elif nums[p1] + nums[p2] > -nums[i]:
                p2 -= 1
            else:
                res.append([nums[i], nums[p1], nums[p2]])
                """ 
                去重
                """
                while p1 < p2 and nums[p1] == nums[p1 + 1]:
                    p1 += 1
                while p1 < p2 and nums[p2] == nums[p2 - 1]:
                    p2 -= 1
                p1 += 1
                p2 -= 1

    return res

時間複雜度爲$O(n^2)$, 空間複雜度爲$O(1)$

16. 最接近的三數之和

給定一個包括 n 個整數的數組 nums 和 一個目標值 target。找出 nums 中的三個整數,使得它們的和與 target 最接近。返回這三個數的和。假定每組輸入只存在惟一答案。

示例:

輸入:nums = [-1,2,1,-4], target = 1
輸出:2
解釋:與 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

這題的思路和上一題的思路相似,一樣是固定第一個數, 移動其他兩個數, 代碼很是一致. 而且還不用去重.

def threeSumClosest(nums, target):
    if not nums or len(nums) < 3:
        return []
        
    n = len(nums)
    
    # 排序
    nums.sort()
    res = []
    nearest_sum = sum(nums[:3])
    if nums[0] >= target:
        return sum(nums[:3])
        
    if nums[-1] <= target:
        return sum(nums[-3:])
        
    for i in range(n-2):
        p1 = i + 1
        p2 = n - 1
        
        while p1 < p2:
            temp = nums[i] + nums[p1] + nus[p2]
            
            if abs(temp - target) < abs(nearest_sum - target):
                nearest_sum = temp
                
            if temp < target:
                p1 += 1
            elif temp > target:
                p2 -= 1
            else:
                return target
                
    return nearest_sum

時間複雜度爲$n^2$, 空間複雜度爲$O(1)$

18. 四數之和

給定一個包含 n 個整數的數組 nums 和一個目標值 target,判斷 nums 中是否存在四個元素 a,b,c 和 d ,使得 a + b + c + d 的值與 target 相等?找出全部知足條件且不重複的四元組。

注意:

答案中不能夠包含重複的四元組。

示例:

給定數組 nums = [1, 0, -1, 0, -2, 2],和 target = 0。

知足要求的四元組集合爲:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]

這題的思路仍是和上面幾題的思路相同

def fourSum(nums, target):
    if not nums or len(nums) < 4:
        return []
    
    nums.sort()
    n = len(nums)
    res = []
    for i in range(n-3):
        # 去重
        if i > 0 and nums[i] == nums[i-1]:
            continue
        for j in range(i, n-2):
            p1 = j + 1
            p2 = n - 1
            
            # 去重
            if j > i and nums[j] == nums[j-1]:
                continue
                
            while p1 < p2:
                temp = nums[i] + nums[j] + nums[p1] + nums[p2]
                if temp < target:
                    p1 += 1
                elif temp > target:
                    p2 -= 1
                    
                else:
                    res.append([nums[i], nums[j], nums[p1], nums[p2]])
                    # 去重
                    while p1 < p2 and nums[p1] == nums[p1+1]:
                        p1 += 1
                        
                    while p1 < p2 and nums[p2] == nums[p2-1]:
                        p2 -= 1
                        
                    p1 += 1
                    p2 -= 1
    return res

時間複雜度爲$O(n^3)$, 空間複雜度爲$O(1)$

26. 刪除排序數組中的重複項

給定一個排序數組,你須要在 原地 刪除重複出現的元素,使得每一個元素只出現一次,返回移除後數組的新長度。

不要使用額外的數組空間,你必須在 原地 修改輸入數組 並在使用 O(1) 額外空間的條件下完成。

示例 1:
給定數組 nums = [1,1,2],

函數應該返回新的長度 2, 而且原數組 nums 的前兩個元素被修改成 1, 2。
示例 2:
給定 nums = [0,0,1,1,1,2,2,3,3,4],

函數應該返回新的長度 5, 而且原數組 nums 的前五個元素被修改成 0, 1, 2, 3, 4。

你不須要考慮數組中超出新長度後面的元素。

這題要求原地修改數組, 那麼顯然, 咱們須要將重複的元素移動到數組末尾. 咱們可使用雙指針來解決這個問題.

  • 設置兩個指針p1, p2, 都初始化爲0, 同時指向數組中第一個元素.
  • 當p1位置的元素和p2位置的元素相等時, p2右移, 不然p1右移, 而後交換兩個位置的值, p2右移.
  • 重複執行第2步.
def removeDuplicates(nums):
    if not nums:
        return 0
    if len(nums) < 2:
        return 1
    
    # 同時指向第一個元素
    p1 = p2 = 0
    n = len(nums)
    while p2 < n:
        # 若是不相等, p1右移後, 交換兩指針位置的值
        if nums[p2] != nums[p1]:
            p1 += 1
            nums[p2], nums[p1] = nums[p1], nums[p2]
        p2 += 1
        
    return p1 + 1

時間複雜度爲$O(n)$, 空間複雜度爲$O(1)$

27. 移除元素

給你一個數組 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。

注意這五個元素可爲任意順序。

你不須要考慮數組中超出新長度後面的元素。
  • 設置兩個指針, p1, p2, 同時初始化爲0
  • 當p2元素不等於val時, 交換p1, p2位置元素. p1右移, p2右移
  • 當p2元素等於val時, p2右移
  • 回到第2步
def removeElement(nums, val):
    if not nums:
        return 0
    if len(nums) == 1:
        return int(nums[0] != val)
    
    n = len(nums)
    p1 = p2 = 0
    
    while p2 < n:
        if nums[p2] != val:
            nums[p1], nums[p2] = nums[p2], nums[p1]
            p1 += 1
        p2 += 1
        
    return p1

還能夠用首尾2個指針來移動

  • 設定兩個指針p1,p2, 分別指向0和n-1.
  • 若是p2位置元素等於val, 將其左移, 直至p2位置元素不等於val或者p2小於0.
  • 當p1位置元素等於val時, 交換p1, p2位置的值, p1右移, p2左移.
  • 當p2位置不等於val時, p1右移.
  • 重複2-4步驟, 直至p1大於p2
def removeElement(nums, val):
    if not nums:
        return 0
    if len(nums) == 1:
        return int(nums[0] != val)
    
    n = len(nums)
    p1 = 0
    p2 = n - 1
    
    # 這裏須要有等號
    while p1 <= p2:
        while p2 > p1 and nums[p2] == val:
            p2 -= 1
        if nums[p1] == val:
            nums[p2], nums[p1] = nums[p1], nums[p2]
            p2 -= 1
            
        p1 += 1
        
    return p2 + 1

時間複雜度爲$O(n)$, 空間複雜度爲$O(1)$

75. 顏色分類

給定一個包含紅色、白色和藍色,一共 n 個元素的數組,原地對它們進行排序,使得相同顏色的元素相鄰,並按照紅色、白色、藍色順序排列。

此題中,咱們使用整數 0、 1 和 2 分別表示紅色、白色和藍色。

注意:
不能使用代碼庫中的排序函數來解決這道題。

示例:

輸入: [2,0,2,1,1,0]
輸出: [0,0,1,1,2,2]

進階:

  • 一個直觀的解決方案是使用計數排序的兩趟掃描算法。

    首先,迭代計算出0、1 和 2 元素的個數,而後按照0、一、2的排序,重寫當前數組。

  • 你能想出一個僅使用常數空間的一趟掃描算法嗎?

這題首先想到的應該是計數排序, 時間複雜度爲$O(2n)$, 空間複雜度爲$O(1)$.
但還有複雜度更低的方法, 一樣仍是雙指針.

  • 設置3個指針cur, p1, p2, 分別指向當前位置, 0的右邊界和2的左邊界, 初始化爲0, 0, n-1.
  • 當cur位置元素爲0, 交換p1, cur位置的元素, p1, cur同時右移
  • 當cur位置元素爲2時, 交換p2, cur位置的元素, p2左移
  • 當cur位置元素爲1時, cur右移.
  • 循環執行以上步驟, 直至cur 等於p2
def sortColors(nums):
    if not nums or len(nums) < 2:
        return 
    
    
    cur = p1 = 0
    p2 = len(nums) - 1
    
    while cur < p2:
        if nums[cur] == 0:
            nums[cur], nums[p1] = nums[p1], nums[cur]
            p1 += 1
            cur += 1
        # Note: 當cur位置的值等於2時, cur不須要右移, 由於交換以後並不能保證cur位置的值不等於2.
        elif nums[cur] == 2:
            nums[cur], nums[p2] = nums[p2], nums[cur]
            p2 -= 1
        else:
            cur += 1

時間複雜度爲$O(n)$, 空間複雜度爲$O(1)$

88. 合併兩個有序數組

給你兩個有序整數數組 nums1 和 nums2,請你將 nums2 合併到 nums1 中,使 nums1 成爲一個有序數組。

說明:

  • 初始化 nums1 和 nums2 的元素數量分別爲 m 和 n 。
  • 你能夠假設 nums1 有足夠的空間(空間大小大於或等於 m + n)來保存 nums2 中的元素。

示例:

輸入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3

輸出: [1,2,2,3,5,6]

最直觀, 最簡單的方式就是合併再排序, 對應的時間複雜度爲$O((m+n)log(m+n))$

但其實咱們可使用雙指針進一步下降複雜度

  • 設置兩個指針p1, p2, 分別指向nums1的m-1和nums2的n-1位置.
  • 當p1位置元素大於p2位置元素時, 將nums1的m+n-1位置的值者只爲p1位置的值, p1左移, 不然將nums1的m+n-1位置的值者只爲p2位置的值, p2左移.
  • 回到上一步, 直至p1<0或者p2<0
def merge(nums1, m, nums2, n):
    if not nums2:
        return nums1
    
    i = m - 1
    j = n - 1
    
    while i >= 0 and j >= 0:
        if nums1[i] > nums[j]:
            nums1[i+j+1] = nums1[i]
            i -= 1
        else:
            nums1[i+j+1] = nums2[j]
            j -= 1
    if j >= 0:
        nums1[:j] = nums2[:j]
    return nums1

時間複雜度爲$O(m)$或者$O(n)$, 空間複雜度爲$O(1)$

21. 合併兩個有序鏈表

將兩個升序鏈表合併爲一個新的 升序 鏈表並返回。新鏈表是經過拼接給定的兩個鏈表的全部節點組成的。 

示例:
輸入:1->2->4, 1->3->4
輸出:1->1->2->3->4->4
def mergeTwoLists(l1, l2):
    
    p1 = l1
    p2 = l2
    
    # 初始化爲一個臨時節點
    p0 = new_ln = ListNode(-1)
    while p1 and p2:
        if p1.val < p2.val:
            p0.next = p1
            p1 = p1.next
        else:
            p0.next = p2
            p2 = p2.next
        p0 = p0.next
    if p1:
        p0.next = p1
    if p2:
        p0.next = p2
        
    return new_ln.next

時間複雜度爲$O(n)$, 空間複雜度爲$O(1)$

劍指 Offer 21. 調整數組順序使奇數位於偶數前面

輸入一個整數數組,實現一個函數來調整該數組中數字的順序,使得全部奇數位於數組的前半部分,全部偶數位於數組的後半部分。

示例:

輸入:nums = [1,2,3,4]
輸出:[1,3,2,4]
注:[3,1,2,4] 也是正確的答案之一。
  • 使用兩個指針p1, p2, 分別初始化爲0, n-1
  • 當p1位置元素爲奇數時, p1右移;
  • 當p2位置元素爲偶數時, p2左移
  • 不然交換p1, p2位置元素.
def exchange(nums):
    if not nums or len(nums) < 2:
        return nums
    
    n = len(nums)
    
    p1 = 0
    p2 = n - 1
    while p1 < p2:
        if nums[p1] % 2:
            p1 += 1
        elif not nums[p2] % 2:
            p2 -= 1
        else:
            nums[p1], nums[p2] = nums[p2], nums[p1]
            
    return nums

一樣, 快慢指針也能夠解決問題

def exchange(self, nums: List[int]) -> List[int]:
    if not nums or len(nums) < 2:
        return nums
    
    n = len(nums)
    p2 = p1 = 0
    while p2 < n:
        if nums[p2] %2:
            nums[p1], nums[p2] = nums[p2], nums[p1]
            p1 += 1
        p2 += 1
    return nums

時間複雜度爲$O(n)$, 空間複雜度爲$O(1)$

總結

雙指針的思路在數組和鏈表的題目中有着普遍的應用, 這裏主要介紹了數組中應用.

雙指針分爲兩種, 快慢指針(這裏將移動頻率不同的稱爲快慢指針)和首尾指針. 首尾指針主要在數組中使用, 快慢指針在數組和鏈表中都有普遍應用.

快慢指針的基本思路是

  • 同時初始化爲0或者鏈表頭節點
  • 當前元素不知足條件時, 交換快慢指針指向的值; 慢指針右移, 快指針右移
  • 不然, 快指針右移
  • 重複2-3步驟

而首尾指針, 一般須要先將數組排序, 好比3數之和,4數之和等 而後根據具體狀況選擇移動首指針或者尾指針, 直至知足條件.

水平有限, 不免有錯誤或不足的地方. 還請不吝賜教.

相關文章
相關標籤/搜索