🏠 总目录📚 本教程 05 · 动态规划:把今天交给昨天 ← →
📑 本页目录(点开跳转)

05 · 动态规划:把今天交给昨天

⏱ 8 分钟 | 最大子数组、跳台阶、正则匹配


🎯 一句话

动态规划不是公式收集:先定义一句“dp[i] 到底代表什么”,再只从已经解决的小问题推当前问题。

🧩 题 1:连续子数组的最大和

走到当前数字时,只有两个选择:接着前一段,或者从我自己重新开始。

def max_subarray(nums):
    best_ending_here = best_so_far = nums[0]
    for value in nums[1:]:
        best_ending_here = max(value, best_ending_here + value)
        best_so_far = max(best_so_far, best_ending_here)
    return best_so_far


assert max_subarray([-2, 1, -3, 4, -1, 2, 1, -5, 4]) == 6

状态:best_ending_here 是“必须以当前位置结尾”的最大和;它不等于全局答案,所以还要 best_so_far。

🧩 题 2:跳台阶

到第 n 阶,最后一步不是从 n-1 来,就是从 n-2 来。因此答案等于两种走法相加。

def jump_ways(n):
    if n < 0:
        return 0
    previous, current = 1, 1  # f(0), f(1)
    for _ in range(2, n + 1):
        previous, current = current, previous + current
    return current


assert jump_ways(0) == 1
assert jump_ways(4) == 5

🧩 题 3:正则表达式匹配(. 和 *)

这题难在 *:它可以让前一个字符出现 0 次,也可以吃一个字符后继续留在原模式。

from functools import lru_cache


def regex_match(text, pattern):
    @lru_cache(maxsize=None)
    def match(i, j):
        if j == len(pattern):
            return i == len(text)
        first = i < len(text) and pattern[j] in {text[i], '.'}
        if j + 1 < len(pattern) and pattern[j + 1] == '*':
            return match(i, j + 2) or (first and match(i + 1, j))
        return first and match(i + 1, j + 1)
    return match(0, 0)


assert regex_match('aaa', 'a*a') is True
assert regex_match('ab', '.*') is True
assert regex_match('ab', 'a') is False

⭐ 看见“很多子问题重复算”,先考虑记忆化;它就是从递归过渡到动态规划的桥。


🔗 这一章连到哪里

去哪 为什么
强化学习基础 04 · 动态规划 ⚠️ 同名不同物,但同一个思想:那里的「动态规划」指的是环境规则已知时直接迭代解贝尔曼方程。⭐ 两边共用的那句话是「今天的最优由昨天的状态推出来」

🛑 可以停在这里

DP 的第一行不是代码,是中文:“我的状态代表什么?”


✅ 检查点

最大子数组题为什么不能只记录全局最大和?

👀 看答案

下一轮需要知道“以当前点结尾”的最佳前缀能否接上。全局最大和可能结束在很早的位置,不能直接接。


⚡ 走神救援

继续: 06 · 回溯与矩阵:试完一条路怎么干净回来

打卡记录保存在你的浏览器里,首页能看到总进度