程序员进大厂必备之算法与数据结构高频题!

题目1: 最长回文子串(难度:中高)

问题描述
给定一个字符串 s,找出其中最长的回文子串(子串是原字符串中连续的一段)。例如,输入 s = "babad",输出可能是 "bab""aba"。输入保证非空。

解题思路
使用动态规划(DP)优化暴力解法。定义二维DP数组 dp[i][j] 表示子串 s[i..j] 是否为回文。状态转移方程为:
$$dp[i][j] = (s[i] == s[j]) \land dp[i+1][j-1]$$
其中,当子串长度小于等于2时,直接判断字符是否相等。初始化时,所有长度为1的子串为回文(即 dp[i][i] = True)。遍历所有可能子串长度,记录最大回文子串的起始位置和长度。

代码实现

def longest_palindrome(s: str) -> str:
    n = len(s)
    if n < 2:
        return s
    
    # 初始化DP数组,dp[i][j]表示s[i..j]是否为回文
    dp = [[False] * n for _ in range(n)]
    start, max_len = 0, 1  # 记录最长回文起始位置和长度
    
    # 所有长度为1的子串都是回文
    for i in range(n):
        dp[i][i] = True
    
    # 遍历子串长度L从2到n
    for L in range(2, n + 1):
        for i in range(n - L + 1):
            j = i + L - 1  # 子串结束位置
            if s[i] == s[j]:
                if L == 2:  # 长度为2的子串
                    dp[i][j] = True
                else:
                    dp[i][j] = dp[i + 1][j - 1]  # 状态转移
                if dp[i][j] and L > max_len:  # 更新最长回文
                    start = i
                    max_len = L
            else:
                dp[i][j] = False
    
    return s[start:start + max_len]

# 测试示例
print(longest_palindrome("babad"))  # 输出: "bab" 或 "aba"

时间复杂度分析
$O(n^2)$,其中 $n$ 是字符串长度。空间复杂度 $O(n^2)$,可通过Manacher算法优化到 $O(n)$,但DP解法更易理解。


题目2: 合并K个排序链表(难度:高)

问题描述
给定一个包含 $k$ 个排序链表的数组 lists,每个链表已按升序排列。合并所有链表为一个排序链表并返回。例如,输入 lists = [[1,4,5],[1,3,4],[2,6]],输出 [1,1,2,3,4,4,5,6]

解题思路
使用最小堆(优先队列)高效合并。关键点:

  • 堆中存储每个链表的当前节点,比较节点值。
  • 每次从堆中弹出最小值,添加到结果链表,并推进该链表的指针。
  • 堆的大小为 $k$,确保每次操作高效。

数学上,堆的插入和弹出操作时间复杂度为 $O(\log k)$,总时间复杂度优化为 $O(n \log k)$,其中 $n$ 是总节点数。

代码实现

import heapq

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def merge_k_lists(lists: list[ListNode]) -> ListNode:
    if not lists:
        return None
    
    # 使用最小堆,存储元组 (节点值, 链表索引, 节点)
    heap = []
    for i, node in enumerate(lists):
        if node:
            heapq.heappush(heap, (node.val, i, node))
    
    dummy = ListNode(0)  # 哑节点简化操作
    curr = dummy
    
    while heap:
        val, idx, node = heapq.heappop(heap)
        curr.next = node
        curr = curr.next
        if node.next:  # 推进链表指针
            heapq.heappush(heap, (node.next.val, idx, node.next))
    
    return dummy.next

# 辅助函数:创建链表和打印(面试中可能不需要,但为完整性添加)
def create_list(arr):
    dummy = ListNode()
    curr = dummy
    for num in arr:
        curr.next = ListNode(num)
        curr = curr.next
    return dummy.next

def print_list(node):
    res = []
    while node:
        res.append(node.val)
        node = node.next
    print(res)

# 测试示例
lists = [create_list([1,4,5]), create_list([1,3,4]), create_list([2,6])]
merged = merge_k_lists(lists)
print_list(merged)  # 输出: [1,1,2,3,4,4,5,6]

时间复杂度分析
$O(n \log k)$,其中 $n$ 是总节点数,$k$ 是链表数。空间复杂度 $O(k)$ 用于堆。


题目3: 编辑距离(难度:高)

问题描述
给定两个单词 word1word2,计算将 word1 转换为 word2 所需的最小操作数。操作包括:插入一个字符、删除一个字符或替换一个字符。例如,输入 word1 = "horse", word2 = "ros",输出 3(删除 'h'、替换 'r' 为 'o'、删除 'e')。

解题思路
动态规划是标准解法。定义 dp[i][j] 为将 word1[0..i-1] 转换为 word2[0..j-1] 的最小编辑距离。状态转移方程为:
$$dp[i][j] = \begin{cases} \min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + \text{cost}) & \text{if } i>0, j>0 \ j & \text{if } i=0 \ i & \text{if } j=0 \end{cases}$$
其中,cost 为0如果 word1[i-1] == word2[j-1],否则为1(表示替换操作)。

代码实现

def min_distance(word1: str, word2: str) -> int:
    m, n = len(word1), len(word2)
    # 初始化DP数组,dp[i][j]表示word1前i字符到word2前j字符的距离
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    
    # 边界条件:空字符串转换
    for i in range(m + 1):
        dp[i][0] = i  # 删除所有字符
    for j in range(n + 1):
        dp[0][j] = j  # 插入所有字符
    
    # 填充DP表
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if word1[i - 1] == word2[j - 1]:
                cost = 0
            else:
                cost = 1
            # 状态转移:取删除、插入、替换的最小值
            dp[i][j] = min(
                dp[i-1][j] + 1,     # 删除word1[i-1]
                dp[i][j-1] + 1,     # 插入word2[j-1]
                dp[i-1][j-1] + cost # 替换或匹配
            )
    
    return dp[m][n]

