【LeetCode】小白算法成長記之雙指針

不積跬步,無以致千里;不積小流,無以成江海。python

前言

內容主要是我的學習使用,題目分類以及部分參考資料來自於CyC的博客,很是感謝大佬,題目來源於LeetCode,很是感謝本站支持。git

167. 兩數之和 II - 輸入有序數組(Easy) 👈

給定一個已按照升序排列的有序數組,找到兩個數使得它們相加之和等於目標數。函數應該返回這兩個下標值 index1 和 index2,其中 index1 必須小於 index2。github

說明:
返回的下標值(index1 和 index2)不是從零開始的。你能夠假設每一個輸入只對應惟一的答案,並且你不能夠重複使用相同的元素。算法

示例:數組

輸入: numbers = [2, 7, 11, 15], target = 9
輸出: [1,2]
解釋: 2 與 7 之和等於目標數 9 。所以 index1 = 1, index2 = 2 。

解題思路:
本題是1. 兩數之和的升級版本,題意給定的是一個升序排列的有序數組,利用雙指針,指針1指向值較小的元素,指針2指向值較大的元素。指針1從頭向尾遍歷,指針2從尾向頭遍歷。app

  • 存在有序性,即左邊元素始終小於右邊元素,因此指針1的元素始終小於指針2的元素
  • 當元素和sum>target,即須要減少某個元素,因此將指針2向左移動,指針1不動(之因此不把指針1向左移動是促使兩個指針相遇,終止條件)
  • 當元素和sum<target,即須要增大某個元素,因此將指針1向右移動,指針2不動
  • sum==target,且指針1和指針2不指向同一個元素

代碼實現:函數

class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        left = 0
        right = len(numbers)-1
        while left<right: # 判斷指針是否相遇
            num = numbers[left] + numbers[right]
            if num>target:
                right-=1 # 指針2左移
            elif num<target:
                left+=1  # 指針1右移
            else:
		# 下標加一返回位置
                left+=1
                right+=1
                return [left,right]

633. 平方數之和(Easy) 👈

給定一個非負整數 c ,你要判斷是否存在兩個整數 a 和 b,使得 a2 + b2 = c。學習

示例1:ui

輸入: 5
輸出: True
解釋: 1 * 1 + 2 * 2 = 5

示例2:指針

輸入: 3
輸出: False

解題思路:
本題實際上是上一題的變形,target等於兩個數的平方和,題目核心條件:

  • 可取範圍是數是0-√target,由於a2+b2=target,極端狀況是a=0,b=√target,因此當a不斷增大,b必定須要小於√target
  • 由此0-√target能夠看做是一組有序的數組[0,1,2,3,4,5,6...]
  • 利用雙指針,指針1指向開頭,向右移動,指針2指向結尾,向左移動

代碼實現:

class Solution:
    def judgeSquareSum(self, c: int) -> bool:
        left = 0 
        right = int(c**0.5) # 至關於√target
        while left<=right: # 指針能夠相遇,例如:1*1+1*1=2,即left指向1,right指向1
            num = left*left+right*right
            if num>c:
                right-=1 # 指針2左移
            elif num<c:
                left+=1  # 指針1右移
            else:
                return True
        return False

345. 反轉字符串中的元音字母(Easy) 👈

編寫一個函數,以字符串做爲輸入,反轉該字符串中的元音字母。

示例 1:

輸入: "hello"
輸出: "holle"

示例 2:

輸入: "leetcode"
輸出: "leotcede"

說明:
元音字母不包含字母"y"。

解題思路:

  • 元音字母:{"a","e","i","o","u",'A', 'E', 'I', 'O', 'U'},採用集合存儲時間複雜度優於列表,Python底層採用的是hash。
  • 主要就是反轉元音字母,即當元音字母爲奇數個,存在一個不動,元音字母爲偶數個,則一一對調。
  • 一一對調即採用雙指針,指針1從左邊找,往有運動。指針2從右邊找,往左運動。
    • 當指針1,2指向的都是元音字母則對調
    • 指針1是元音字母,指針2不是,則指針1不動,等待指針2找到元音字母,雙方對調
    • 反之亦是
  • 終止條件是指針1與指針2相遇

