LeetCode Hot 100

1 Tree

1.1 Recursion

  • 递归三角: node, node.left, node.right构成一个递归三角,即当前节点,左子树,右子树
  • : 调用递归函数是用来向下递的
  • : return是用来返回给父节点的, 比如return root, return value
  • 边界条件: 用于终止向下递,要考虑空节点的情况,即当前节点,左子树,右子树都为空
  1. 二叉树的前序遍历: https://leetcode.cn/problems/binary-tree-preorder-traversal/
  • 先记录当前节点,再向左子树递,最后向右子树递
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        self.res = []
        def dfs(node):
            if node is None: return
            self.res.append(node.val) # 前序位置
            dfs(node.left)
            dfs(node.right)
        dfs(root)
        return self.res
  1. 二叉树的中序遍历: https://leetcode.cn/problems/binary-tree-inorder-traversal/
  • 先向左子树递,再记录当前节点,最后向右子树递
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        self.res = []
        def dfs(node):
            if node is None: return
            dfs(node.left)
            self.res.append(node.val) # 中序位置
            dfs(node.right)
        dfs(root)
        return self.res
  1. 二叉树的后序遍历: https://leetcode.cn/problems/binary-tree-postorder-traversal/
  • 先向左子树递,再向右子树递,最后记录当前节点
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        self.res = []
        def dfs(node):
            if node is None: return
            dfs(node.left)
            dfs(node.right)
            self.res.append(node.val) # 后序位置
        dfs(root)
        return self.res
  1. 二叉树的最大深度: https://leetcode.cn/problems/maximum-depth-of-binary-tree/
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        if not root: return 0
        l_depth = self.maxDepth(root.left)
        r_depth = self.maxDepth(root.right)
        return max(l_depth, r_depth) + 1
  1. 二叉树的直径: https://leetcode.cn/problems/diameter-of-binary-tree/
  • 两端点一定是叶子结点,因为如果不是叶子结点,则会继续走向叶子结点
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
        self.ans = 0
        def dfs(node):
            if node is None: return -1 # 叶子结点返回0给父节点
            l_len = dfs(node.left)
            r_len = dfs(node.right)
            self.ans = max(self.ans, l_len + r_len + 2)
            return max(l_len, r_len) + 1
        dfs(root)
        return self.ans
  1. 相同的树: https://leetcode.cn/problems/same-tree/
  • p.val和q.val是否相同
  • 子问题:左子树和左子树是否相同,右子树和右子树是否相同
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
        if p is None or q is None:
            return p is q
        return p.val == q.val and self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
  1. 对称二叉树: https://leetcode.cn/problems/symmetric-tree/
  • p.left和q.right比较,p.right和q.left比较
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
        if p is None or q is None:
            return p is q
        return p.val == q.val and self.isSameTree(p.left, q.right) and self.isSameTree(p.right, q.left)

    def isSymmetric(self, root: Optional[TreeNode]) -> bool:
        return self.isSameTree(root.left, root.right)
  1. 翻转二叉树: https://leetcode.cn/problems/invert-binary-tree/
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if not root: return
        left = self.invertTree(root.left)
        right = self.invertTree(root.right)
        root.left, root.right = right, left
        return root

1.2 LevelOrderTraversal

  1. 二叉树的层序遍历: https://leetcode.cn/problems/binary-tree-level-order-traversal/
class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if root is None:
            return []
        ans = []
        from collections import deque
        q = deque([root])
        while q:
            tmp = []
            for _ in range(len(q)): # for loop [], until it's full
                node = q.popleft()
                tmp.append(node.val)
                if node.left: q.append(node.left)
                if node.right: q.append(node.right)
            ans.append(tmp)
        return ans
  1. 二叉树的锯齿形层序遍历: https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal/
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if root is None:
            return []
        ans = []
        from collections import deque
        q = deque([root])
        while q:
            tmp = []
            for _ in range(len(q)): # for loop [], until it's full
                node = q.popleft()
                tmp.append(node.val)
                if node.left: q.append(node.left)
                if node.right: q.append(node.right)
            ans.append(tmp[::-1] if len(ans)%2 else tmp)
        return ans
  1. 找树左下角的值: https://leetcode.com/problems/find-bottom-left-tree-value/
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
        if root is None:
            return
        from collections import deque
        q = deque([root])
        tmp = []
        while q:
            node = q.popleft()
            tmp.append(node.val)
            if node.right: q.append(node.right)
            if node.left: q.append(node.left)
        return tmp[-1]
  1. 二叉树的完全性检验
  • 思路:以BFS遍历,第一次遇到空节点flag=True,如果之后遇到的节点都是空节点,则为完全二叉树
  • 细节: 不需要判断if node.left和if node.left,因为在下一次出队时,无论是None还是非空,都会被判断。如果Node为空,则不会再继续入队,q终将会完全出队(则此时为完全二叉树),或半路打断(则此时为非完全二叉树)。
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def isCompleteTree(self, root: Optional[TreeNode]) -> bool:
        q = deque([root])
        flag = False
        while q:
            node = q.popleft()
            if not node:
                flag = True
            else:
                if flag: return False # flag为true时遇到非空节点, 说明不是完全二叉树
                q.append(node.left) # 无需判断node.left是否为空
                q.append(node.right)
        return True

