动态规划(Dynamic Programming)
Posted 森明帮大于黑虎帮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划(Dynamic Programming)相关的知识,希望对你有一定的参考价值。
文章目录
一、Dynamic Programming定义
动态规划是分治思想的延伸,通俗一点来说就是大事化小,小事化无的艺术。
在将大问题化解为小问题的分治过程中,保存对这些小问题已经处理好的结果,并供后面处理更大规模的问题时直接使用这些结果。
- 动态规划具备了以下三个特点:
- 把原来的问题分解成了几个相似的子问题。
- 所有的子问题都只需要解决一次。
- 储存子问题的解。
动态规划的本质,是对问题状态的定义和状态转移方程的定义(状态以及状态之间的递推关系)。
- 动态规划问题一般从以下四个角度考虑:
- 状态定义。
- 状态间的转移方程定义。
- 状态的初始化。
- 返回结果。
状态定义的要求:定义的状态一定要形成递推关系。
一句话概括:三特点四要素两本质。
适用场景:最大值/最小值, 可不可行, 是不是,方案个数。
二、斐波那契数列
class Solution {
public:
int Fibonacci(int n)
{
/*if(n==0||n==1)
{
return n;
}
else
{
return Fibonacci(n-1)+Fibonacci(n-2);
}*/
if(n==0||n==1)
{
return n;
}
int n1=0;
int n2=1;
int n3=0;
while(n>1)
{
n3=(n1+n2)%1000000007;
n1=n2;
n2=n3;
n--;
}
return n3;
}
};
三、跳台阶扩展问题
class Solution {
public:
int jumpFloorII(int number)
{
if(number==0||number==1)
{
return number;
}
else
{
int ret=1;
while(number>1)
{
ret=ret*2;
number--;
}
return ret;
}
}
};
四、最大连续子数组和
class Solution {
public:
int FindGreatestSumOfSubArray(vector<int> array)
{
if(array.size()==0)
{
return 0;
}
int sum=array[0];
for(int i=0;i<array.size();i++)
{
array[i]=max(array[i-1]+array[i],array[i]);
sum=max(sum,array[i]);
}
return sum;
}
};
五、背包问题
单词就是物品,字符串s就是背包,单词能否组成字符串s,就是问物品能不能把背包装满。拆分时可以重复使用字典中的单词,说明就是一个完全背包!
动规五部曲分析如下:
-
确定dp数组以及下标的含义:
dp[i] : 字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词。 -
确定递推公式:
如果确定dp[j] 是true,且 [j, i] 这个区间的子串出现在字典里,那么dp[i]一定是true。(j < i )。
所以递推公式是 if([j, i] 这个区间的子串出现在字典里 && dp[j]是true) 那么 dp[i] = true。
- dp数组如何初始化:
从递归公式中可以看出,dp[i] 的状态依靠 dp[j]是否为true,那么dp[0]就是递归的根基,dp[0]一定要为true,否则递归下去后面都都是false了。
那么dp[0]有没有意义呢?
dp[0]表示如果字符串为空的话,说明出现在字典里。
但题目中说了“给定一个非空字符串 s” 所以测试数据中不会出现i为0的情况,那么dp[0]初始为true完全就是为了推导公式。
下标非0的dp[i]初始化为false,只要没有被覆盖说明都是不可拆分为一个或多个在字典中出现的单词。
- 确定遍历顺序:
题目中说是拆分为一个或多个在字典中出现的单词,所以这是完全背包。
还要讨论两层for循环的前后循序。
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
本题最终要求的是是否都出现过,所以对出现单词集合里的元素是组合还是排列,并不在意!那么本题使用求排列的方式,还是求组合的方式都可以。
即:外层for循环遍历物品,内层for遍历背包 或者 外层for遍历背包,内层for循环遍历物品 都是可以的。
但本题还有特殊性,因为是要求子串,最好是遍历背包放在外循环,将遍历物品放在内循环。
如果要是外层for循环遍历物品,内层for遍历背包,就需要把所有的子串都预先放在一个容器里。(如果不理解的话,可以自己尝试这么写一写就理解了)。
所以最终我选择的遍历顺序为:遍历背包放在外循环,将遍历物品放在内循环。内循环从前到后。
1.单词拆分 (字符串分割)
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict)
{
unordered_set<string> wordSet(wordDict.begin(),wordDict.end());
vector<bool> dp(s.size()+1,false);
dp[0]=true;
for(int i=1;i<=s.size();i++) //遍历背包
{
for(int j=0;j<i;j++) //遍历物品
{
string word=s.substr(j,i-j); //substr(起始位置,截取的个数)
{
if(dp[j]&&wordSet.find(word)!=wordSet.end())
{
dp[i]=true;
}
}
}
}
return dp[s.size()];
}
};
- 时间复杂度:O(n^3),因为substr返回子串的副本是O(n)的复杂度(这里的n是substring的长度)。
- 空间复杂度:O(n)。
六、三角矩阵(Triangle)
普通解法:
动规做法:
class Solution {
public:
int minimumTotal(vector<vector<int> > &triangle)
{
if(triangle.empty())
{
return 0;
}
int row=triangle.size(); //行数
//起始位置(row-1,j)已经确定过了
//返回结果,最上面也就是开始的地方[0][0];
for(int i=row-2;i>=0;i--)
{
for(int j=0;j<=i;j++)
{
triangle[i][j]=min(triangle[i+1][j],triangle[i+1][j+1])+triangle[i][j];
}
}
return triangle[0][0];
}
};
七、路径总数I
class Solution {
public:
/**
*
* @param m int整型
* @param n int整型
* @return int整型
*/
int uniquePaths(int m, int n)
{
// write code here
if(m<1||n<1)
{
return 0;
}
vector<vector<int>> pathNum(m,vector<int>(n,1));
for(int i=1;i<m;i++ )
{
for(int j=1;j<n;j++)
{
pathNum[i][j]=pathNum[i-1][j]+pathNum[i][j-1];
}
}
return pathNum[m-1][n-1];
}
};
八、路径总数II
class Solution {
public:
/**
*
* @param obstacleGrid int整型vector<vector<>>
* @return int整型
*/
int uniquePathsWithObstacles(vector<vector<int> >& obstacleGrid)
{
// write code here
if(obstacleGrid.empty())
{
return 0;
}
int row=obstacleGrid.size();
int col=obstacleGrid[0].size();
vector<vector<int>> pathNum(row,vector<int>(col,0));
for(int i=0;i<row;i++)
{
if(obstacleGrid[i][0]==0)
{
pathNum[i][0]=1;
}
else
{
break;
}
}
for(int j=0;j<col;j++)
{
if(obstacleGrid[0][j]==0)
{
pathNum[0][j]=1;
}
else
{
break;
}
}
for(int i=1;i<row;i++)
{
for(int j=1;j<col;j++)
{
if(obstacleGrid[i][j]==0)
{
pathNum[i][j]=pathNum[i-1][j]+pathNum[i][j-1];
}
}
}
return pathNum[row-1][col-1];
}
};
九、最小路径和(Minimum Path Sum)
class Solution {
public:
/**
*
* @param grid int整型vector<vector<>>
* @return int整型
*/
int minPathSum(vector<vector<int> >& grid)
{
// write code here
if(grid.size()==0)
{
return 0;
}
int row=grid.size();
int col=grid[0].size();
for(int i=1;i<row;i++)
{
grid[i][0]=grid[i-1][0]+grid[i][0];
}
for(int j=1;j<col;j++)
{
grid[0][j]=grid[0][j-1]+grid[0][j];
}
for(int i=1;i<row;i++)
{
for(int j=1;j<col;j++)
{
grid[i][j]=min(grid[i-1][j],grid[i][j-1])+grid[i][j];
}
}
return grid[row-1][col-1];
}
};
十、背包问题
class Solution {
public:
/**
* @param m: An integer m denotes the size of a backpack
* @param A: Given n items with size A[i]
* @param V: Given n items with value V[i]
* @return: The maximum value
*/
int backPackII(int m, vector<int> &A, vector<int> &V)
{
// write your code here
int n=A.size();
if(n==0||m==0)
{
return 0;
}
vector<int> maxV(m+1,0);
for(int i=1;i<=n;i++)
{
for(int j=m;j>0;j--)
{
if(A[i-1]<=j)
{
maxV[j]=max(maxV[j],maxV[j-A[i-1]]+V[i-1]);
}
}
}
return maxV[m];
}
};
十一、回文串分割最小次数问题
class Solution {
public:
bool isPal(string& s,int start,int end)
{
while(start<end)
{
if(s[start]!=s[end])
{
return false;
}
start++;
end--;
}
return true;
}
int minCut(string s)
{
// write code here
vector<int> minC(s.size()+1);
for(int i=1;i<=s.size();i++)
{
minC[i]=i-1;
}
for(int i=2;i<=s.size();i++)
{
//判断整体
if(isPal(s, 0, i-1))
{
minC[i]=0;
continue;
}
for(int j=1;j<i;j++) //从1开始的
{
//j<i &&[j+1,i]
if(isPal(s, j,i以上是关于动态规划(Dynamic Programming)的主要内容,如果未能解决你的问题,请参考以下文章
动态规划(Dynamic Programming)LeetCode经典题目