目录
一、动态规划原理
1、基本思想
问题的最优解如果可以由子问题的最优解推导得到,则可以先求解子问题的最优解,在构造原问题的最优解;若子问题有较多的重复出现,则可以自底向上从最终子问题向原问题逐步求解。
2、使用条件:
可分为多个相关子问题,子问题的解被重复使用
Optimal substructure(最优子结构):
一个问题的优化解包含了子问题的优化解,缩小子问题集合,只需那些优化问题中包含的子问题,降低实现复杂性 我们可以自下而上的
Subteties(重叠子问题):在问题的求解过程中,很多子问题的解将被多次使用。
3、动态规划算法的设计步骤
- 分析优化解的结构
- 递归地定义最优解的代价
- 自底向上地计算优化解的代价保存之,并获取构造最优解的信息
- 根据构造最优解的信息构造优化解
4、动态规划特点
- 把原始问题划分成一系列子问题;
- 求解每个子问题仅一次,并将其结果保存在一个表中,以后用到时直接存取,不重复计算,节省计算时间
- 自底向上地计算。
- 整体问题最优解取决于子问题的最优解(状态转移方程)(将子问题称为状态,最终状态的求解归结为其他状态的求解)
二、LeetCode例题
1、word-break-ii
(1)题目描述:
给定一个字符串s和一组单词dict,在s中添加空格将s变成一个句子,使得句子中的每一个单词都是dict中的单词
返回所有可能的结果
例如:给定的字符串
s =“catsanddog”, dict =[“cat”, “cats”, “and”, “sand”, “dog”].
返回的结果为
[“cats and dog”, “cat sand dog”].
Question:
Given a string s and a dictionary of words dict, add spaces in s to construct a sentence where each word is a valid dictionary word.
Return all such possible sentences.
For example, given s =“catsanddog”, dict =[“cat”, “cats”, “and”,
“sand”, “dog”].
A solution is[“cats and dog”, “cat sand dog”].
(2)思路分析:
利用一维容器数组存储字典中出现过的word,并且记为True,未出现记为False
vector<type_of_bool> *cstr = new vector<type_of_bool>[s.length()];
*cstr | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
0 | c | ca | cat | cats | catsa | catsan | catsand | catsandd | catsanddo | catsanddog |
1 | a | at | ats | atsa | atsan | atsand | atsandd | atsanddo | atsanddog | |
2 | t | ts | tsa | tsan | tsand | tsandd | tsanddo | tsanddog | ||
3 | s | sa | san | sand | sandd | sanddo | sanddog | |||
4 | a | an | and | andd | anddo | anddog | ||||
5 | n | nd | ndd | nddo | nddog | |||||
6 | d | dd | ddo | ddog | ||||||
7 | d | do | dog | |||||||
8 | o | og | ||||||||
9 | g |
递归遍历顺序是先搜索i到串尾的子串,若子串在dict里,再搜索串头到i的子串。
利用mystring临时存储每次遍历得到的正确分割,result存储所有可能的结果构成的字符串。
(3)牛客网线上测试代码以及注释:
class Solution {
private:
vector<string> mystring;//临时存储每次遍历得到的正确分割
vector<string> result;//存储所有可能的结果构成的字符串
vector<bool> *cstr;//指向一维容器数组的指针,数组的元素是vector容器
public:
vector<string> wordBreak(string s, unordered_set<string> &dict) {
//构建子字符串对应的bool数组来存储信息,true表示该子串在字典中存在
cstr = new vector<bool>[s.length()];
for (int i = 0; i < s.length(); i++) {
for (int j = i; j < s.length(); j++) {
if (dict.end() != dict.find(s.substr(i, j - i + 1))) {
cstr[i].push_back(true);
}
else {
cstr[i].push_back(false);
}
}
}
//将字符串按照数字存储内容分割
handleStr(s.size() - 1, s);
return result;
}
void handleStr(int i, string s) {//字符串s和数组查找序号
if (i == -1) {//递归终止条件
string str;
for (int j = mystring.size() - 1; j >= 0; j--) {
str += mystring[j];
if (j != 0)
str += " ";
}
result.push_back(str);
}
else {
for (int k = i; k >= 0; k--) {//自底向上自左向右遍历数组
if (cstr[k][i - k]) {
mystring.push_back(s.substr(k, i - k + 1));
handleStr(k - 1, s);
mystring.pop_back();
}
}
}
}
};
2、candy
(1)题目描述
有N个小朋友站在一排,每个小朋友都有一个评分
你现在要按以下的规则给孩子们分糖果:
- 每个小朋友至少要分得一颗糖果
- 分数高的小朋友要他比旁边得分低的小朋友分得的糖果多
你最少要分发多少颗糖果?
There are N children standing in a line. Each child is assigned a
rating value. You are giving candies to these children subjected to
the following requirements:Each child must have at least one candy. Children with a higher rating
get more candies than their neighbors. What is the minimum candies you
must give?
(2)思路分析
假设n个孩子的评分如下:
学生编号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
ratings[i] | 5 | 1 | 4 | 3 | 3 | 2 | 6 |
有i个学生时的糖果数如下图:
candys[i] | |||||||
---|---|---|---|---|---|---|---|
i=0 | 1 | ||||||
i=1 | 2 | 1 | |||||
i=2 | 2 | 1 | 2 | ||||
i=3 | 2 | 1 | 2 | 1 | |||
i=4 | 2 | 1 | 2 | 1 | 1 | ||
i=5 | 2 | 1 | 3 | 2 | 2 | 1 | |
i=6 | 2 | 1 | 3 | 2 | 2 | 1 | 2 |
- 与前面的邻居比较,前向遍历权重数组ratings,如果ratings[i]>ratings[i-1],则cands[i]=cands[i-1]+1;
- 与后面的邻居比较,后向遍历权重数组ratings,如果ratings[i-1]>ratings[i]且cands[i]>=cands[i-1],则更新cands,cands[i]=cands[i+1]+1;
- 对cands求和即为最少需要的糖果。
(3)牛客网线上测试代码以及注释:
class Solution {
public:
int candy(vector<int> &ratings) {
//特殊值处理
int len = ratings.size();
if (len == 0)
return 0;
if (len == 1)
return 1;
//每个孩子初始化 有1个糖果
vector<int> candys(len, 1);
//从左向右扫描 与前比
for (int i = 1; i < len; i++) {
if (ratings[i] > ratings[i - 1])
candys[i] = candys[i - 1] + 1;
}
//从右向左扫描 与后比
for (int i = len - 1; i > 0; i--) {
if (ratings[i] < ratings[i - 1] && candys[i] >= candys[i - 1]) {
candys[i - 1] = candys[i] + 1;
}
}
//计算最优值
int sum = 0;
for (int i = 0; i < len; i++) {
sum += candys[i];
}
return sum;
}
};
3、palindrome-partitioning-ii
(1)题目描述
给出一个字符串s,分割s使得分割出的每一个子串都是回文串,计算将字符串s分割成回文分割结果的最小切割数。
例如:给定字符串s=“aab”,
返回1,因为回文分割结果[“aa”,“b”]是切割一次生成的。
Given a string s, partition s such that every substring of the
partition is a palindrome. Return the minimum cuts needed
for a palindrome partitioning of s.For example, given s =“aab”, Return 1 since the palindrome partitioning[“aa”,“b”]could be produced using 1 cut.
(2)思路分析
定义动态规划数组dp,dp[i]的含义是子串str[0…i]至少需要切割几次,才能把str[0…i]全部切成回文子串,那么dp[len-1]就是最后的结果。
从左往右依次计算dp[i]的值,i 初始为0,具体计算过程如下:
A、假设 j 处在 0 到 i 之间,如果str[j…i]是回文串,那么dp[i]的值可能是dp[j-1] + 1,其含义是在str[0…i]上,既然str[j…i]是回文串,那么它可以自己作为一个分割的部分,剩下的部分str[0…j-1]继续做最经济的分割,也就是dp[j-1]的值。
B、根据步骤A的方式,让 j 在 i 到 0 的位置上枚举,那么所有可能中最小值就是dp[i]的值,
即dp[i] = min{dp[j-1]+1 (0<= j <= i,且str[j…i]必须是回文串)}。
C、如何快速方便的判断str[j…i]是否为回文串?
1)定义一个二维数组p,如果p[j][i]为True,表示str[j…i]是回文串,否则不是。在计算dp过程中,希望能够同步、快速的计算出矩阵p。
2)p[j][i]如果为True,一定来自以下三种情况:
- str[j][i]由一个字符组成
- str[j][i]由两个字符组成且两个字符相等
- str[j][i]由多个字符组成,str[j] == str[i]且p[j+1][i-1] == True。
3)在计算dp数组的过程中,位置i是从左向右依次计算的。而对于每一个i来说,又依次从 i 位置向左遍历所有的位置,以此来决策dp[i]。所以对于p[j][i]来说,p[j+1][i-1]一定已经计算过。
(3)牛客网测试代码
class Solution
{
public:
int minCut(string s)
{
int len = s.size();
vector<int> vec(len, 0);//vec[i]存储str[0...i]的最少回文串数目
vector< vector<bool> > bvec(len, vector<bool>(len, false));//判断str[j..i]是否是回文串
for (int i = 0; i < len; i++)
{
vec[i] = i;
for (int j = i; j >= 0; j--)
{
if ((s[i] == s[j]) && (i - j < 2 || bvec[i - 1][j + 1]))//子串str[j...i]是回文串的判断条件
{
bvec[i][j] = true;
if (j == 0)
vec[i] = min(vec[i], 0);
else
vec[i] = min(vec[i], vec[j - 1] + 1);
}
}
}
};
-----------------------------------------------------------------------------------------------------------------------------------------------------
如果本文对你有所帮助,请不要忘了点赞、收藏哦!!!
-----------------------------------------------------------------------------------------------------------------------------------------------------
三、相关参考
参考一:https://www.cnblogs.com/hithongming/p/9229871.html
参考二:https://blog.csdn.net/i_am_bird/article/details/78153666?utm_source=blogxgwz0
参考三:https://blog.csdn.net/xinwenhuayu/article/details/100899478