# 测试示例
print(min_distance("horse", "ros"))  # 输出: 3
print(min_distance("intention", "execution"))  # 输出: 5

时间复杂度分析
$O(m \times n)$,其中 $m$ 和 $n$ 是单词长度。空间复杂度 $O(m \times n)$,可优化到 $O(\min(m, n))$。


题目4: 二叉树的序列化与反序列化(难度:中高)

问题描述
设计一个算法来序列化和反序列化二叉树。序列化是将二叉树转换为字符串,反序列化是将字符串还原为二叉树。例如,输入二叉树:

    1
   / \
  2   3
     / \
    4   5

序列化输出可能为 "1,2,None,None,3,4,None,None,5,None,None"(使用先序遍历)。

解题思路
使用深度优先搜索(DFS)进行先序遍历。序列化时,遍历节点并记录值,空节点用特殊标记(如 "None")。反序列化时,根据序列重建树结构。关键点:

  • 序列化:递归先序遍历,拼接字符串。
  • 反序列化:分割字符串为列表,递归构建子树。

数学上,树的高度 $h$ 影响时间复杂度,平均情况下为 $O(n)$。

代码实现

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

class Codec:
    def serialize(self, root: TreeNode) -> str:
        """Encodes a tree to a single string."""
        if not root:
            return "None"
        # 先序遍历:根-左-右
        left = self.serialize(root.left)
        right = self.serialize(root.right)
        return f"{root.val},{left},{right}"  # 使用逗号分隔

    def deserialize(self, data: str) -> TreeNode:
        """Decodes your encoded data to tree."""
        nodes = data.split(',')  # 分割字符串为列表
        return self._deserialize_helper(nodes)
    
    def _deserialize_helper(self, nodes: list) -> TreeNode:
        if nodes[0] == "None":
            nodes.pop(0)
            return None
        # 构建当前节点
        root = TreeNode(int(nodes[0]))
        nodes.pop(0)
        root.left = self._deserialize_helper(nodes)  # 递归左子树
        root.right = self._deserialize_helper(nodes)  # 递归右子树
        return root

# 测试示例
# 构建树: 1 -> 2, 3; 3 -> 4, 5
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.right.left = TreeNode(4)
root.right.right = TreeNode(5)

codec = Codec()
serialized = codec.serialize(root)
print(serialized)  # 输出类似: "1,2,None,None,3,4,None,None,5,None,None"
deserialized = codec.deserialize(serialized)
# 验证:可通过遍历检查树结构

时间复杂度分析
序列化和反序列化均为 $O(n)$,其中 $n$ 是节点数。空间复杂度 $O(n)$ 用于递归栈。

亚马逊高频高难度算法题解析:矩阵中的最长递增路径

题目描述
给定一个 $m \times n$ 整数矩阵,找出最长严格递增路径的长度(每一步可向上下左右移动,但不能重复访问单元格)。
示例
输入矩阵:
$$\begin{bmatrix}
9 & 9 & 4 \
6 & 6 & 8 \
2 & 1 & 1
\end{bmatrix}$$
输出:$4$(路径 $1 \to 2 \to 6 \to 9$)


解题思路(DFS + 记忆化)

1. 问题分析
  • 核心挑战:路径方向不固定,暴力DFS会重复计算子路径。
  • 关键优化:为每个单元格 $(i,j)$ 存储最长路径长度 $dp[i][j]$,避免重复计算。
2. 算法设计
  • 状态定义:$dp[i][j]$ 表示以 $(i,j)$ 为起点的最长递增路径长度。
  • 转移方程
    $$dp[i][j] = 1 + \max_{\text{dir} \in {\text{上,下,左,右}}} \left( dp[\text{next}] \right)$$
    其中 $\text{next}$ 需满足值大于 $matrix[i][j]$。
  • 终止条件:当所有邻居均小于当前值时,$dp[i][j] = 1$。
3. 复杂度分析
  • 时间复杂度:$O(mn)$(每个单元格计算一次)
  • 空间复杂度:$O(mn)$(存储 $dp$ 矩阵)

代码实现(Python)

def longestIncreasingPath(matrix):
    if not matrix: return 0
    m, n = len(matrix), len(matrix[0])
    dp = [[0] * n for _ in range(m)]  # 记忆化数组
    max_len = 0
    
    def dfs(i, j):
        if dp[i][j]: return dp[i][j]  # 已计算则直接返回
        directions = [(0,1), (1,0), (0,-1), (-1,0)]
        max_path = 1
        for dx, dy in directions:
            x, y = i + dx, j + dy
            if 0 <= x < m and 0 <= y < n and matrix[x][y] > matrix[i][j]:
                max_path = max(max_path, 1 + dfs(x, y))
        dp[i][j] = max_path  # 存储结果
        return max_path
    
    for i in range(m):
        for j in range(n):
            max_len = max(max_len, dfs(i, j))
    return max_len


关键考点

  1. DFS剪枝:通过 $dp$ 数组避免重复计算子问题。
  2. 方向处理:四方向移动需检查边界和递增条件。
  3. 初始化技巧:$dp$ 初始为 $0$,首次访问时计算并存储。

应用场景:亚马逊物流路径优化、游戏地图寻路等动态规划高频题型。练习时需重点掌握状态转移设计记忆化搜索的平衡。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值