leetcode熱題 HOT 100刷題筆記(1)

這里是 HOT 100刷題筆記,篇幅較長(zhǎng),因此分成兩部分,按照題目序號(hào)排列。有幾題沒(méi)做后序會(huì)補(bǔ)上。

  1. 兩數(shù)之和

比較好的辦法就是利用map來(lái)存儲(chǔ)每一個(gè)元素以及其對(duì)應(yīng)的索引,但是這里要考慮到map是無(wú)法存儲(chǔ)重復(fù)的key,因此我們?cè)谂袛嗍欠翊嬖跐M足符合條件的,一個(gè)為原數(shù)組中的索引,另一個(gè)為Map中的索引這個(gè)一定要注意。

解法1:
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        Map = {}
        for i in range(len(nums)):
            Map[nums[i]] = i
        for i in range(len(nums)):
            j = Map.get(target - nums[i])
            if j and j != i:
                return[i, j]
        return []
        
解法2:(將兩個(gè)for循環(huán)合并一下,可以讓程序提前終止,讓Map存儲(chǔ)當(dāng)前元素之前的信息)
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        Map = {}
        for i in range(len(nums)):
            j = Map.get(target - nums[i])
            if j != None :
                return[j, i]
            Map[nums[i]] = i #這一步的順序不能調(diào)換
        return []
  1. 兩數(shù)相加

這一題其實(shí)就是兩個(gè)鏈表相加,這一需要考慮到兩個(gè)鏈表的長(zhǎng)度不一致和進(jìn)位的情況,這一題還有一個(gè)小技巧,就是使用一個(gè)頭節(jié)點(diǎn),這樣代碼寫起來(lái)更方便。

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

class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        s = 0
        head = p = ListNode(0)
        while(l1 or l2 or s):
            s += (l1.val if l1 else 0) + (l2.val if l2 else 0)
            p.next = ListNode(s%10)
            p = p.next
            s = s // 10
            if l1:
                l1 = l1.next
            else:
                l1 = None
            if l2:
                l2 = l2.next
            else:
                l2 = None
        return head.next
  1. 最長(zhǎng)無(wú)重復(fù)子串

經(jīng)典的滑動(dòng)窗口題目,模板題,詳情參考:https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/solution/hua-dong-chuang-kou-tong-yong-si-xiang-jie-jue-zi-/

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        window = {}
        left = 0
        right = 0
        length = len(s)
        res = 0
        while(right < length):
            c = s[right]
            if c not in window:
                window[c] = 1
            else:
                window[c] += 1
            right += 1
            while(window[c] > 1):
                d = s[left]
                left += 1
                window[d] -= 1
            res = max(res, right-left)
        return res
  1. 最長(zhǎng)回文子串

這一題與647回文字串這一題一樣,具體的我就不多說(shuō)了。

class Solution:
    def longestPalindrome(self, s: str) -> str:
        if not s:
            return ''
        length = len(s)
        dp = [[0]*length for i in range(length)]
        for i in range(length):
            for j in range(length):
                if i == j:
                    dp[i][j] = 1
        maxlength = 1
        res = s[0]
        for i in range(length-1, -1, -1):
            for j in range(i+1, length):
                if s[j] == s[i]:
                    if j == i+1:
                        dp[i][j] = 1
                    else:
                        dp[i][j] = dp[i+1][j-1]
                else:
                    dp[i][j] = 0
                if dp[i][j] == 1:
                    if j - i + 1 > maxlength:
                        maxlength = j - i + 1
                        res = s[i:j+1]
        return res
  1. 盛最多水的容器

簡(jiǎn)單的DP:
res = max{min(a[i], a[j])*(j - i)}
采用雙指針進(jìn)行搜索,將a[i]和a[j]中較小的坐標(biāo)進(jìn)行移動(dòng),因?yàn)閷⑤^大的往里進(jìn)行移動(dòng),最終的結(jié)果一定會(huì)變小。

class Solution:
    def maxArea(self, height: List[int]) -> int:
        i = 0
        j = len(height) - 1
        res = -99999999
        while(i <= j):
            res = max(res, min(height[i], height[j])*(j - i))
            if height[i] < height[j]:
                i += 1
            else:
                j -= 1
        return res
            
  1. 三數(shù)之和

參考:https://blog.csdn.net/sd4567855/article/details/86777399 先排序再利用雙指針來(lái)去重。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        res = []
        nums.sort()
        length = len(nums)
        if length < 3 or nums[0] > 0 or nums[-1] < 0 :
            return res
        for i in range(length-2):
            if nums[i] > 0:
                break
            if i > 0 and nums[i-1] == nums[i]:
                continue
            j = i + 1
            k = length - 1
            while(j < k):
                if nums[i] + nums[j] + nums[k] == 0:
                    res.append([nums[i], nums[j], nums[k]])
                    while(j < k and nums[j] == nums[j+1]):
                        j += 1
                    while(j < k and nums[k-1] == nums[k]):
                        k -= 1
                    j += 1
                    k -= 1
                elif nums[i] + nums[j] + nums[k] > 0:
                    k -= 1
                else:
                    j += 1
        return res
  1. 電話號(hào)碼的字母組合