# Time Complexity: O(n), where n is the total number of nodes in the binary tree.
# Space Complexity: O(n), because in the worst case, the queue can hold all nodes of the tree at one level, and we are storing all nodes in memory.

1.3 BST

BST(Binary Search Tree),二叉搜索树具有左子树 < Root < 右子树的性质。

  1. 将有序数组转换为二叉搜索树: https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
        def dfs(nums, lo, hi):
            if lo > hi: return
        # 以升序数组的中间元素作为根节点 root
            mid = lo + (hi - lo) // 2
            root = TreeNode(nums[mid])
            root.left = dfs(nums, lo, mid - 1)
            root.right = dfs(nums, mid + 1, hi)
            return root
        return dfs(nums, 0, len(nums)-1)
  1. 验证二叉搜索树: https://leetcode.cn/problems/validate-binary-search-tree/

1.4 Common Ancestor

https://leetcode.cn/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/


https://leetcode.cn/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/


2 Sliding Window

  1. 最长无重复子串: https://leetcode.cn/problems/longest-substring-without-repeating-characters/
class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        ans = 0
        cnt = Counter()
        left = 0
        for right, c in enumerate(s): # add the new character to the s, from right
            cnt[c] += 1
            while cnt[c] > 1: # abcb -> bcb -> cb
                cnt[s[left]] -= 1
                left += 1
            ans = max(ans, right - left + 1)
        return ans
  1. 找到字符串中所有字母异位词: https://leetcode.cn/problems/find-all-anagrams-in-a-string
class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        n, m, res = len(s), len(p),[]
        if n < m: return res
        p_cnt = [0] * 26
        s_cnt = [0] * 26
        for i in range(m): # initialize p_cnt
            p_cnt[ord(p[i]) - ord("a")] += 1
            s_cnt[ord(s[i]) - ord("a")] += 1
        if s_cnt == p_cnt:
            res.append(0)

        for i in range(m,n): # sliding window
            s_cnt[ord(s[i-m])-ord("a")] -= 1
            s_cnt[ord(s[i])-ord("a")] += 1
            if s_cnt == p_cnt:
                res.append(i-m+1) # start index
        return res

3 Prefix Sum

  1. 区域和检索: https://leetcode.cn/problems/range-sum-query-immutable/

  1. 和为k的子数组: https://leetcode.cn/problems/subarray-sum-equals-k/
  • 计算前缀和s,注意要初始化一个0
  • 因为s[j] - s[i] = k,所以s[j] - k = s[i]
  • 每次寻找 s[j] 前面有多少个前缀和等于 s[j]−k, 用哈希表记录前缀和的个数,就能 O(1) 算出前面有多少个 s[j]−k
  • 遍历前缀和s, ans += cnt[s[j] - k], cnt[s[i]] += 1,顺序不能颠倒,因为如果k=0,会多记一次
class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        s = [0] * (len(nums) + 1)
        for i, num in enumerate(nums):
            s[i+1] = s[i] + num

        ans = 0
        cnt = Counter()
        for sj in s:
            ans += cnt[sj - k]
            cnt[sj] += 1

        return ans

4 Stack

  1. 有效的括号: https://leetcode.cn/problems/valid-parentheses/
class Solution:
    def isValid(self, s: str) -> bool:
        if len(s)%2 != 0:
            return False
        mp = {"}":"{", ")":"(", "]":"["}
        st = []
        for c in s:
            if c not in mp: # c是左括号
                st.append(c)
            elif not st or st.pop() != mp[c]: # c是右括号
                return False # 没有左括号,或右括号不匹配
        return not st # 所有左括号必须匹配完毕,检查左括号比右括号多的情况,如{{}

