Skip to content

Commit d27faa4

Browse files
committed
dp,recursion and slide window
1 parent 103403d commit d27faa4

File tree

1 file changed

+91
-177
lines changed

1 file changed

+91
-177
lines changed

basic_algorithm/dp.md

+91-177
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,11 @@ int lengthOfLIS1(vector<int>& nums) {
375375

376376
#pragma region 复杂度O(n log n)
377377
// 题目给了提示,看到log n可以考虑往二分查找之类的上凑
378+
// 思路:保存最大长度及其末尾节点,通过判断是否大于末尾节点来决定是否能够组成子序列
379+
// 又,既然不能保证当前的"最大长度"会不会被其他组合反超
380+
// 干脆把整个过程,每个长度都保存下来,构成单调栈
381+
// 一方面通过栈顶来决定是否能够组合成更长的子序列
382+
// 另一方面通过查找,找出比当前节点小的第一个末尾进行更新,以确保其他组合的机会
378383
int lengthOfLIS(vector<int> &nums) {
379384
int size = nums.size();
380385
if (size < 2) {
@@ -413,53 +418,21 @@ int lengthOfLIS(vector<int> &nums) {
413418
414419
> 给定一个**非空**字符串  *s*  和一个包含**非空**单词列表的字典  *wordDict*,判定  *s*  是否可以被空格拆分为一个或多个在字典中出现的单词。
415420
416-
```go
417-
func wordBreak(s string, wordDict []string) bool {
418-
// f[i] 表示前i个字符是否可以被切分
419-
// f[i] = f[j] && s[j+1~i] in wordDict
420-
// f[0] = true
421-
// return f[len]
422-
423-
if len(s) == 0 {
424-
return true
425-
}
426-
f := make([]bool, len(s)+1)
427-
f[0] = true
428-
max,dict := maxLen(wordDict)
429-
for i := 1; i <= len(s); i++ {
430-
l := 0
431-
if i - max > 0 {
432-
l = i - max
433-
}
434-
for j := l; j < i; j++ {
435-
if f[j] && inDict(s[j:i],dict) {
436-
f[i] = true
437-
break
438-
}
439-
}
440-
}
441-
return f[len(s)]
442-
}
443-
444-
445-
446-
func maxLen(wordDict []string) (int,map[string]bool) {
447-
dict := make(map[string]bool)
448-
max := 0
449-
for _, v := range wordDict {
450-
dict[v] = true
451-
if len(v) > max {
452-
max = len(v)
453-
}
454-
}
455-
return max,dict
456-
}
457-
458-
func inDict(s string,dict map[string]bool) bool {
459-
_, ok := dict[s]
460-
return ok
421+
```c++
422+
bool wordBreak(string s, vector<string> &wordDict) {
423+
unordered_set<string> wordDictSet{wordDict.begin(), wordDict.end()};
424+
vector<bool> dp(s.size() + 1);
425+
dp[0] = true;
426+
for (int i = 1; i <= s.size(); ++i) {
427+
for (int j = 0; j < i; ++j) {
428+
if (dp[j] && wordDictSet.find(s.substr(j, i - j)) != wordDictSet.end()) {
429+
dp[i] = true;
430+
break;
431+
}
432+
}
433+
}
434+
return dp.back();
461435
}
462-
463436
```
464437

465438
小结
@@ -479,50 +452,23 @@ func inDict(s string,dict map[string]bool) bool {
479452
> 一个字符串的   子序列   是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
480453
> 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。
481454
482-
```go
483-
func longestCommonSubsequence(a string, b string) int {
484-
// dp[i][j] a前i个和b前j个字符最长公共子序列
485-
// dp[m+1][n+1]
486-
// ' a d c e
487-
// ' 0 0 0 0 0
488-
// a 0 1 1 1 1
489-
// c 0 1 1 2 1
490-
//
491-
dp:=make([][]int,len(a)+1)
492-
for i:=0;i<=len(a);i++ {
493-
dp[i]=make([]int,len(b)+1)
494-
}
495-
for i:=1;i<=len(a);i++ {
496-
for j:=1;j<=len(b);j++ {
497-
// 相等取左上元素+1,否则取左或上的较大值
498-
if a[i-1]==b[j-1] {
499-
dp[i][j]=dp[i-1][j-1]+1
455+
```c++
456+
vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1));
457+
for (int i = 1; i <= text1.size(); ++i) {
458+
for (int j = 1; j <= text2.size(); ++j) {
459+
if (text1[i - 1] == text2[j - 1]) {
460+
dp[i][j] = dp[i - 1][j - 1] + 1;
500461
} else {
501-
dp[i][j]=max(dp[i-1][j],dp[i][j-1])
462+
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
502463
}
503464
}
504465
}
505-
return dp[len(a)][len(b)]
506-
}
507-
func max(a,b int)int {
508-
if a>b{
509-
return a
510-
}
511-
return b
466+
return dp.back().back();
512467
}
513468
```
514469
515470
注意点
516471
517-
- go 切片初始化
518-
519-
```go
520-
dp:=make([][]int,len(a)+1)
521-
for i:=0;i<=len(a);i++ {
522-
dp[i]=make([]int,len(b)+1)
523-
}
524-
```
525-
526472
- 从 1 开始遍历到最大长度
527473
- 索引需要减一
528474
@@ -536,37 +482,30 @@ for i:=0;i<=len(a);i++ {
536482
537483
思路:和上题很类似,相等则不需要操作,否则取删除、插入、替换最小操作次数的值+1
538484
539-
```go
540-
func minDistance(word1 string, word2 string) int {
541-
// dp[i][j] 表示a字符串的前i个字符编辑为b字符串的前j个字符最少需要多少次操作
542-
// dp[i][j] = OR(dp[i-1][j-1],a[i]==b[j],min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1)
543-
dp:=make([][]int,len(word1)+1)
544-
for i:=0;i<len(dp);i++{
545-
dp[i]=make([]int,len(word2)+1)
546-
}
547-
for i:=0;i<len(dp);i++{
548-
dp[i][0]=i
549-
}
550-
for j:=0;j<len(dp[0]);j++{
551-
dp[0][j]=j
552-
}
553-
for i:=1;i<=len(word1);i++{
554-
for j:=1;j<=len(word2);j++{
555-
// 相等则不需要操作
556-
if word1[i-1]==word2[j-1] {
557-
dp[i][j]=dp[i-1][j-1]
558-
}else{ // 否则取删除、插入、替换最小操作次数的值+1
559-
dp[i][j]=min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1
485+
```c++
486+
int minDistance(string word1, string word2) {
487+
// 删除与插入操作是等价的,修改word1和修改word2也是等价的
488+
// 故,实际操作只有在三种:
489+
// 1. 在word1插入,dp[i][j] = dp[i][j - 1] + 1
490+
// 2. 在word2插入,dp[i][j] = dp[i - 1][j] + 1
491+
// 3. 在word1修改,dp[i][j] = dp[i - 1][j - 1] + 1
492+
vector<vector<int>> dp(word1.size(), vector<int>(word2.size()));
493+
for (int i = 0; i < word1.size(); ++i) {
494+
dp[i][0] = i;
495+
}
496+
for (int i = 0; i < word2.size(); ++i) {
497+
dp[0][i] = i;
498+
}
499+
for (int i = 1; i <= word1.size(); ++i) {
500+
for (int j = 1; j <= word2.size(); ++j) {
501+
if (word1[i - 1] == word2[j - 1]) {
502+
dp[i][j] = dp[i - 1][j - 1];
503+
} else {
504+
dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
560505
}
561506
}
562507
}
563-
return dp[len(word1)][len(word2)]
564-
}
565-
func min(a,b int)int{
566-
if a>b{
567-
return b
568-
}
569-
return a
508+
return dp.back().back();
570509
}
571510
```
572511

@@ -582,35 +521,23 @@ func min(a,b int)int{
582521
583522
思路:和其他 DP 不太一样,i 表示钱或者容量
584523

585-
```go
586-
func coinChange(coins []int, amount int) int {
587-
// 状态 dp[i]表示金额为i时,组成的最小硬币个数
588-
// 推导 dp[i] = min(dp[i-1], dp[i-2], dp[i-5])+1, 前提 i-coins[j] > 0
589-
// 初始化为最大值 dp[i]=amount+1
590-
// 返回值 dp[n] or dp[n]>amount =>-1
591-
dp:=make([]int,amount+1)
592-
for i:=0;i<=amount;i++{
593-
dp[i]=amount+1
594-
}
595-
dp[0]=0
596-
for i:=1;i<=amount;i++{
597-
for j:=0;j<len(coins);j++{
598-
if i-coins[j]>=0 {
599-
dp[i]=min(dp[i],dp[i-coins[j]]+1)
524+
```c++
525+
int coinChange(vector<int>& coins, int amount) {
526+
// 初始化为amount + 1,如果最后大于amount说明无解
527+
// dp[i]表示金额为i时,所需最少硬币个数
528+
// dp[i] = min(dp[i], dp[i - coin] + 1)
529+
// dp[i - coin],倒扣当前面额
530+
// 注意不要越界,i - coin >= 0
531+
vector<int> dp(amount + 1, amount + 1);
532+
dp[0] = 0;
533+
for (auto i = 1; i <= amount; ++i) {
534+
for (const auto &coin : coins) {
535+
if (i - coin >= 0) {
536+
dp[i] = min(dp[i], dp[i - coin] + 1);
600537
}
601538
}
602539
}
603-
if dp[amount] > amount {
604-
return -1
605-
}
606-
return dp[amount]
607-
608-
}
609-
func min(a,b int)int{
610-
if a>b{
611-
return b
612-
}
613-
return a
540+
return dp.back() > amount ? -1 : dp.back();
614541
}
615542
```
616543
@@ -622,34 +549,30 @@ func min(a,b int)int{
622549
623550
> 在 n 个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为 m,每个物品的大小为 A[i]
624551
625-
```go
626-
func backPack (m int, A []int) int {
552+
```c++
553+
int backPack(int m, vector<int> &A) {
627554
// write your code here
628-
// f[i][j] 前i个物品,是否能装j
629-
// f[i][j] =f[i-1][j] f[i-1][j-a[i] j>a[i]
630-
// f[0][0]=true f[...][0]=true
631-
// f[n][X]
632-
f:=make([][]bool,len(A)+1)
633-
for i:=0;i<=len(A);i++{
634-
f[i]=make([]bool,m+1)
635-
}
636-
f[0][0]=true
637-
for i:=1;i<=len(A);i++{
638-
for j:=0;j<=m;j++{
639-
f[i][j]=f[i-1][j]
640-
if j-A[i-1]>=0 && f[i-1][j-A[i-1]]{
641-
f[i][j]=true
555+
// dp[i][j] 前i个物品,是否能装j
556+
// dp[i - 1][j] == true? 则不需要第i个物品也能装满
557+
// dp[i - 1][j - A[i - 1]]? 腾出第i个物品的空间,剩下空间能否装满
558+
// dp[i][j] = dp[i - 1][j] or dp[i - 1][j - A[i - 1]]
559+
vector<vector<bool>> dp(A.size() + 1, vector<bool>(m + 1));
560+
dp[0][0] = true;
561+
for (int i = 1; i <= A.size(); ++i) {
562+
for (int j = 0; j <= m; ++j) {
563+
dp[i][j] = dp[i - 1][j];
564+
if (j - A[i - 1] >= 0 && dp[i - 1][j - A[i - 1]]) {
565+
dp[i][j] = true;
642566
}
643567
}
644568
}
645-
for i:=m;i>=0;i--{
646-
if f[len(A)][i] {
647-
return i
569+
for (int i = m; i >= 0; ++i) {
570+
if (dp[A.size()][i]) {
571+
return i;
648572
}
649573
}
650-
return 0
574+
return 0;
651575
}
652-
653576
```
654577

655578
### [backpack-ii](https://www.lintcode.com/problem/backpack-ii/description)
@@ -659,31 +582,22 @@ func backPack (m int, A []int) int {
659582
660583
思路:f[i][j] 前 i 个物品,装入 j 背包 最大价值
661584

662-
```go
663-
func backPackII (m int, A []int, V []int) int {
585+
```c++
586+
int backPackII(int m, vector<int> &A, vector<int> &V) {
664587
// write your code here
665-
// f[i][j] 前i个物品,装入j背包 最大价值
666-
// f[i][j] =max(f[i-1][j] ,f[i-1][j-A[i]]+V[i]) 是否加入A[i]物品
667-
// f[0][0]=0 f[0][...]=0 f[...][0]=0
668-
f:=make([][]int,len(A)+1)
669-
for i:=0;i<len(A)+1;i++{
670-
f[i]=make([]int,m+1)
671-
}
672-
for i:=1;i<=len(A);i++{
673-
for j:=0;j<=m;j++{
674-
f[i][j]=f[i-1][j]
675-
if j-A[i-1] >= 0{
676-
f[i][j]=max(f[i-1][j],f[i-1][j-A[i-1]]+V[i-1])
588+
// dp[i][j] 前i个物品,装入j背包 最大价值
589+
vector<vector<int>> dp(A.size() + 1, vector<int>(V.size() + 1));
590+
for (int i = 1; i <= A.size(); ++i) {
591+
for (int j = 0; j <= m; ++j) {
592+
// 不选第i个物品,则dp[i][j] = dp[i - 1][j]
593+
// 选了第i个物品,则dp[i][j] = dp[i - 1][j - A[i - 1]],倒扣i的大小
594+
dp[i][j] = dp[i - 1][j];
595+
if (j - A[i - 1] >= 0) {
596+
dp[i][j] = max(dp[i][j], dp[i - 1][j - A[i - 1]] + V[i - 1]);
677597
}
678598
}
679599
}
680-
return f[len(A)][m]
681-
}
682-
func max(a,b int)int{
683-
if a>b{
684-
return a
685-
}
686-
return b
600+
return dp.back().back();
687601
}
688602
```
689603

0 commit comments

Comments
 (0)