【中级算法】数组/字符串/链表/树/图 (上)

目录

一、数组和字符串

1.1 LC 三数之和

1.1.1 题求

1.1.2 求解

1.2 LC 矩阵置零

1.2.1 题求

1.2.2 求解

1.3 LC 字母异位词分组

1.3.1 题求

1.3.2 求解

1.4 LC 无重复字符的最长子串

1.4.1 题求

1.4.2 求解

1.5 LC 最长回文子串

1.5.1 题求

1.5.2 求解

1.6 LC 递增的三元子序列 ☆

1.6.1 题求

1.6.2 求解

二、链表

2.1 LC 两数相加

2.1.1 题求

2.1.2 求解

2.2 LC 奇偶链表

2.2.1 题求

2.2.2 求解

2.3 相交链表

2.3.1 题求

2.3.2 求解

三、树和图

3.1 LC 二叉树的中序遍历

3.1.1 题求

3.1.2 求解

3.2 LC 二叉树的锯齿形层次遍历

3.2.1 题求

3.2.2 求解

3.3 LC 从前序与中序遍历序列构造二叉树 ☆

3.3.1 题求

3.3.2 求解

3.4 LC 从前序与中序遍历序列构造二叉树 ☆

3.4.1 题求

3.4.2 求解

3.5 LC 二叉搜索树中第K小的元素

3.5.1 题求

3.5.2 求解

3.6 LC 岛屿数量

3.6.1 题求

3.6.2 求解


一、数组和字符串


1.1 LC 三数之和

1.1.1 题求

1.1.2 求解

法一:排序+双指针

# 36.18% - 788ms

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        # 从小到大排序
        nums.sort()
        n = len(nums)
        res = []
        
        # 枚举 a
        for a in range(n):
            # a 需和上次枚举数不同
            if a > 0 and nums[a] == nums[a-1]:
                continue
            # c 初始指向数组最右端 ☆
            c = n - 1
            for b in range(a+1, n-1):
                # b 需和上次枚举的数不同
                if b > a+1 and nums[b] == nums[b-1]:
                    continue
                # 需保证 b 在 c 左侧; 若 a+b+c 过大,减小 c  (a+b+c>0)
                while b < c and nums[b] + nums[c] > -nums[a]:
                    c -= 1
                # 若 c 减少至与 b 重合,将不再有满足 a+b+c=0 且 b<c 的 c 了,退出循环 ☆
                if b == c:
                    break
                # 前面条件都符合,才记录当前组合 (a+b+c=0)
                if nums[b] + nums[c] == -nums[a]:
                    res.append([nums[a], nums[b], nums[c]])
        return res

参考资料:

力扣


1.2 LC 矩阵置零

1.2.1 题求

1.2.2 求解

法一:哈希集合+迭代

# 95.30% - 32ms