5 Linked List

5.1 反转链表

206.反转链表: https://leetcode.cn/problems/reverse-linked-list/

  • 初始化:pre指向null,cur指向head
  • 先暂存head.next,再改变head.next,最后更新head
    • tmp = cur.next
    • cur.next = pre
    • pre = cur
    • cur = tmp
  • 必记性质:反转结束后,pre指向这一段的末尾,cur指向这一段末尾的下一个节点
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        pre, cur = None, head
        while cur:
            tmp = cur.next
            cur.next = pre
            pre = cur
            cur = tmp
        return pre

92.反转链表II: https://leetcode.cn/problems/reverse-linked-list-ii/

5.2 快慢指针

  1. 链表的中间节点: https://leetcode.cn/problems/middle-of-the-linked-list/
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def middleNode(self, head: Optional[ListNode]) -> Optional[ListNode]:
        slow = head
        fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        return slow
  1. 环形链表: https://leetcode.cn/problems/linked-list-cycle/
  • 关注fast和slow的相对运动,相当于fast相对运动一格,如果有环,最终fast肯定会与slow相遇
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        slow = head
        fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if fast is slow:
                return True
        return False
  1. 环形链表II: https://leetcode.cn/problems/linked-list-cycle-ii/
  • 哈希表记录遍历过的节点, 记录大于2表示有环,且为入口
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        visited = Counter()
        while head:
            if visited[head]:
                return head
            visited[head] += 1
            head = head.next
        return None

Follow up: Can you solve it using O(1) (i.e. constant) memory?

  • 最差情况: 当慢指针进入环时,快指针刚好在慢指针前面,根据相对距离分析,快指针需要走(环长-1)步的相对距离才能追上慢指针,其余任何情况快指针走的步数都小于(环长-1),所以慢指针移动的距离是小于环长的。
  • slwo和fast相遇之后:head从出发点出发,slow从相遇点出发,head和slow相遇的位置必定是入口,这是因为设相遇点到入口的距离为c, a - c = (k - 1)(b + c)
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        slow = head
        fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if fast is slow: # 有环
                while slow is not head:
                    slow = slow.next
                    head = head.next
                return slow
        return None
  1. 重排链表: https://leetcode.cn/problems/reorder-list/
  • 找到链表中点,反转链表
  • 先暂存head.next,再改变head.next,最后更新head
    • tmp = head.next
    • tmp2 = head2.next
    • head.next = head2
    • head2.next = tmp
    • head = tmp
    • head2 = tmp2
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def middleNode(self, head):
        slow = head
        fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        return slow

    def reverseList(self, head):
        pre = None
        cur = head
        while cur:
            tmp = cur.next
            cur.next = pre
            pre = cur
            cur = tmp
        return pre

    def reorderList(self, head: Optional[ListNode]) -> None:
        mid = self.middleNode(head)
        head2 = self.reverseList(mid)

        while head2.next:
            tmp = head.next
            tmp2 = head2.next
            head.next = head2
            head2.next = tmp
            head = tmp
            head2 = tmp2
  1. 回文链表: https://leetcode.cn/problems/palindrome-linked-list/
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def middleNode(self, head):
        slow = head
        fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        return slow

    def reverseList(self, head):
        pre = None
        cur = head
        while cur:
            tmp = cur.next
            cur.next = pre
            pre = cur
            cur = tmp
        return pre

    def isPalindrome(self, head: Optional[ListNode]) -> bool:
        if head is None:
            return True

        mid = self.middleNode(head)
        head2 = self.reverseList(mid)
        while head2:
            if head.val != head2.val:
                return False
            head = head.next
            head2 = head2.next
        return True

5.3 删除链表

5.4 前后指针

  1. 相交链表: https://leetcode.cn/problems/intersection-of-two-linked-lists/
  • 初始化: A指向headA,B指向headB,同时开始向前遍历
  • 遍历链表: a + (b - c) = b + (a - c)
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
        A = headA
        B = headB
        while A!=B:
            A = A.next if A else headB
            B = B.next if B else headA
        return A

6 Two Pointers

6.1 相向双指针

  1. 盛最多水的容器: https://leetcode.cn/problems/container-with-most-water/
  • 容器的高度取决于短线,容器的宽度取决于这两条线的距离(下标的差)