簡(jiǎn)單的DFS即可。

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        if not digits:
            return []
        Dict = {'2':{'a', 'b', 'c'}, 
        '3':{'d', 'e', 'f'}, '4':{'g', 'h', 'i'}, '5':{'j', 'k', 'l'}, 
        '6':{'m', 'n', 'o'}, '7':{'p', 'q', 'r', 's'}, '8':{'t', 'u', 'v'}, 
        '9':{'w', 'x', 'y', 'z'}}
        length = len(digits)
        res = []
        def dfs(route, count):
            if count == length:
                res.append(route)
                return
            for c in Dict[digits[count]]:
                dfs(route + c, count + 1)
        dfs('', 0)
        return res
  1. 刪除鏈表的倒數(shù)第N個(gè)節(jié)點(diǎn)

快慢指針,基本操作,需要注意的是有一種情況是直接刪除頭節(jié)點(diǎn)。

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

class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        if not head:
            return None
        temp = head
        count = 0
        while(temp):
            count += 1
            if count == n:
                break
            temp = temp.next
        
        slow = head
        fast = temp

        pre = None
        while(fast.next):
            fast = fast.next
            pre = slow
            slow = slow.next
        if not pre:
            return head.next
        pre.next = slow.next
        return head
  1. 有效的括號(hào)
  1. 比較直接的方法就是利用python的正則表達(dá)式直接對(duì)這三種括號(hào)進(jìn)行替換。
  2. 借助棧結(jié)構(gòu)來(lái)完成括號(hào)的匹配操作。
解法1:
class Solution:
    def isValid(self, s: str) -> bool:
        while('()' in s or '{}' in s or '[]' in s):
            s = s.replace('()', '')
            s = s.replace('[]', '')
            s = s.replace('{}', '')
        return s == ''

解法2:
class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        for c in s:
            if c == '(' or c == '[' or c == '{':
                stack.append(c)
            else:
                if not stack:
                    return False
                if c == ')':
                    temp = stack.pop()
                    if temp != '(':
                        return False
                elif c == ']':
                    temp = stack.pop()
                    if temp != '[':
                        return False
                elif c == '}':
                    temp = stack.pop()
                    if temp != '{':
                        return False
        return stack == []
  1. 合并兩個(gè)有序鏈表

正常的鏈表遍歷操作,注意在合并鏈表的時(shí)候,除了要保存頭節(jié)點(diǎn)以外,還需要用一個(gè)指針來(lái)記錄鏈表當(dāng)前節(jié)點(diǎn)的位置。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        if not l1:
            return l2
        if not l2:
            return l1
        head1 = l1
        head2 = l2
        head3 = None
        temp = None
        if head1.val <= head2.val:
            head3 = head1
            temp = head3
            head1 = head1.next
        else:
            head3 = head2
            temp = head3
            head2 = head2.next
        while(head1 and head2):
            if head1.val <= head2.val:
                temp.next = head1
                temp = head1
                head1 = head1.next
            else:
                temp.next = head2
                temp = head2
                head2 = head2.next
        while(head1):
            temp.next = head1
            temp = head1
            head1 = head1.next
        while(head2):
            temp.next = head2
            temp = head2
            head2 = head2.next
        return head3
  1. 括號(hào)生成

https://leetcode-cn.com/problems/generate-parentheses/solution/hui-su-suan-fa-by-liweiwei1419/
這一題使用遞歸比較符合正常的想法,同樣需要設(shè)計(jì)好遞歸函數(shù)的參數(shù),這里使用route,left,right這三個(gè)參數(shù),分別表示存儲(chǔ)的路徑,左括號(hào)還剩余的數(shù)量,右括號(hào)還剩余的數(shù)量,該遞歸函數(shù)沒(méi)有返回值,使用全局變量來(lái)存儲(chǔ)不同的路徑。
當(dāng)左右剩余的括號(hào)數(shù)量為0的時(shí)候,表明找到了滿足題意的路徑。
當(dāng)剩余的左括號(hào)數(shù)量大于剩余的右括號(hào)數(shù)量的時(shí)候,程序終止,因?yàn)檫@樣生成的路徑一點(diǎn)不滿足題意。
左右剩余的括號(hào)數(shù)量大于0的時(shí)候,才進(jìn)行下一次擴(kuò)展。

class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        res = []

        def dfs(route, left, right):
            if left == 0 and right == 0:
                res.append(route)
                return
            if left > right:
                return
            if left > 0:
                dfs(route + '(', left - 1, right)
            if right > 0:
                dfs(route + ')', left, right - 1)

        dfs('', n, n)
        return res
  1. 合并K個(gè)排序鏈表

比較直觀的做法,直接利用分治的方法,接下來(lái)的重點(diǎn)就是如何設(shè)計(jì)好遞歸函數(shù)的參數(shù)了。
遞歸函數(shù)的參數(shù)還是借用二分的寫法,采用左右邊界來(lái)確定鏈表的個(gè)數(shù)。
也可以通過(guò)建立一個(gè)堆來(lái)合并這k個(gè)鏈表。

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

class Solution:
    def mergeKLists(self, lists: List[ListNode]) -> ListNode:

        def merge(l1, l2):
            p = ListNode()
            temp = p
            while(l1 and l2):
                if l1.val < l2.val:
                    temp.next = l1
                    l1 = l1.next
                else:
                    temp.next = l2
                    l2 = l2.next
                temp = temp.next
            if l1 :
                temp.next = l1
            else:
                temp.next = l2
            return p.next
    
        def recursion(l, r):
            if l == r:
                return lists[l]
            if l > r:
                return
            mid = (l + r) // 2
            left = recursion(l, mid)
            right = recursion(mid + 1, r)
            return merge(left, right)
        
        return recursion(0, len(lists) - 1)

