动态规划(DP)确实是一个难点。因为相对其他方面来说,DP也练得最少。加上坊间都在传,DP很难,就吓退了很多人吧。
虽然DP确实五花八门,想找一套普遍适用的方法是没有的。
但DP也就是解决编程问题的一种,见得多了,也就知道需要用DP来解了。无他,唯手熟尔。
举例来说,大部分学过一点DP的人,都知道要用DP来解斐波那契数列。原因大家也都知道,就是通过空间换时间,通过开销额外的空间,去降维时间复杂度。
其他的DP题也都是一样的思路,所以,先通过简单的例子入手,理解几个题目之后。就会发现DP也是有规律可循的了。
我在下面就列举三个对我有启发的资料吧!
希望对你也有帮助,如果有帮助,麻烦点赞,毕竟这也是我进一步写作的动力。
第一个资料,来自LeetCode去年圣诞节大奖的第一名,为了方便大家阅读,我把内容拷贝过来,放在分割线之间了,大家可以去看原文,还能看到原文下面的很多很有参考价值的评论。
原文作者:aatalyk
原文出处:LeetCode Discuss
原文链接:https://leetcode.com/discuss/general-discussion/458695/dynamic-programming-patterns
Before starting the topic let me introduce myself. I am a Mobile Developer currently working in Warsaw and spending my free time for interview preparations. I started to prepare for interviews two years ago. At that time I should say I could not solve the two sum problem. Easy problems seemed to me like hard ones so most of the time I had to look at editorials and discuss section. Currently, I have solved ~800 problems and time to time participate in contests. I usually solve 3 problems in a contest and sometimes 4 problems. Ok, lets come back to the topic.
Recently I have concentrated my attention on Dynamic Programming cause its one of the hardest topics in an interview prep. After solving ~140 problems in DP I have noticed that there are few patterns that can be found in different problems. So I did a research on that and find the following topics. I will not give complete ways how to solve problems but these patterns may be helpful in solving DP.
原作者把DP的题目分成了五类:
Minimum (Maximum) Path to Reach a Target
Distinct Ways
Merging Intervals
DP on Strings
Decision Making
Generate problem statement for this pattern
Given a target find minimum (maximum) cost / path / sum to reach the target.
Choose minimum (maximum) path among all possible paths before the current state, then add value for the current state.
routes[i] = min(routes[i-1], routes[i-2], ... , routes[i-k]) + cost[i]
Generate optimal solutions for all values in the target and return the value for the target.
for (int i = 1; i <= target; ++i) { for (int j = 0; j < ways.size(); ++j) { if (ways[j] <= i) { dp[i] = min(dp[i], dp[i - ways[j]]) + cost / path / sum; } } } return dp[target]
746. Min Cost Climbing Stairs Easy
for (int i = 2; i <= n; ++i) { dp[i] = min(dp[i-1], dp[i-2]) + (i == n ? 0 : cost[i]); } return dp[n]
64. Minimum Path Sum Medium
for (int i = 1; i < n; ++i) { for (int j = 1; j < m; ++j) { grid[i][j] = min(grid[i-1][j], grid[i][j-1]) + grid[i][j]; } } return grid[n-1][m-1]
322. Coin Change Medium
for (int j = 1; j <= amount; ++j) { for (int i = 0; i < coins.size(); ++i) { if (coins[i] <= j) { dp[j] = min(dp[j], dp[j - coins[i]] + 1); } } }
931. Minimum Falling Path Sum Medium
983. Minimum Cost For Tickets Medium
650. 2 Keys Keyboard Medium
279. Perfect Squares Medium
1049. Last Stone Weight II Medium
120. Triangle Medium
474. Ones and Zeroes Medium
221. Maximal Square Medium
322. Coin Change Medium
1240. Tiling a Rectangle with the Fewest Squares Hard
174. Dungeon Game Hard
871. Minimum Number of Refueling Stops Hard
Generate problem statement for this pattern
Given a target find a number of distinct ways to reach the target.
Sum all possible ways to reach the current state.
routes[i] = routes[i-1] + routes[i-2], ... , + routes[i-k]
Generate sum for all values in the target and return the value for the target.
for (int i = 1; i <= target; ++i) { for (int j = 0; j < ways.size(); ++j) { if (ways[j] <= i) { dp[i] += dp[i - ways[j]]; } } } return dp[target]
70. Climbing Stairs easy
for (int stair = 2; stair <= n; ++stair) { for (int step = 1; step <= 2; ++step) { dp[stair] += dp[stair-step]; } }
62. Unique Paths Medium
for (int i = 1; i < m; ++i) { for (int j = 1; j < n; ++j) { dp[i][j] = dp[i][j-1] + dp[i-1][j]; } }
1155. Number of Dice Rolls With Target Sum Medium
for (int rep = 1; rep <= d; ++rep) { vector<int> new_ways(target+1); for (int already = 0; already <= target; ++already) { for (int pipe = 1; pipe <= f; ++pipe) { if (already - pipe >= 0) { new_ways[already] += ways[already - pipe]; new_ways[already] %= mod; } } } ways = new_ways; }
Note,备注
Some questions point out the number of repetitions, in that case, add one more loop to simulate every repetition.
688. Knight Probability in Chessboard Medium
494. Target Sum Medium
377. Combination Sum IV Medium
935. Knight Dialer Medium
1223. Dice Roll Simulation Medium
416. Partition Equal Subset Sum Medium
808. Soup Servings Medium
790. Domino and Tromino Tiling Medium
801. Minimum Swaps To Make Sequences Increasing
673. Number of Longest Increasing Subsequence Medium
63. Unique Paths II Medium
576. Out of Boundary Paths Medium
1269. Number of Ways to Stay in the Same Place After Some Steps Hard
1220. Count Vowels Permutation Hard
Generate problem statement for this pattern
Given a set of numbers find an optimal solution for a problem considering the current number and the best you can get from the left and right sides.
Find all optimal solutions for every interval and return the best possible answer.
// from i to j dp[i][j] = dp[i][k] + result[k] + dp[k+1][j]
Get the best from the left and right sides and add a solution for the current position.
for(int l = 1; l<n; l++) { for(int i = 0; i<n-l; i++) { int j = i+l; for(int k = i; k<j; k++) { dp[i][j] = max(dp[i][j], dp[i][k] + result[k] + dp[k+1][j]); } } } return dp[0][n-1]
1130. Minimum Cost Tree From Leaf Values Medium
for (int l = 1; l < n; ++l) { for (int i = 0; i < n - l; ++i) { int j = i + l; dp[i][j] = INT_MAX; for (int k = i; k < j; ++k) { dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + maxs[i][k] * maxs[k+1][j]); } } }
96. Unique Binary Search Trees Medium
1039. Minimum Score Triangulation of Polygon Medium
546. Remove Boxes Medium
1000. Minimum Cost to Merge Stones Medium
312. Burst Balloons Hard
375. Guess Number Higher or Lower II Medium
General problem statement for this pattern can vary but most of the time you are given two strings where lengths of those strings are not big
Given two stringss1
ands2
, returnsome result
.
Most of the problems on this pattern requires a solution that can be accepted in O(n^2) complexity.
// i - indexing string s1 // j - indexing string s2 for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { if (s1[i-1] == s2[j-1]) { dp[i][j] = /*code*/; } else { dp[i][j] = /*code*/; } } }
If you are given one string s
the approach may little vary
for (int l = 1; l < n; ++l) { for (int i = 0; i < n-l; ++i) { int j = i + l; if (s[i] == s[j]) { dp[i][j] = /*code*/; } else { dp[i][j] = /*code*/; } } }
1143. Longest Common Subsequence Medium
for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { if (text1[i-1] == text2[j-1]) { dp[i][j] = dp[i-1][j-1] + 1; } else { dp[i][j] = max(dp[i-1][j], dp[i][j-1]); } } }
647. Palindromic Substrings Medium
for (int l = 1; l < n; ++l) { for (int i = 0; i < n-l; ++i) { int j = i + l; if (s[i] == s[j] && dp[i+1][j-1] == j-i-1) { dp[i][j] = dp[i+1][j-1] + 2; } else { dp[i][j] = 0; } } }
516. Longest Palindromic Subsequence Medium
1092. Shortest Common Supersequence Medium
72. Edit Distance Hard
115. Distinct Subsequences Hard
712. Minimum ASCII Delete Sum for Two Strings Medium
5. Longest Palindromic Substring Medium
The general problem statement for this pattern is forgiven situation decide whether to use or not to use the current state. So, the problem requires you to make a decision at a current state.
Given a set of values find an answer with an option to choose or ignore the current value.
If you decide to choose the current value use the previous result where the value was ignored; vice-versa, if you decide to ignore the current value use previous result where value was used.
// i - indexing a set of values // j - options to ignore j values for (int i = 1; i < n; ++i) { for (int j = 1; j <= k; ++j) { dp[i][j] = max({dp[i][j], dp[i-1][j] + arr[i], dp[i-1][j-1]}); dp[i][j-1] = max({dp[i][j-1], dp[i-1][j-1] + arr[i], arr[i]}); } }
198. House Robber Easy
for (int i = 1; i < n; ++i) { dp[i][1] = max(dp[i-1][0] + nums[i], dp[i-1][1]); dp[i][0] = dp[i-1][1]; }
121. Best Time to Buy and Sell Stock Easy
714. Best Time to Buy and Sell Stock with Transaction Fee Medium
309. Best Time to Buy and Sell Stock with Cooldown Medium
123. Best Time to Buy and Sell Stock III Hard
188. Best Time to Buy and Sell Stock IV Hard
I hope these tips will be helpful
第二个材料,是下面这个LeetCode最近的帖子,列出了LC上面的DP的经典题目:
第三个,则是educative上面的DP课:
该门课程中, 作者将DP的问题分成以下几类:
0/1 Knapsack,0/1背包问题
Equal Subset Sum Partition,相等子集划分问题
Subset Sum,子集和问题
Minimum Subset Sum Difference,子集和的最小差问题
Count of Subset Sum,相等子集和的个数问题
Target Sum,寻找目标和的问题
Unbounded Knapsack,无限背包
Rod Cutting,切钢条问题
Coin Change,换硬币问题
Minimum Coin Change,凑齐每个数需要的最少硬币问题
Maximum Ribbon Cut,丝带的最大值切法
Fibonacci numbers,斐波那契数列问题
Staircase,爬楼梯问题
Number factors,分解因子问题
Minimum jumps to reach the end,蛙跳最小步数问题
Minimum jumps with fee,蛙跳带有代价的问题
House thief,偷房子问题
Longest Palindromic Subsequence,最长回文子序列
Longest Palindromic Substring,最长回文子字符串
Count of Palindromic Substrings,最长子字符串的个数问题
Minimum Deletions in a String to make it a Palindrome,怎么删掉最少字符构成回文
Palindromic Partitioning,怎么分配字符,形成回文
Longest Common Substring,最长相同子串
Longest Common Subsequence,最长相同子序列
Minimum Deletions & Insertions to Transform a String into another,字符串变换
Longest Increasing Subsequence,最长上升子序列
Maximum Sum Increasing Subsequence,最长上升子序列和
Shortest Common Super-sequence,最短超级子序列
Minimum Deletions to Make a Sequence Sorted,最少删除变换出子序列
Longest Repeating Subsequence,最长重复子序列
Subsequence Pattern Matching,子序列匹配
Longest Bitonic Subsequence,最长字节子序列
Longest Alternating Subsequence,最长交差变换子序列
Edit Distance,编辑距离
Strings Interleaving,交织字符串
大家可以先把以上35个题目练熟,这样DP到达中等水平肯定是okay了的。再加以训练和提高。突破算法的硬骨头不在话下。一定要按照三种方式对照起来练。
这个平台的介绍,看这个文章:
Happy Dynamic Programming!
今天的推送我觉得是所有去夜店蹦迪、电音节的人都应该看的一篇文章。
tututu先讲个真实发生过的故事:
主人公是个白纸男孩,我们就叫小白吧。
小白去蹦迪认识了朋友的朋友Yuki,Yuki是个留学生、老夜店玩咖,一看到小白跟个宝藏男孩一样,就疯狂的去倒追小白,两个人就在一起了。
在一起之后,Yuki就经常会去小白家里睡觉。
但因为Yuki在美国留学的时候喜欢飞叶子(大麻),每次事后就喜欢在小白家里自己卷上一根。
一次、两次、三次…小白实在受不来了,他是个小白纸啊,咋可能找个会飞叶子的女朋友啊?
两个人就吵架,小白说:“你以后不能碰这种东西了。”
Yuki就跟他说美国叶子合法之云云,两个人越吵越厉害,小白气坏了说我现在就报警叫警察抓你!
结果Yuki也是个岔道B,她说你报警就报警,我他妈现在就自己举报我自己。
警察就来了,一到现场,好,看到叶子了,给小白和Yuki验尿,小白没事,Yuki倒闭了,警察跟说Yuki得被关进去十四天,完了Yuki就进去了。
行政拘留十四天,第一次吸毒被抓都是这个处理结果。
接下来就到了这个故事最他妈精彩的部分了——
小白知道自己没事啊,就问警察叔叔说我要不先回家了?明天还得早起去上班呢。
警察叔叔笑了笑,就跟小白说你更走不了。
“吧哒”,一个手铐跟呼啦圈一样拷小白手上了,“您因为涉嫌容留他人吸毒,请配合调查。”
那一刻小白的心里真的是日了大狗了。
两个月后,法院判决结果出来了:
小白在三次明知女友在自己家吸毒的情况下,仍然容留她,为其提供吸毒场所,触犯了《中华人民共和国刑法》,判有期徒刑6个月,罚款5000元。
你是不是觉得这个故事在扯犊子?
你会觉得,WTF??小白连他妈碰都没碰,他简直太无辜了!
但对不起,这个故事就是现实中的判例:
而且判的合理合法,没有任何错误。
因为根据《中华人民共和国刑法》,容留他人吸毒的量刑标准就是三年以下有期徒刑,这个跟你吸不吸没有任何关系。
你说那我假如夜店认识了一个女生,我把她带回酒店满分,但她却要打气、飞叶子,我根本不碰这些东西,我就是在旁边守株待兔玩手机呢,我这也得被判刑么?
是的,如果缉毒警察突击进来,那你就拉闸了。
要是她还带了几个女生一起,是在你酒店房间高轰趴,那情节更严重,还得加刑。
你得被判半年,那个害你入狱的岔道B,没准被关了14天就出来了,还能去监狱探访你呢。
她还会隔着玻璃跟你说,兄弟你太惨了,我就是被关看守所里而已,环境还不错呢,你咋就进监狱了呢?等兄弟你出来之后我请你蹦迪好吧!
之所以要写这篇文章,是因为昨天朋友跟我吐槽了她以前一个蹭住的朋友,那个朋友就天天蹭住在她家里,她那个蹭住的朋友不仅每天带不同的男生回家,还玩东西。
我朋友给我吐槽的时候,她就根本不知道她已经触犯了刑法里“容留他人吸毒”这条罪名。
这也让我意识到,大部分的蹦迪选手也好、电音节Raver也好,其实都缺乏法律常识的,不知道有一条罪名是“容留他人吸毒”,总觉得“我不碰,就一点关系都没有”,真别等到自己被关半年,才后悔自己是个法盲。
我不知道这个数据是不是正确的:
但根据2019年国家禁毒办发布的中国毒品形势报告,登记在册的吸毒人员是214.8W名,这个群体其实鱼龙混杂,人们总觉得吸毒的都是黑社会、站街女,其实不是。
富二代、女艺人、留学生、网红、导演、金融…各行各业都有,所以如果你常去电音节和夜店,总会不可避免的接触到这类人。
这是很常出现的一种情况:
你知道对方吸毒,对方却不会像电影里一样教唆你吸毒,反而是在你好奇的时候严厉呵斥你,让你别碰这种东西。
而你自己也有一定的自制力、明辨是非的能力,不碰那些东西。
你会发现对方也并不像电影里描绘的那样精神错乱,反而温和有礼,跟你还很聊得来,而且人家还有正当工作,并且杰出优秀。
于是你们渐渐的变成了好友,你们会一起去电音节或夜店,之后你们还会一起在酒店的房间里轰趴、小酌。
他有时候还会来你的城市,你会直接让他住在你家里,因为你信的过他的人品。
他会在你的酒店房间、你家玩点东西什么的,当然每次你好奇的时候,他都让你别碰这些。
结果有一天恰好是电音节的时候,一个瘾君子被警察抓了,他又跟警察报出了很多他知道的名字,于是你朋友就被点了,警察来抓你朋友,直接敲开了你酒店房间的门。
一进门,你在沙发上玩手机喝酒上头呢,你朋友在那里玩东西。
最后的结果是什么呢?
你朋友被关14天,而且他是行政处罚,你得被关半年甚至更久,你这个叫刑事处罚。
所以我希望所有蹦迪选手、Raver都能记住这一点:
不管你碰不碰那些东西,请务必不要收留任何人在你家吸毒。
也别随便让吸毒的人来你家,你咋知道人家会不会因为你抢过他的小哥哥/小姐姐,所以故意接近你,去你家吸毒,自己举报自己,然后跟你来个同归于尽呢?
最后我想来理性的谈谈毒品和法律这个问题。
不可否认吸毒对于灵感创作、人生顿悟的帮助,比如许多画家、作家、音乐人都通过毒品创造了传世的佳作,比如柯尔律、济慈、勃朗宁、伯勒斯、梅勒、金斯堡、迪克、卡林、斯蒂芬金,甚至连白求恩都是经过一段毒品、糜烂的生活后,才寻找到了人生真正的意义。
但须知,世界上没有对错,所谓价值观即是多种利益之妥协,法律乃是社会契约所成,法律的本质目的在于维系社会的稳定,在大部分人生命权益的自由和小部人创作灵感的自由中进行取舍是困难的,这就像“电车难题”一样,从来不会有一个答案,每个人的答案都是主观的。
但如果我是立法者,我仍然会坚定的在毒品和枪支两件事上以重刑。
很多自由派会批评社会过于追求稳定,但须知一个人生在中国,他大可以在凌晨三点放心的出门,他不用担心自己乘坐的地铁会突然爆炸,更不用担心自己的办公室会突然遭到枪击,甚至连新冠流行你都会像没事人一样轻松。
但生在其他国家,你必须为这些事情担心,这个世界上没有能够兼顾到所有人、所有事的法律,没有尽善尽美的制度,任何一种法律和制度都是在各种问题上进行取舍得出一个答案而已。
很高兴认识你,我是tututu、一个从衡中考到上财之后不务正业的蹦迪博主,并莫名其妙写着写着就成了中国最大的蹦迪公众号,如果你想看更多有趣的蹦迪文章,请关注公众号 满分激光枪