7 Dynamic Programing

动态规划五部曲

  1. 确定dp数组以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

7.1 0-1背包

7.2 完全背包

  1. 零钱兑换: https://leetcode.cn/problems/coin-change/

1. 确定dp数组以及下标的含义
dp[j]: 凑齐j金额的最小硬币数
2. 确定递推公式
dp[j] = min(d[j], d[j-coin] + 1)
3. dp数组如何初始化
dp = [float("inf")] * (amount + 1),
dp[0] = 0,表示容量为0的背包,装0个
4. 确定遍历顺序
默认循环外层物品,内层背包,由于是完全背包,不需要考虑物品只能拿一次,所以从前向后遍历dp数组。
所以,coins(物品)放在外循环,target(背包)在内循环。且内循环正序。
5. 举例推导dp数组
以输入:coins = [1, 2, 5], amount = 5为例
dp = [0, inf,inf, inf,inf, inf]
加入1,考虑1,dp = [0, 1, 2, 3, 4, 5]
加入2,考虑1, 2,dp = [0, 1, 1, 2, 2, 3]
加入5,考虑1, 2, 5, dp = [0, 1, 1, 2, 2, 1]

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        dp = [float("inf")] * (amount + 1)
        dp[0] = 0

        for coin in coins:
            for j in range(coin, amount + 1):
                dp[j] = min(dp[j], dp[j - coin] + 1)

        return dp[-1] if dp[-1] != float("inf") else -1 # 如果dp[-1]是inf,说明无法凑齐该金额

7.3 打家劫舍

  1. 打家劫舍: https://leetcode.cn/problems/house-robber/

1. 确定dp数组以及下标的含义
dp[]
2. 确定递推公式
3. dp数组如何初始化
4. 确定遍历顺序
5. 举例推导dp数组

7.4 子序列/子串

  • 子序列(Subsequence): 不连续
  • 子数组(Subarray)和子串(Substring):连续
  1. 最长公共子序列: https://leetcode.cn/problems/longest-consecutive-sequence/
class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        m, n = len(text1), len(text2)
        dp = [[0] * (n+1) for _ in range(m+1)]
        for i, x in enumerate(text1):
            for j, y in enumerate(text2):
                if x == y:
                    dp[i+1][j+1] = dp[i][j] + 1
                else:
                    dp[i+1][j+1] = max(dp[i][j+1], dp[i+1][j])
        return dp[m][n]
  1. 最长连续递增子序列: https://leetcode.cn/problems/longest-consecutive-sequence/
  • 哈希表num_set:通过哈希表存储数组中的所有元素,支持常数时间复杂度的查找操作。

  • 跳过非起点的数字:通过检查 num - 1 是否存在,我们确保每个序列只被完整计算一次,避免了重复计算。

  • 举例推导
    我们以 nums = [100, 4, 200, 1, 3, 2] 为例:

  1. 将数组 nums 转换为集合 set(nums)
  2. 逐个遍历数组:
    • 遍历 100:99 不在集合中,因此它是一个序列的起点。当前序列只有 100,长度为 1。
    • 遍历 4:3 在集合中,因此跳过,不处理(因为 4 已经被包含在以 1 开始的序列中)。
    • 遍历 200:199 不在集合中,因此它是一个序列的起点。当前序列只有 200,长度为 1。
    • 遍历 1:0 不在集合中,因此它是一个序列的起点。检查 2、3、4 都在集合中,最长序列为 [1, 2, 3, 4],长度为 4。

最终最长序列的长度为 4。

class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        if not nums:
            return 0

        nums = set(nums) # 去重
        max_cnt = 0
        for num in nums:
            if num - 1 not in nums: # num没有前一个元素,说明cur是作为连续序列的第一个元素
                cur = num
                cnt = 1
                while cur + 1 in nums: # check the consecutive sequence
                    cur += 1
                    cnt += 1
                max_cnt = max(cnt, max_cnt)
        return max_cnt

383.赎金信: https://leetcode.cn/problems/ransom-note/

class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        hash_l = Counter(ransomNote)
        hash_c = Counter(magazine)
        for l in hash_l:
            if hash_l[l] > hash_c.get(l,0):
                return False
        return True

7.5 股票

posted @ 2024-09-23 13:55  ForHHeart  阅读(4)  评论(0编辑  收藏  举报