解法2:
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
from heapq import heappush, heappop

class MyNode:
    def __init__(self, node):
        self.node = node
    
    def __lt__(self, other):
        if self.node.val <= other.node.val:
            return True
        else: return False
    
    def get(self):
        return self.node


class Solution:
    def mergeKLists(self, lists: List[ListNode]) -> ListNode:
        heap = []
        for l in lists:
            if l:
                heappush(heap, MyNode(l))
        
        p = head = ListNode(0)
        while heap:
            node = heappop(heap)
            p.next = node.get()
            p = p.next
            if node.get().next:
                heappush(heap, MyNode(node.get().next))
        return head.next
  1. 下一個(gè)排列

求下一個(gè)排列有著比較標(biāo)準(zhǔn)的算法,這個(gè)在組合數(shù)學(xué)上都有專門講過(guò),C++中STL也專門封裝了這個(gè)方法。算法分為三步:

  1. 從后往前搜,找到第一個(gè)跳變:要求后一個(gè)大于前一個(gè),如果沒(méi)有說(shuō)明當(dāng)前排列已經(jīng)是最大的了。
  2. 通過(guò)前面的跳變查找將數(shù)組分成了前后兩個(gè)部分,在后面部分的數(shù)組中找到第一個(gè)大于跳變的那個(gè)數(shù),進(jìn)行交換。
  3. 將后面部分的數(shù)組按照從小到達(dá)的順序進(jìn)行交換。
    語(yǔ)言表述可能不是很清楚,參考:https://leetcode-cn.com/problems/next-permutation/solution/xia-yi-ge-pai-lie-suan-fa-xiang-jie-si-lu-tui-dao-/
class Solution:
    def nextPermutation(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        length = len(nums) - 1
        i = length
        while(i >= 1 and nums[i-1] >= nums[i]):
            i -= 1
        if i == 0:
            nums.reverse()
            return nums
        j = length
        
        while(j >= 0 and nums[j] <= nums[i-1]):
            j -= 1
        temp = nums[j]
        nums[j] = nums[i-1]
        nums[i-1] = temp
        
        res = nums[i:]
        for i in range(len(res)):
            nums[length-i] = res[i]
        return nums
  1. 最長(zhǎng)有效括號(hào)

直觀上來(lái)看需要用到棧這樣的結(jié)構(gòu)。需要記錄啥時(shí)候入棧,啥時(shí)候會(huì)出棧。當(dāng)棧為空的時(shí)候,當(dāng)前元素為'('或者當(dāng)前棧頂元素為')'入棧,否則出棧,入棧的是字符的索引,這樣方便計(jì)算出括號(hào)的長(zhǎng)度。

class Solution:
    def longestValidParentheses(self, s: str) -> int:
        if s == "":
            return 0
        stack = []
        res = 0
        for index, c in enumerate(s):
            if not stack or c == '(' or s[stack[-1]] == ')': #注意細(xì)節(jié)
                stack.append(index)
            else:
                stack.pop()
                temp = stack[-1] if stack else -1 #注意細(xì)節(jié)
                res = max(res, index - temp )
        return  res
  1. 搜索旋轉(zhuǎn)排序數(shù)組

那這一題顯然是要用二分查找,但是這一題卻是局部有序,因此我們需要在原始的二分查找上面進(jìn)行一些改動(dòng),一半來(lái)說(shuō)二分查找的難點(diǎn)在于邊界條件。首先需要判斷元素是在左邊部分還是右邊部分,然后對(duì)于左邊部分和右邊部分需要各自判斷其的左右部分。

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left = 0
        right = len(nums) - 1
        while(left <= right):
            mid = (left + right) // 2
            if nums[mid] == target:
                return mid
            if nums[mid] >= nums[left]:
                if nums[mid] > target and target >= nums[left]:
                    right = mid - 1
                else:
                    left = mid + 1
            else:
                if nums[mid] < target and target <= nums[right]:
                    left = mid + 1
                else:
                    right = mid - 1
        return -1
  1. 在排序數(shù)組中查找元素的第一個(gè)和最后一個(gè)位置

二分法查找左邊界和右邊界,二分法的細(xì)節(jié)太難寫了,開區(qū)間還是閉區(qū)間,等于還是不等于都需要注意。這里還有一種寫法來(lái)求左右邊界,如果有那么就一直向左移動(dòng)或者是向右移動(dòng),沒(méi)有的話就返回-1。

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        def serachLeft(nums):
            if not nums:
                return -1
            left = 0
            right = len(nums) - 1
            while(left <= right):
                mid = (left + right) // 2
                if nums[mid] == target: #要求找最左邊,因此更新右邊界
                    right = mid - 1
                elif nums[mid] > target:
                    right = mid - 1
                else:
                    left = mid + 1
            if left <= len(nums) - 1 and nums[left] == target: #注意
                return left
            else:
                return -1
        def searchRight(nums):
            if not nums:
                return -1
            left = 0
            right = len(nums) - 1
            while(left <= right):
                mid = (left + right) // 2
                if nums[mid] == target: #要求找最右邊,因此更新左邊界
                    left = mid + 1
                elif nums[mid] > target:
                    right = mid - 1
                else:
                    left = mid + 1
            if right >= 0 and nums[right] == target: #注意
                return right
            else:
                return -1
        return [serachLeft(nums), searchRight(nums)]

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        def search_left(nums):
            l = 0
            r = len(nums)-1
            while(l <= r):
                mid = (l+r) // 2
                if nums[mid] == target:
                    left = mid
                    right = mid
                    while(left>=1):
                        if nums[left] == nums[left-1]:
                            left = left - 1
                        else:
                            break
                    while(right <= len(nums)-2):
                        if nums[right] == nums[right+1]:
                            right = right + 1
                        else:
                            break
                    return [left, right]
                elif nums[mid] > target:
                    r = mid - 1
                else:
                    l = mid + 1
            return [-1, -1]
        return search_left(nums)

最后一種寫法:
class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        def left_search(nums):
            l = 0 
            r = len(nums) - 1
            while(l <= r):
                mid = (l + r) // 2
                if nums[mid] ==  target:
                    r = mid - 1
                elif nums[mid] > target:
                    r = mid - 1
                else:
                    l = mid + 1
            if l == len(nums) or nums[l] != target:
                return -1
            return l
        
        def right_searh(nums):
            l = 0
            r = len(nums) - 1
            while(l <= r):
                mid = (l + r) // 2
                if nums[mid] == target:
                    l = mid + 1
                elif nums[mid] > target:
                    r = mid - 1
                else:
                    l = mid + 1
            if r < 0 or nums[r] != target:
                return -1
            return r
        l = left_search(nums)
        r = right_searh(nums)
        return [l, r]
  1. 組合總和

標(biāo)準(zhǔn)的DFS,需要設(shè)計(jì)好遞歸函數(shù)的參數(shù)。基本的遍歷和剪枝都很簡(jiǎn)單,關(guān)鍵是如何去重,這個(gè)跟以前的DFS有區(qū)別,不能用標(biāo)記來(lái)記錄走過(guò)的路徑,因?yàn)檫@里的數(shù)字可以重復(fù)選取。解法里給了一個(gè)索引來(lái)記錄當(dāng)前節(jié)點(diǎn)的順序,在下一次的遍歷中從這個(gè)節(jié)點(diǎn)開始,這樣就保證了數(shù)字可以重復(fù)選取,也避免了路徑的重復(fù)。

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        
        res = []
        def dfs(i, sumNum, route):
            if sumNum == 0:
                res.append(route)
                return
            for index in range(i, len(candidates)):
                if sumNum >= candidates[index]:
                    dfs(index, sumNum - candidates[index], route + [candidates[index]])
        dfs(0, target, [])
        return res
  1. 接雨水