class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        """
        m, n = len(matrix), len(matrix[0])
        zero_cols = set()       # 全 0 列
        none_zero_rows = set()  # 非全 0 行

        for i in range(m):
            zero_row = False  # 当前行是否清零
            for j in range(n):
                if matrix[i][j] == 0:
                    zero_row = True
                    zero_cols.add(j)
            
            if zero_row:
                for j in range(n):
                    matrix[i][j] = 0
            else:
                none_zero_rows.add(i)

        # 对非全 0 行中尚未清 0 的列清 0
        for i in none_zero_rows:
            for j in zero_cols:
                matrix[i][j] = 0

参考资料:

力扣


1.3 LC 字母异位词分组

1.3.1 题求

1.3.2 求解

法一:哈希表+排序

# 93.31% - 40ms

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        from collections import defaultdict
        hashtable = defaultdict(list)

        for string in strs:
            key = tuple(sorted(list(string)))
            hashtable[key].append(string)

        return [val for val in hashtable.values()]

参考资料:

力扣


1.4 LC 无重复字符的最长子串

1.4.1 题求

1.4.2 求解

法一:双指针

# 93.31% - 48ms

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        max_len = 0  # 不重复数组的最大长度
        hashtable = {}  # 不重复数组各字符及其索引
        left = 0  # 不重复数组的左、右边界
        # 遍历右边界
        for right in range(len(s)):
            # 若当前字符未曾出现,则加入不重复数组 s[left, right],更新最大长度
            if s[right] not in hashtable:
                hashtable[s[right]] = right  # key: val = s: idx
                max_len = max(max_len, right-left+1)
            # 若当前字符已出现,则从上次出现的位置截断
            else:
                # 删除截断点之前的记录
                new_left = hashtable[s[right]]+1
                for i in range(left, new_left):
                    del hashtable[s[i]]
                # 更新新的左、右边界索引
                left = new_left
                hashtable[s[right]] = right  # key: val = s: idx
        return max_len

参考资料:

力扣


1.5 LC 最长回文子串

1.5.1 题求

1.5.2 求解

法一:动态规划

# 44.82% - 4480ms

class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        max_left, max_right, max_len = 0, 0, 1

        # dp[i][j] 表示 i, j 处是否为回文字符串
        dp = [[False for _ in range(n)] for _ in range(n)]
        for i in range(n):
            dp[i][i] = True

        # 状态转移
        for i in range(n-1, -1, -1):
            for j in range(i+1, n):
                if s[i] == s[j] and (j - i == 1 or dp[i+1][j-1] == True):
                    dp[i][j] = True
                    if (cur_len := j-i+1) > max_len:
                        max_left, max_right, max_len = i, j, cur_len

        return s[max_left: max_right+1]

参考资料:

力扣


1.6 LC 递增的三元子序列 ☆

1.6.1 题求

1.6.2 求解

法一:一次遍历

# 98.72% - 42ms

class Solution:
    def increasingTriplet(self, nums: List[int]) -> bool:
        n = len(nums)
        if n < 3:
            return False

        small, mid = float("inf"), float("inf")
        for num in nums:
            if num <= small:  # num <= small < mid 令 num 插左
                small = num
            elif num <= mid:  # small < num <= mid 令 num 插中
                mid = num
            elif num > mid:   # small < mid < num  令 num 插右
                return True
        return False

参考资料:

力扣


二、链表


2.1 LC 两数相加

2.1.1 题求

2.1.2 求解

法一:基本法

# 80.38% - 52ms

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

class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        # 链表 1 的数字 1
        num1 = base1 = 0 
        while l1:
            num1 += (l1.val * 10**base1)
            l1 = l1.next
            base1 += 1

        # 链表 2 的数字 2
        num2 = base2 = 0 
        while l2:
            num2 += (l2.val * 10**base2)
            l2 = l2.next 
            base2 += 1
                   
        # 数字 1 和数字 2 之和 summ
        summ = num1 + num2
        # 哨兵节点 / 伪头部
        dummy = node = ListNode()
        # 和 summ 为 0 则直接返回
        if summ == 0:
            return node
        # 逐个分配节点
        while summ != 0:
            summ, residual = divmod(summ, 10)
            node.next = ListNode(residual)
            node = node.next
        return dummy.next

法二:一次遍历

# 80.38% - 52ms

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
 
class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        head = tail = None
        carry = 0

        while l1 or l2:
            # 当前节点值与上次进位之和
            n1 = l1.val if l1 else 0
            n2 = l2.val if l2 else 0
            summ = n1 + n2 + carry

            # 尾部节点
            if not head:
                head = tail = ListNode(summ % 10)
            else:
                tail.next = ListNode(summ % 10)
                tail = tail.next

            # 进位
            carry = summ // 10

            # 指针后移
            if l1:
                l1 = l1.next
            if l2:
                l2 = l2.next
                
        # 进位补充尾部节点
        if carry > 0:
            tail.next = ListNode(carry)

        return head

参考资料:

力扣


2.2 LC 奇偶链表

2.2.1 题求

2.2.2 求解

法一:双指针

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

class Solution:
    def oddEvenList(self, head: ListNode) -> ListNode:
        # 节点数小于 2 的链表直接返回
        if not head or not head.next:
            return head

        # 偶数索引链表 0, 2, 4 ...
        even_head = even = head
        # 奇数索引链表 1, 3, 5 ...
        odd_head = odd = head.next

        # 交叉链接
        while odd.next and even.next:
            even.next = odd.next
            even = even.next
            if even.next:
                odd.next = even.next
                odd = odd.next

        # 链表尾部解耦
        if not odd.next:
            even.next = None  # odd.next
        elif not even.next:
            odd.next = None   # even.next

        # 偶数索引链表尾部链接奇数索引链表头部
        even.next = odd_head

        return even_head

法一改:最简化

class Solution:
    def oddEvenList(self, head: ListNode) -> ListNode:
        if not head:
            return head

        odd_head = head.next
        even, odd = head, odd_head

        while odd and odd.next:
            even.next = odd.next
            even = even.next
            odd.next = even.next
            odd = odd.next

        even.next = odd_head

        return head

参考资料:

力扣


2.3 相交链表

2.3.1 题求

2.3.2 求解

法一:交替迭代

# 86.53% - 128ms

# 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) -> ListNode:
        # 若不相交, 则有 A + B = B + A, 最终同时为 None
        # 若相交, 则有 A + C + B = B + C + A, 最终在 C 头相遇
        nodeA, nodeB = headA, headB
        while nodeA or nodeB:
            # 若有两个节点相同, 则相交
            if nodeA == nodeB:
                return nodeA
            # 第一次由 A 交换到 B
            if not nodeA:
                nodeA = headB
            else:
                nodeA = nodeA.next
            # 第一次由 B 交换到 A
            if not nodeB:
                nodeB = headA
            else:
                nodeB = nodeB.next
        # 不想交, 最终同时为 None
        return None

参考资料:

力扣


三、树和图


3.1 LC 二叉树的中序遍历

3.1.1 题求

3.1.2 求解

法一:递归

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        def inorder(node):
            if node:
                inorder(node.left)
                res.append(node.val)
                inorder(node.right)
            return

        inorder(root)
        return res

法二:迭代

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        stack = []
        while stack or root:
            if root:
                stack.append(root)
                root = root.left
            else:
                root = stack.pop()
                res.append(root.val)  # 中序记录时机
                root = root.right
        return res

参考资料:

力扣


3.2 LC 二叉树的锯齿形层次遍历

3.2.1 题求

3.2.2 求解

法一:正常出入队+正反记录 - 比正常记录然后每两层结果 reverse 更高效

# 97.31% - 24ms

from collections import deque

class Solution:
    def zigzagLevelOrder(self, root: TreeNode) -> List[List[int]]:
        if not root:
            return []

        res = []
        queue = deque([root])
        reverse = 0  # 逆序标志
        while queue:
            num = len(queue)
            tmp = [-1 for _ in range(num)]
            for idx in range(num):
                # 正常弹出和压入
                cur = queue.popleft()
                if cur.left:
                    queue.append(cur.left)
                if cur.right:
                    queue.append(cur.right)
                # 正反记录
                if reverse == 1:
                    idx = -idx - 1
                tmp[idx] = cur.val
            # 记录当前层遍历结果
            res.append(tmp)
            # 翻转标志
            reverse = 1 - reverse
        return res

法二:双反双端队列

# 97.31% - 24ms

from collections import deque

class Solution:
    def zigzagLevelOrder(self, root: TreeNode) -> List[List[int]]:
        if not root:
            return []

        res = []
        queue1 = deque([root])
        queue2 = deque()
        while queue1 or queue2:
            tmp = []
            if queue1:
                for _ in range(len(queue1)):
                    # 正弹出
                    cur = queue1.popleft()
                    tmp.append(cur.val)
                    # 正压入
                    if cur.left:
                        queue2.append(cur.left)
                    if cur.right:
                        queue2.append(cur.right)
            # 除了记录照常, 全部镜像相反!!!
            else:
                for _ in range(len(queue2)):
                    # 反弹出
                    cur = queue2.pop()  # 反
                    tmp.append(cur.val)
                    # 反压入
                    if cur.right:  # 反
                        queue1.appendleft(cur.right)  # 反
                    if cur.left:  # 反
                        queue1.appendleft(cur.left)  # 反
            # 记录当前层遍历结果
            res.append(tmp)
        return res

参考资料:

力扣


3.3 LC 从前序与中序遍历序列构造二叉树 ☆

3.3.1 题求

3.3.2 求解

法一:递归

# 90.32% - 40ms

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        
        # preorder - 根节点 - 左子树 - 右子树 - [3,9,20,15,7]
        # inorder  - 左子树 - 根节点 - 右子树 - [9,3,15,20,7]

        def helper(lhs, rhs):
            if lhs > rhs:
                return None
            # 当前根节点 node
            node = TreeNode(pre.popleft())  
            # 当前根节点 node 在中序遍历中的坐标 - 用于划分左、右子树
            idx = hashmap[node.val]  
            # 左子树范围
            node.left = helper(lhs, idx-1)
            # 右子树范围
            node.right = helper(idx+1, rhs)
            # 返回当前根节点 node
            return node

        # 使用双端队列代替 list.pop(0) 降低运算量
        pre = collections.deque(preorder)

        # 中序遍历值:索引
        hashmap = {val: idx for idx, val in enumerate(inorder)}

        return helper(0, len(inorder)-1)
# 98.47% - 32ms

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:

        def helper(lhs, rhs):
            if lhs > rhs:
                return None
            # 当前根节点 node
            node = TreeNode(pre.pop())  
            # 当前根节点 node 在中序遍历中的坐标 - 用于划分左、右子树
            idx = hashmap[node.val]  
            # 左子树范围
            node.left = helper(lhs, idx-1)
            # 右子树范围
            node.right = helper(idx+1, rhs)
            # 返回当前根节点 node
            return node

        # 使用 stack 代替 list.pop(0) 降低运算量
        n = len(preorder)
        pre = [preorder[i] for i in range(n-1, -1, -1)]

        # 中序遍历值:索引
        hashmap = {val: idx for idx, val in enumerate(inorder)}

        return helper(0, n-1)
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:

        def helper(lhs, rhs):
            nonlocal node_idx
            if lhs > rhs:
                return None
            # 当前根节点 node
            node = TreeNode(preorder[node_idx])  
            node_idx += 1
            # 当前根节点 node 在中序遍历中的坐标 - 用于划分左、右子树
            idx = hashmap[node.val]  
            # 左子树范围
            node.left = helper(lhs, idx-1)
            # 右子树范围
            node.right = helper(idx+1, rhs)
            # 返回当前根节点 node
            return node

        # 使用非局部变量 node_idx 代替 list.pop(0) 降低运算量
        node_idx = 0

        # 中序遍历值:索引
        hashmap = {val: idx for idx, val in enumerate(inorder)}

        return helper(0, len(inorder)-1)

法二:迭代

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        if not preorder:
            return None

        root = TreeNode(preorder[0])
        stack = [root]   # 前序遍历首个节点 - 根节点
        inorder_idx = 0  # 中序遍历首个节点索引
        # 依次枚举前序遍历中除了首个节点外的每个节点
        for i in range(1, len(preorder)):
            # 前序遍历第 i 个节点
            preorder_val = preorder[i]  
            # 栈顶当前节点
            node = stack[-1]
            # 如果 inorder[inorder_idx] 和栈顶节点不同,于是将当前节点 node 作为栈顶节点的左子节点
            if node.val != inorder[inorder_idx]:
                node.left = TreeNode(preorder_val)
                stack.append(node.left)  # 左子节点入栈
            # 如果 inorder_idx 恰好指向栈顶节点,那么不断地弹出栈顶节点 inorder_idx,
            # 并将当前节点作为最后一个弹出的节点的右子节点
            else:
                while stack and stack[-1].val == inorder[inorder_idx]:
                    node = stack.pop()  # 栈顶节点
                    inorder_idx += 1  # 栈顶节点
                node.right = TreeNode(preorder_val)
                stack.append(node.right)

        return root

参考资料:

力扣


3.4 LC 从前序与中序遍历序列构造二叉树 ☆

3.4.1 题求

3.4.2 求解

法一:迭代 - BFS

# 80.27% - 56ms

class Solution:
    def connect(self, root: 'Node') -> 'Node':
        if not root:
            return root

        queue = collections.deque()
        queue.append(root)
        while queue:
            nxt = None
            num = len(queue)
            for _ in range(num):
                # 当前节点
                cur = queue.popleft()
                # 子节点入队
                if cur.left:
                    queue.append(cur.left)
                if cur.right:
                    queue.append(cur.right)
                # 右节点连接
                if not nxt:
                    nxt = cur
                else:
                    nxt.next = cur
                    nxt = nxt.next
        return root

法二:递归

# 80.27% - 56ms

class Solution:
    def connect(self, root: 'Node') -> 'Node':

        def dfs(cur, nxt):
            if not cur:
                return None
            # 当前连接
            cur.next = nxt
            # 左子节点连接右子节点
            dfs(cur.left, cur.right)
            # 右子节点连接父节点的后继节点的左子节点
            dfs(cur.right, None if not cur.next else cur.next.left)

        dfs(root, None)
        return root

参考资料:

力扣


3.5 LC 二叉搜索树中第K小的元素

3.5.1 题求

3.5.2 求解

法一:中序遍历+迭代

# 86.77% - 44ms

class Solution:
    def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
        stack = [root]
        while stack or root:
            if root:
                stack.append(root)
                root = root.left
            else:
                root = stack.pop()
                k -= 1
                if k == 0:
                    return root.val
                root = root.right
        return

参考资料:

力扣


3.6 LC 岛屿数量

3.6.1 题求

3.6.2 求解

法一:DFS

# 59.19% - 112ms

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        def dfs(i, j):
            # 坐标越界或不为新岛屿, 返回 False
            if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] == "0":
                return False
            # 已经 DFS 的节点置 0
            grid[i][j] = "0"
            # DFS 所有相邻节点
            dfs(i-1, j)
            dfs(i+1, j)
            dfs(i, j-1)
            dfs(i, j+1)
            # 当前所有相邻节点为同一岛屿, 返回 True
            return True

        num = 0
        m, n = len(grid), len(grid[0])
        for i in range(m):
            for j in range(n):
                if dfs(i, j):
                    num += 1
        return num

参考资料:

力扣

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值