算法笔记 -- 经典动态规划
2021-10-26 19:49:20
#
algorithm
正则表达式

我们考虑,s
和p
相互匹配的过程就是两个指针i
和j
分别在s
和p
上移动。
如果最后两个指针都可以达到字符串的末尾,那么说明匹配成功,反之匹配失败。
- 如果不考虑
*
匹配符,只需要判断两个指针是否能达到相同的位置即可; - 考虑
*
匹配符,若\(s[i] == p[j]\),\(p[j]\)有可能需要向\(s[i+1...]\)匹配\(0\)个或多个字符。 - 若\(s[i] \neq p[j]\),则\(p[j]\)只能匹配0次,然后再看
*
的下一个字符是否能与\(s[i]\)匹配。
现在的问题就是,碰到*
的时候,是应该匹配多少次?
考虑使用动态规划来解决问题。定义\(dp(string\quad s,int\quad i, string \quad p, int\quad j )\)为字符串\(s[i...]\)是否可以匹配\(p[j...]\)。
1 | class Solution { |
四键键盘

定义\(dp\)函数如下:
第一个参数为剩余的按键次数,用 n
表示;第二个状态是当前屏幕上字符 A 的数量,用 a_num
表示;第三个状态是剪切板中字符 A 的数量,用 copy
表示。
结合刚才说的 4 种「选择」,我们可以把这几种选择通过状态转移表示出来:
1 | dp(n - 1, a_num + 1, copy), # A |
这样可以看到问题的规模 n
在不断减小,肯定可以到达
n = 0
的 base case,所以这个思路是正确的:
1 | def maxA(N: int) -> int: |
并且要达到最多,最后一次按键要么是A
要么是Ctrl+V
。
1 | int[] dp = new int[N + 1]; |
对于「按 A
键」这种情况,就是状态 i - 1
的屏幕上新增了一个 A 而已,很容易得到结果:
1 | // 按 A 键,就比上次多一个 A 而已 |
但是要按 C-V
,还要考虑之前是在哪里 C-A C-C
的。所以最优的操作序列一定是 C-A C-C
接着若干
C-V
,所以我们用一个变量 j
作为若干
C-V
的起点。那么 j
之前的 2
个操作就应该是 C-A C-C
了
1 | public int maxA(int N) { |
鸡蛋掉落

若在不限制鸡蛋个数的情况下,可以使用线性扫描或是二分的方式来查找。但是给定了鸡蛋个数N的情况下,这两种都是不可行的。
1 | dp[k][m] = n |

1 | class Solution { |