這一題本質(zhì)上是要求一個(gè)定值,第一眼看到這題我也沒(méi)啥好的思路,看了題解才知道要用單調(diào)遞減棧。需要使用棧是因?yàn)樾碌陌疾圩筮吔缫欢ㄊ亲钚掠龅降?,而要使用遞減棧是因?yàn)楫?dāng)?shù)偷挠龅礁叩牟艜?huì)相應(yīng)的凹槽。在具體實(shí)現(xiàn)上這一題有很多細(xì)節(jié),首先我們?nèi)霔J窃氐南聵?biāo),因?yàn)楹竺嫖覀冃枰褂孟聵?biāo)來(lái)求寬度。當(dāng)前棧不為空且當(dāng)前元素大于棧頂則出棧。在計(jì)算凹槽能存儲(chǔ)的雨水時(shí),需要當(dāng)前元素,前一個(gè)元素,后面一個(gè)元素來(lái)共同計(jì)算,當(dāng)沒(méi)有前一個(gè)元素的時(shí)候,直接跳出,計(jì)算凹槽的寬度沒(méi)啥難度,但是計(jì)算凹槽的高度的時(shí)候需要注意:前后兩個(gè)高度取較低的再減去中間這個(gè)最低的才能得到相對(duì)高度差。

class Solution:
    def trap(self, height: List[int]) -> int:
        ans = 0
        stack = []
        for i in range(len(height)):
            while(stack and height[stack[-1]] < height[i]):
                temp = stack.pop()#出棧
                if not stack:#沒(méi)有前一個(gè)元素直接跳出
                    break
                left = stack[-1]
                right = i
                width = right - left -1
                length = min(height[left], height[right]) - height[temp]
                ans += width*length
            stack.append(i)#下標(biāo)入棧
        return ans
  1. 全排列

標(biāo)準(zhǔn)的排列做法。這里需要注意一下python中傳遞列表這一類數(shù)據(jù)的時(shí)候

解法1:(錯(cuò)誤)
class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        length = len(nums)
        res = []
        route = [0]*length
        flags = [0]*length
        def dfs(count):
            if count == length:
                print(route)
                res.append(route)
                return
            for i in range(length):
                if flags[i] == 0:
                    flags[i] = 1
                    route[count] = nums[i]
                    dfs(count + 1)
                    flags[i] = 0

        dfs(0)
        return res

解法2:(對(duì)傳遞的列表進(jìn)行復(fù)制)
class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        length = len(nums)
        res = []
        route = [0]*length
        flags = [0]*length
        def dfs(count):
            if count == length:
                print(route)
                res.append(route[:])
                return
            for i in range(length):
                if flags[i] == 0:
                    flags[i] = 1
                    route[count] = nums[i]
                    dfs(count + 1)
                    flags[i] = 0

        dfs(0)
        return res