代碼實現:

class Solution:
    def reverseVowels(self, s: str) -> str:
        words = {"a", "e", "i", "o", "u", 'A', 'E', 'I', 'O', 'U'}
        l = list(s) # 拆分列表用於交換元素
        left = 0
        right = len(l) - 1

        while left < right:
            if l[left] in words and l[right] in words:  # 指針1,2都指向元音
                l[left], l[right] = l[right], l[left]
                left += 1
                right -= 1
            elif l[left] not in words:  # 指針1沒有指向元音,指針2指向元音
                left += 1
            elif l[right] not in words:  # 指針2沒有指向元音,指針1指向元音
                right -= 1

        return "".join(l)

680. 驗證迴文字符串 Ⅱ(Easy)👈

給定一個非空字符串 s,最多刪除一個字符。判斷是否能成爲迴文字符串。

示例 1:

輸入: "aba"
輸出: True

示例 2:

輸入: "abca"
輸出: True
解釋: 你能夠刪除c字符。

注意:
字符串只包含從 a-z 的小寫字母。字符串的最大長度是50000。

解題思路:
本題是9. 迴文數的升級版,即給迴文字符串一次機會,注意這個機會存在兩種狀況,具體狀況以下:

  • 迴文字符串採用雙指針的思想,指針1從左向右掃描移動,指針2從右向左掃描移動,判斷指針1和指針2的值是否相等,相等則,指針1右移一格,指針2左移一格,不然給一次機會,即刪除一個字符
    • 指針2不動,指針1右移一格,繼續上面的步驟,直到指針相遇
    • 指針1不動,指針2左移一個,繼續上面的步驟,直到指針相遇

官方說明:貪心算法,即當左右指針相等,此時驗證就是去頭去尾後的字符串,保證它爲迴文,則整個字符串就是迴文,依次遞減。

代碼實現:

class Solution:
    def validPalindrome(self, s: str) -> bool:
        left = 0
        right = len(s) - 1
        while left < right:
            if s[left] == s[right]:
                left += 1
                right -= 1
            else:
                return self.compare(s, left + 1, right) or self.compare(s, left, right - 1) # 刪除一個元素,左邊或右邊,剩餘的檢查是否爲迴文字符串
        return True

    def compare(self, s, left, right):
        while left < right:
            if s[left] != s[right]:
                return False
            left += 1
            right -= 1
        return True

88. 合併兩個有序數組(Easy)👈

給你兩個有序整數數組 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]

解題思路:
當時看到這個題仍是有點懵逼,主要是利用num1,由題意可知nums1有足夠空間,採用雙指針以下:

  • 指針1指向num1結尾元素(此時的結尾就是nums1中真正存在元素的最後一個位置),指針2指向num2結尾元素
  • 比較指針1元素和指針2元素,誰大,就把當前元素放在num1結尾(此時結尾就是m+n-1)例如:
    • nums1 = [1,2,3,0,0,0], nums2 = [2,5,6],指針1=3,指針2=6,6>3,則:
    • nums1 = [1,2,3,0,0,6], nums2 = [2,5,6]
  • 當某個指針移動到數組頭部,存在某種狀況:
    • 指針1移動到頭部,指針2還沒,說明剩餘nums2的元素小於nums1最小的元素,則把剩餘nums2的元素添加到nums1開頭
    • 指針2移動頭部,無論指針1到沒到頭部都無論,已經將nums2完整合併到nums1

代碼實現:

class Solution:
    def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
        """
        Do not return anything, modify nums1 in-place instead.
        """
        p1 = m - 1
        p2 = n - 1
        k = m + n - 1
        while p1 >= 0 and p2 >= 0:
            if nums1[p1] > nums2[p2]:
                nums1[k] = nums1[p1] # nums1>nums2,將nums1當前元素添加在末尾
                p1 -= 1
            else:
                nums1[k] = nums2[p2] # nums2>nums1,將nums2當前元素添加在末尾 
                p2 -= 1
            k -= 1 # 每添加一個末尾遊標減一
        nums1[:p2+1] = nums2[:p2+1]  # 將剩餘的nums2元素添加在nums對應位置(通常爲頭部)

141. 環形鏈表(Easy)👈

給定一個鏈表,判斷鏈表中是否有環。
爲了表示給定鏈表中的環,咱們使用整數 pos 來表示鏈表尾鏈接到鏈表中的位置(索引從 0 開始)。 若是 pos 是 -1,則在該鏈表中沒有環。

示例 1:

輸入:head = [3,2,0,-4], pos = 1
輸出:true
解釋:鏈表中有一個環,其尾部鏈接到第二個節點。

解題思路:
這個題目用了一個很巧妙的方法,就像是腦筋急轉彎,主要利用雙指針+快慢指針:

  • 雙指針即指針1指向第一個節點,指針2指向第二個節點,而且讓指針1每次移動一格,指針2每次移動2格,就是所謂的快慢指針。
  • 結合本題判斷鏈表是否有環,由於指針2在前而且步長大,若是沒有環指針2確定優先到達鏈表末尾,結束判斷,即結果是無環。若是鏈表有環,因爲指針2比指針1跑的快,因此在某一節點指針2一定追上指針1與其相遇,即結果有環。

代碼實現:

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        if(head == None or head.next == None):
            return False
        slow = head # 慢指針
        quick = head.next # 快指針
        while quick != None and quick.next != None: # 判斷當前快指針和快指針下一個(防止溢出)是否走到鏈表尾部
            if quick == slow: # 快慢相遇
                return True
            slow = slow.next  # 慢指針指向下一個
            quick = quick.next.next # 快指針指向下下個
        return False

524. 經過刪除字母匹配到字典裏最長單詞(Medium)👈

給定一個字符串和一個字符串字典,找到字典裏面最長的字符串,該字符串能夠經過刪除給定字符串的某些字符來獲得。若是答案不止一個,返回長度最長且字典順序最小的字符串。若是答案不存在,則返回空字符串。

示例 1:

輸入:
s = "abpcplea", d = ["ale","apple","monkey","plea"]
輸出: 
"apple"

題意:

  1. 把s字符串中某些字符刪除等到的字符串存在於d數組中,即找出d數組中s的子串
  2. 存在多個子串,找出最長的那一個
  3. 存在多個子串,長度最長且相等,選出按字典順序靠前的那一個

解題思路:

  • 把列表字符按照長度和字典順序排序
  • 遍歷排序後的數組,判斷是否是s的子串
  • 利用雙指針,指針1指向s,指針2指向當前遍歷的字符串,當指針1元素與指針2元素相等,指針同時日後移,當元素不等時,指針1日後移,指針2不動,直到指針2移動到結尾且指針1沒有走完,表示是子串,若指針1走到告終尾,指針2尚未走到結尾,表示未在s中找到當前遍歷的字符串,即不是子串。

代碼實現:

class Solution:
    def findLongestWord(self, s: str, d: List[str]) -> str:
        d.sort(key=lambda x: [-len(x), x]) # 排序,規則:長的靠前,相同長度按照字典順序
        for item in d:
            i = 0
            j = 0
            while i < len(item) and j < len(s): # 判斷某一個串是否到達結尾
                if item[i] == s[j]: # 相同,子串指針加一
                    i += 1
                j += 1 # 主串指針始終加一
            if len(item) == i: # 找到子串,因爲已是有序的即直接返回
                return item
        return ""
相關文章
相關標籤/搜索