解法3:(將存儲(chǔ)路徑的變量采用參數(shù)的形式來(lái)傳遞)
class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        length = len(nums)
        res = []
        flags = [0]*length
        def dfs(count, route):
            if count == length:
                res.append(route)
                return
            for i in range(length):
                if flags[i] == 0:
                    flags[i] = 1
                    dfs(count + 1, route + [nums[i]])
                    flags[i] = 0

        dfs(0, [])
        return res
  1. 旋轉(zhuǎn)圖像

先將矩陣進(jìn)行轉(zhuǎn)置,再將每一行反轉(zhuǎn)即可。

class Solution:
    def rotate(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        """
        for i in range(len(matrix)):
            for j in range(i):
                matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
        for i in range(len(matrix)):
            matrix[i].reverse()
  1. 字母異位詞分組

比較好的辦法就是利用字典,但是我們需要區(qū)分好鍵值,這里使用排序函數(shù)來(lái)進(jìn)行區(qū)分。

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        Dict = {}
        for s in strs:
            key = ''.join(sorted(s))
            if key not in Dict:
                Dict[key] = []
            Dict[key].append(s)
        return list(Dict.values())
  1. 最大子序和

簡(jiǎn)答的DP。

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        dp = [0]*len(nums)
        dp[0] = nums[0]
        res = nums[0]
        for i in range(1, len(nums)):
            if dp[i-1] < 0:
                dp[i] = nums[i]
            else:
                dp[i] = dp[i-1] + nums[i]
            res = max(res, dp[i])
        return res
  1. 跳躍游戲

這一題需要用動(dòng)態(tài)規(guī)劃,k表示到達(dá)第i步時(shí),能夠走的最遠(yuǎn)距離。如果k<i那么就返回False,每次對(duì)k進(jìn)行更新 k = max(k, i+nums[i])

解法1:超時(shí)
class Solution:
    def canJump(self, nums: List[int]) -> bool:
        length = len(nums)
        flags = [0]*length
        flags[0] = 1
        for i in range(length-1):
            if flags[i] == 1:
                for j in range(i, min(i+nums[i]+1, length)):
                    flags[j] = 1
                    if flags[-1] == 1:
                        return True
        if flags[-1] == 1:
            return True
        else:
            return False

解法2:
k表示在在當(dāng)前位置能夠走到的最遠(yuǎn)距離。
class Solution:
    def canJump(self, nums: List[int]) -> bool:
        length = len(nums)
        k = 0
        for i in range(length):
            if i > k:
                return False
            k = max(k, i + nums[i])
        return True
  1. 合并區(qū)間

這個(gè)題目從暴力上是沒(méi)法解決的,暴力的復(fù)雜度是n!,只能通過(guò)排序的手段來(lái)降低時(shí)間復(fù)雜度。注意cmp_to_key這個(gè)關(guān)鍵字需要引入functools包。
按照貪心的策略來(lái)進(jìn)行排序,優(yōu)先按照左端從小到大進(jìn)行排序,如果左端相同則將右端較大的放在前面,如果前一個(gè)區(qū)間的右端點(diǎn)小于后一個(gè)區(qū)間的左端點(diǎn),那么這兩個(gè)區(qū)間不會(huì)重合,否則兩個(gè)重復(fù),新區(qū)間的右端點(diǎn)為這兩個(gè)區(qū)間有邊界的較大值。
改正,這個(gè)題目只需要對(duì)左邊界排序即可,其他的不用管。

class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        if not intervals:
            return []
        def compare(a, b):
            if a[0] > b[0] or (a[0] == b[0] and a[1] < b[1]):
                return 1
            elif a[0] == b[0] and a[1] == b[1]:
                return 0
            else:
                return -1
        intervals.sort(key = cmp_to_key(compare))
        res = [intervals[0]]
        for left, right in intervals[1:]:
            temp = res[-1]
            if left > temp[1]: #注意是嚴(yán)格大于
                res.append([left, right])
            else:
                res[-1][1] = max(right, temp[1])
        return res

  1. 不同路徑

第一眼看上去像是簡(jiǎn)單的迷宮搜索問(wèn)題,可以直接嘗試暴力搜索,結(jié)果超時(shí),這里我給了兩種寫法,第一種使用了標(biāo)記數(shù)組,后面發(fā)現(xiàn)是沒(méi)有必要的,因?yàn)楦鶕?jù)題意是絕對(duì)不會(huì)往回走的?;剡^(guò)頭來(lái)仔細(xì)看一下,這個(gè)題其實(shí)就是組合數(shù)的定義,或者我們也可以使用標(biāo)準(zhǔn)的DP來(lái)解決:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
需要注意初始化的問(wèn)題,不然結(jié)果全為0。

解法1:超時(shí)
class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        self.count = 0
        flags = [[0]*m for i in range(n)]
        def dfs(x, y):
            if x == n-1 and y == m-1:
                self.count += 1
            if x <0 or x >n-1 or y < 0 or y > m-1:
                return
            for (delta_x, delta_y) in [(0, 1), (1, 0)]:
                if x+delta_x < 0 or x+delta_x > n-1 or y+delta_y <0 or y+delta_y > m-1:
                    continue
                if flags[x+delta_x][y+delta_y] == 0:
                    flags[x+delta_x][y+delta_y] = 1
                    dfs(x+delta_x, y+delta_y)
                    flags[x+delta_x][y+delta_y] = 0
        flags[0][0] = 1
        dfs(0, 0)
        return self.count

解法2:超時(shí)
class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        self.count = 0
        def dfs(x, y):
            if x == n-1 and y == m-1:
                self.count += 1
            if x <0 or x >n-1 or y < 0 or y > m-1:
                return
            dfs(x+1, y)
            dfs(x, y+1)
        dfs(0, 0)
        return self.count

解法3:DP
class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        dp = [[0]*(m + 2) for i in range(n+2)]
        for i in range(1, n+1):
            for j in range(1, m+1):
                if i == 1 and j == 1:
                    dp[i][j] = 1
                else:
                    dp[i][j] = dp[i-1][j] + dp[i][j-1]
        return dp[n][m]
  1. 最小路徑和

同樣這一題是非常基礎(chǔ)的DP,按照我之前的寫法將數(shù)組外圍擴(kuò)充一下,但是這種寫法不能填充0,應(yīng)該填充一個(gè)很大的值。這個(gè)一定要注意。

class Solution:
    def minPathSum(self, grid: List[List[int]]) -> int:
        width = len(grid)
        length = len(grid[0])
        dp = [[0]*length for i in range(width)]
        for i in range(0, width):
            for j in range(0, length):
                if i == 0 and j == 0:
                    dp[i][j] = grid[i][j]
                elif i == 0 and j != 0:
                    dp[i][j] =  dp[i][j-1] + grid[i][j]
                elif i !=0 and j == 0:
                    dp[i][j] = dp[i-1][j] + grid[i][j]
                else: 
                    dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
        return dp[-1][-1]
  1. 爬樓梯

斐波拉契數(shù)列,注意初始值。

class Solution:
    def climbStairs(self, n: int) -> int:
        if n == 1:
            return 1
        dp = [0]*n
        dp[0] = 1
        dp[1] = 2
        for i in range(2, n):
            dp[i] = dp[i-1] + dp[i-2]
        return dp[n-1]
  1. 編輯距離

這一題也是經(jīng)典的DP,這一題與求兩個(gè)序列的最長(zhǎng)公共子序列這一題類似,同樣是二階DP。
假設(shè)現(xiàn)在有兩個(gè)字符串a(chǎn),b,dp[i][j]表示a[:i]和b[:j]這兩個(gè)字串的最短編輯距離,那么我們可以得到相應(yīng)的狀態(tài)轉(zhuǎn)移方程:

f(x)=\left\{ \begin{aligned} dp[i][j] = & dp[i-1][j-1] (a[i]=b[j]) \\ dp[i][j] = & 1 + min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) (a[i]!=b[j]) \end{aligned} \right.

a[i]=b[j]的時(shí)候很好理解,當(dāng)a[i]!=b[j]的時(shí)候,我們以a為例,我們可以將a[i]修改成和b[j]一樣的,這個(gè)時(shí)候就轉(zhuǎn)換成求dp[i-1][j-1]了,我們也可以直接將a[i]刪除,但是刪除a[i]之后,我們也無(wú)法去頂a[i-1]和b[j]是否相等,這個(gè)時(shí)候問(wèn)題轉(zhuǎn)換成求dp[i-1][j],我們也可以在a[i]后面增加一個(gè)和b[j]一樣的元素,這個(gè)時(shí)候a[i+1]和b[j]是一樣的,然后問(wèn)題就轉(zhuǎn)換成dp[i][j-1]了。在具體實(shí)現(xiàn)的時(shí)候,我們需要添加一個(gè)空串,因?yàn)檫@樣容易初始化,因此代碼中會(huì)有相應(yīng)的調(diào)整。

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        length1 = len(word1) + 1
        length2 = len(word2) + 1
        #print(length1, length2)
        dp = [[0]*length1 for i in range(length2)]
        for i in range(length2):
            dp[i][0] = i
        for j in range(length1):
            dp[0][j] = j
        for i in range(1, length2):
            for j in range(1, length1):
                if word2[i-1] == word1[j-1]:
                    dp[i][j] = dp[i-1][j-1]
                else:
                    #print(i, j)
                    dp[i][j] = 1 + min(dp[i-1][j-1], min(dp[i-1][j], dp[i][j-1]))
        return dp[-1][-1]

  1. 顏色分類

三色國(guó)旗問(wèn)題。官方題解寫的挺好的:https://leetcode-cn.com/problems/sort-colors/solution/yan-se-fen-lei-by-leetcode/ ,關(guān)于這個(gè)算法其實(shí)也比較好理解。p0表示左邊界,p2表示右邊界,cur表示遍歷的當(dāng)前元素,因?yàn)樽罱K我們需要讓整個(gè)數(shù)組變成遞增的形式,當(dāng)nums[cur] = 2的時(shí)候,就需要和nums[p2]進(jìn)行交換,并且將p2左移,當(dāng)nums[cur] = 0的時(shí)候,就需要和nums[p1]進(jìn)行交換,并且cur和p1都要右移,當(dāng)nums[cur] = 1的時(shí)候,只需要cur右移即可。

class Solution:
    def sortColors(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        p0 = cur = 0
        p2 = len(nums) - 1
        while(cur <= p2):
            if nums[cur] == 2:
                nums[cur], nums[p2] = nums[p2], nums[cur]
                p2 -= 1
            elif nums[cur] == 0:
                nums[cur], nums[p0] = nums[p0], nums[cur]
                cur += 1
                p0 += 1
            else:
                cur += 1
        
  1. 最小覆蓋子串

同樣是經(jīng)典的滑動(dòng)窗口題目。需要維護(hù)兩個(gè)字典,和兩個(gè)指針。但是這一題需要子串覆蓋,因此窗口收縮的條件更為嚴(yán)格,不能簡(jiǎn)單的通過(guò)雙指針之間的距離大于等于目標(biāo)串的長(zhǎng)度就來(lái)進(jìn)行窗口收縮操作,這樣會(huì)增加時(shí)間復(fù)雜度。

class Solution:
    def minWindow(self, s: str, t: str) -> str:
        window = {}
        need = {}
        left = 0
        right = 0
        valid = 0
        maxlength = inf
        temp = 0
        for c in t:
            if c not in need:
                need[c] = 1
            else:
                need[c] += 1
        while(right < len(s)):
            c = s[right]
            right += 1
            if c not in window:
                window[c] = 1
            else:
                window[c] += 1
            if c in need:
                if window[c] == need[c]:
                    valid += 1
            while(valid == len(need)): #這個(gè)收縮條件更為嚴(yán)格
                if right - left < maxlength:
                    maxlength = right - left
                    temp = left
                c = s[left]
                left += 1
                if c in need:
                    if need[c] == window[c]:
                        valid -= 1
                    window[c] -= 1
        if maxlength == inf:
            return ''
        else:
            return s[temp:temp+maxlength]

  1. 子集

類似于生成全排列,用一個(gè)深度優(yōu)先搜索最為直接。具體的實(shí)現(xiàn)的時(shí)候需要注意終止條件,這里不需要使用標(biāo)記數(shù)組來(lái)防止重復(fù)遍歷。

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        length = len(nums)
        
        res = []
        def dfs(count, route):
            if count == length:
                res.append(route)
                return
            
            for i in range(2):
                if i == 1:
                    dfs(count + 1, route + [nums[count]])
                else:
                    dfs(count + 1, route)
                    
        dfs(0, [])
        return res
  1. 單詞搜索

同樣是DFS,但是這個(gè)DFS搜索稍微有點(diǎn)復(fù)雜。遞歸函數(shù)參數(shù)有3個(gè),分別表示橫縱坐標(biāo)和走過(guò)的步數(shù),由于這個(gè)題需要從所有可能的路徑中找出一條滿足題意的路徑,因此這一題需要采用for循環(huán)的寫法:用flags標(biāo)記數(shù)組來(lái)在防止同一條路徑上重復(fù)遍歷,并且還要保證不同的路徑之間互不影響。在進(jìn)行下一步遞歸的時(shí)候需要保證不越界、未走過(guò)、且保證當(dāng)前遍歷的元素與word中相同位置的元素相同這三個(gè)條件。最后需要注意的是,這一題需要剪枝,也就是當(dāng)找到一條滿足題意的路徑的時(shí)候,需要立刻將程序終止,否則會(huì)超時(shí)。

class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        length = len(board)
        width = len(board[0])
        flags = [[0]*width for i in range(length)]
        self.res = 0
        def dfs(x, y, count):
            if count == len(word):
                self.res = 1
                return
            for delta_x, delta_y in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
                if self.res == 1:
                    return
                if x + delta_x >= 0 and x + delta_x <= length -1 and y + delta_y >= 0 and y + delta_y <= width - 1 and flags[x + delta_x][y + delta_y] == 0 and board[x+delta_x][y+delta_y] == word[count]:
                    flags[x + delta_x][y + delta_y] = 1
                    dfs(x + delta_x, y + delta_y, count + 1)
                    flags[x + delta_x][y + delta_y] = 0

        for i in range(length):
            for j in range(width):
                if board[i][j] == word[0]:
                    flags[i][j] = 1
                    dfs(i, j, 1)
                    flags[i][j] = 0
                    if self.res == 1:
                        return True
        return False
  1. 柱狀圖中最大的矩形

這一題與接雨水這一題神似,不同的是,接雨水那一題需要碰到更高的才會(huì)形成相應(yīng)的凹槽,而這一題是遇到更低的柱形之后,后面會(huì)有凹槽這樣無(wú)法形成對(duì)應(yīng)的矩形,只能計(jì)算前面的矩形了。因此這一題也需要使用到單調(diào)棧,單調(diào)不減的棧,這一同樣需要把元素索引入棧,因?yàn)樾枰?jì)算對(duì)應(yīng)的寬度,在計(jì)算寬度的時(shí)候還需要考慮前面是否有柱形,還有一個(gè)技巧就是在最后加上一個(gè)高度為0的柱形,計(jì)算寬度需要注意下,右邊界為當(dāng)前元素的位置,左邊界為出棧后的棧頂,沒(méi)有的話就為空。參考:https://blog.csdn.net/Zolewit/article/details/88863970

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        heights.append(0) #注意這一步
        stack = []
        res = 0
        for i in range(len(heights)):
            while(stack and heights[i] < heights[stack[-1]]):
                mid = stack.pop()
                right = i #注意這一步
                if not stack:
                    width = right
                else:
                    left = stack[-1] #注意這一步
                    width = right - left - 1
                res = max(res, width*heights[mid])
            stack.append(i)
        return res
  1. 二叉樹的中序遍歷

題目要求使用非遞歸的方式來(lái)完成。這就需要定義一個(gè)棧來(lái)模擬系統(tǒng)棧的壓棧、彈棧過(guò)程。
在模擬中序遍歷的時(shí)候,同樣需要讓左子樹不斷入棧,直到最左邊即可。若當(dāng)前節(jié)點(diǎn)為空,則出棧并獲取棧頂元素的值,并將當(dāng)前元素更新到其右子樹的狀態(tài)。整個(gè)循環(huán)終止的條件為當(dāng)前節(jié)點(diǎn)為空且棧也為空。

詳細(xì)情況請(qǐng)參考:
https://leetcode-cn.com/problems/binary-tree-inorder-traversal/solution/die-dai-fa-by-jason-2/

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        stack = []
        cur = root
        while(cur or stack):
            while(cur):
                stack.append(cur)
                cur = cur.left
            cur = stack.pop()
            res.append(cur.val)
            cur = cur.right
        return res

  1. 不同的二叉搜索樹

簡(jiǎn)單的DP,dp[i]表示i個(gè)節(jié)點(diǎn)的樹能夠構(gòu)成二叉搜索的種類:
dp[n] = dp[0]*dp[n-1] + ... + dp[n-1]*dp[0]
如果這一題是普通的二叉樹的話,就需要再乘以n!。

class Solution:
    def numTrees(self, n: int) -> int:
        dp = [0]*(n + 1)
        dp[0] = 1
        dp[1] = 1
        for i in range(2, n + 1):
            for j in range(0, i):
                dp[i] += dp[j]*dp[i-1-j]
        return dp[n]
  1. 驗(yàn)證二叉搜索樹

簡(jiǎn)單的遞歸函數(shù)即可,同樣需要設(shè)計(jì)好參數(shù)和返回值,遞歸函數(shù)其實(shí)比較簡(jiǎn)單,違反二叉樹的性質(zhì)就返回False,當(dāng)樹遍歷到空節(jié)點(diǎn)的時(shí)候返回True,因?yàn)檫@個(gè)時(shí)候表示在遍歷過(guò)程中沒(méi)有出現(xiàn)任何違反二叉搜索樹性質(zhì)的情況,最后當(dāng)所有的條件都滿足的時(shí)候,返回True即可。但是有一點(diǎn)需要注意,就是右子樹的每一個(gè)節(jié)點(diǎn)都要大于根節(jié)點(diǎn),左子樹的每一個(gè)節(jié)點(diǎn)都要小于根節(jié)點(diǎn),因此單獨(dú)地判斷每一個(gè)根節(jié)點(diǎn)和其左右子節(jié)點(diǎn)的大小關(guān)系是不夠的。這里第一種解法就是這種錯(cuò)誤的解法。

解法1:錯(cuò)誤的解法
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isValidBST(self, root: TreeNode) -> bool:
        if not root:
            return True
        if root.left:
            if root.left.val >= root.val:
                return False
        if root.right:
            if root.right.val <= root.val:
                return False
        left = self.isValidBST(root.left)
        right = self.isValidBST(root.right)
        if not left or  not right:
            return False
        else:
            return True

解法2:在遞歸函數(shù)的參數(shù)里設(shè)置上下界,lower和upper,注意是開區(qū)間:
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isValidBST(self, root: TreeNode) -> bool:
        minNum = -9999999999
        maxNum = 9999999999
        def dfs(root, minNum, maxNum):
            if not root:
                return True
            if root.val <= minNum or root.val >= maxNum:
                return False
            left = dfs(root.left, minNum, root.val)
            right = dfs(root.right, root.val, maxNum)
            if not left or not right:
                return False
            else:
                return True
        return dfs(root, minNum, maxNum)
  1. 對(duì)稱二叉樹
  1. 有一種最笨的辦法就是直接將這棵樹進(jìn)行交換然后再遍歷,如果遍歷的結(jié)果相同,那么說(shuō)明這是一顆對(duì)稱樹,這里強(qiáng)調(diào)一下,應(yīng)該使用先序遍歷,使用中序遍歷的話有些數(shù)據(jù)過(guò)不了。
  2. 建立一個(gè)合理的遞歸關(guān)系,直接對(duì)左右子樹進(jìn)行判斷,不滿足相應(yīng)的條件就返回False,滿足相應(yīng)的條件就返回True,但是要注意的一點(diǎn)是根節(jié)點(diǎn)為空需要單獨(dú)判讀,不能放在遞歸函數(shù)里,遞歸函數(shù)里的參數(shù)為兩個(gè)表示左右子樹。
解法1:
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        def swap(root):
            if not root:
                return None
            newRoot = TreeNode()
            
            temp1 = root.left
            temp2 = root.right
            root.left = swap(temp2)
            root.right = swap(temp1)
            '''
            newRoot.val = root.val
            newRoot.left = swap(root.right)
            newRoot.right = swap(root.left)
            return newRoot
            '''
            return root

        def dfs(root):
            if not root:
                return ['null']
            else:
                left = dfs(root.left)
                right = dfs(root.right)
                return  [root.val] + left + right

        res1 = dfs(root)
        newNode = swap(root)
        res2 = dfs(newNode)

        if res1 == res2:
            return True
        else:
            return False

解法2:
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        def judge(left, right):
            if not left and not right:
                return True
            if not left:
                return False
            if not right:
                return False
            if left.val != right.val:
                return False
            return judge(left.left, right.right) and judge(left.right, right.left)
            
        if not root:
            return True
        if judge(root.left, root.right):
            return True
        else:
            return False
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容