目录
预备知识
1.Fibonacci
class Solution {
public:
int Fibonacci(int n)
{
vector<int> F(n+1,0);
//初始化
F[0] = 0;
F[1] = 1;
//解决子问题
for(int i = 2 ;i <= n; ++i)
{
F[i] = F[i-1] +F[i-2];
}
//返回值
return F[n];
}
};
上述解法的空间复杂度为O(n)
其实F(n)只与它相邻的前两项有关,所以没有必要保存所有子问题的解
只需要保存两个子问题的解就可以
下面方法的空间复杂度将为O(1)
class Solution {
public:
int Fibonacci(int n)
{
if(n == 1)
return 1;
//初始化
int f1 = 0,f2 = 1;
int ret = 0;
//解决子问题
for(int i = 2 ;i <= n; ++i)
{
ret = f1 + f2 ;
f1 = f2;
f2 = ret;
}
//返回值
return ret;
}
};
2.最大连续子数组和
class Solution {
public:
int maxSubArray(vector<int>& nums)
{
//直接利用原数组进行操作
int ret = nums[0];
for(int i = 1 ; i < nums.size() ; ++i)
{
nums[i] = max(nums[i-1] + nums[i] , nums[i]);
if(nums[i] > ret)
ret = nums[i];
}
return ret;
}
};
3.单词拆分
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict)
{
unordered_set<string> wordDictSet; //使用unordered_set便于进行查找单词
for (auto word: wordDict)
{
wordDictSet.insert(word);
}
vector<bool> dp(s.size()+1,false);
dp[0] = true; //初始化
// j < i && f(j) && [j,i]
for(int i = 1 ; i<=s.size() ; ++i)
{
for(int j = 0; j < i ;++j)
{
if(dp[j] && wordDictSet.find(s.substr(j, i - j)) != wordDictSet.end())
{
dp[i] = true;
break;
}
}
}
return dp[s.size()];
}
};
4. 三角矩阵
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle)
{
vector<vector<int>> ret = triangle; //开辟二维数组+初始化
int row = triangle.size();
for(int i = 1 ;i < row ; ++i)
{
for(int j = 0 ; j < triangle[i].size() ;++j)
{
if(j == 0)//处理左边界
{
ret[i][j] += ret[i-1][j];
continue;
}
if(j == triangle[i].size()-1)//处理右边界
{
ret[i][j] += ret[i-1][j-1];
continue;
}
ret[i][j] += min(ret[i-1][j-1] , ret[i-1][j]);
}
}
int minRoute = ret[row-1][0];
for(int i = 1; i < triangle[row-1].size() ;++i)
{
if(minRoute > ret[row-1][i])
{
minRoute = ret[row-1][i];
}
}
return minRoute;
}
};
这种逆向思维不需要考虑边界,也不需要最后寻找最小值,直接返回F(0,0)即可
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle)
{ //从底部到顶部
int row = triangle.size();
for(int i = row - 2 ; i >= 0 ;--i)
{
for(int j = 0 ; j < triangle[i].size() ; ++j )
{
triangle[i][j] += min(triangle[i+1][j] , triangle[i+1][j+1]);
}
}
return triangle[0][0];
}
};
5.路径总数
class Solution {
public:
int uniquePaths(int m, int n)
{
//申请空间 + 初始化
vector<vector<int>> route(m, vector<int>(n , 1));
for(int i = 1 ; i < m ; ++i )
{
for(int j = 1 ; j < n ; ++j)
{
route[i][j] = route[i-1][j] + route[i][j-1] ;
}
}
return route[m-1][n-1];
}
};
6.最小路径和
class Solution {
public:
int minPathSum(vector<vector<int>>& grid)
{
int row = grid.size();
int col = grid[0].size();
//第一行初始化
for(int i = 1 ; i < col ; ++i)
{
grid[0][i] += grid[0][i-1] ;
}
//第一列初始化
for(int i = 1 ; i < row ; ++i)
{
grid[i][0] += grid[i-1][0] ;
}
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]);
}
}
return grid[row-1][col-1];
}
};
7.背包问题
int backPackII(int m, vector<int> &A, vector<int> &V)
{
int row = A.size();
vector<vector<int>> dp(row + 1 , vector<int>(m + 1 , 0));
for(int i = 1 ; i < (row+1) ; ++i)
{
for(int j = 1 ; j < (m+1) ; ++j)
{
//第i个商品在A中对应的索引为i-1: i从1开始
//如果第i个商品大于j,说明放不下, 所以(i,j)的最大价值和(i-1,j)相同
if(j < A[i-1]) //放不下
{
dp[i][j] = dp[i-1][j];
}
else
{
dp[i][j] = max(dp[i-1][j] , dp[i-1][j - A[i-1]] + V[i-1] );
}
//如果可以装下,分两种情况,装或者不装
//如果不装,则即为(i-1, j)
//如果装,需要腾出放第i个物品大小的空间: j - A[i-1],装入之后的最大价值即为
//(i - 1,j - A[i-1]) + 第i个商品的价值V[i - 1]
//最后在装与不装中选出最大的价值
}
}
return dp[row][m];
}
};
优化算法:
上面的算法在计算第i行元素时,只用到第i-1行的元素,所以二维的空间可以优化为一维空间
但是如果是一维向量,需要从后向前计算,因为后面的元素更新需要依靠前面的元素未更新(模拟二维矩阵的上一行的值)的值
int backPackII(int m, vector<int> &A, vector<int> &V)
{
int row = A.size();
vector<int> dp(m+1 , 0); //一维数组,从后向前更新 , 不需要列
for(int i = 1 ; i < (row+1) ; ++i)
{
for(int j = m ; j > 0 ; --j)
{
if(j >= A[i-1]) //从后向前计算,利用 i-1列的值
{
dp[j] = max(dp[j] , dp[j - A[i-1]] + V[i-1] );
}
}
}
return dp[m];
}
8. 回文串分割
class Solution {
public:
bool ispal(const string& s, int left,int right)
{
while(left < right)
{
if(s[left++] != s[right--])
return false;
}
return true;
}
int minCut(string s)
{
int sz = s.size();
vector<int> dp(sz+1 , 0);
for(int i = 1 ; i <= sz ; ++i) //初始化
{
dp[i] = i-1;
}
for(int i = 2 ; i <= sz ; ++i )
{
if( ispal(s,0,i-1) ) //如果整体是回文直接返回
{
dp[i] = 0;
continue;
}
for(int j = i-1 ; j >= 1 ; --j)//整体不是回文,则需要一次根据前面的值进行判断
{
if( ispal(s, j , i-1) )
{
dp[i] = min(dp[i] , dp[j]+1);
}
}
}
return dp[sz];
}
};
判断是否为回文串也需要进行动态规划进行优化
class Solution {
public:
vector<vector<bool>> getMat(string s)
{
int len = s.size();
vector<vector<bool>> mat = vector<vector<bool>>(len, vector<bool>(len, false));
for (int i = len - 1; i >= 0; --i)//i为什么要从右向左, 要利用 F(i+1, j-1)的值进行判断
{ //
for (int j = i; j < len; ++j)
{
if (j == i)
{
// 单字符为回文字符串
mat[i][j] = true;
}
else if (j == i + 1)
{
// 相邻字符如果相同,则为回文字符串
mat[i][j] = (s[i] == s[j]);
}
else
{
// F(i,j) = {s[i]==s[j] && F(i+1,j-1)
// j > i+1
mat[i][j] = ((s[i] == s[j]) && mat[i + 1][j - 1]);
}
}
}
return mat;
}
int minCut(string s)
{
int sz = s.size();
vector<int> dp(sz+1 , 0);
vector<vector<bool>> mat = getMat(s);
for(int i = 1 ; i <= sz ; ++i) //初始化
{
dp[i] = i-1;
}
for(int i = 2 ; i <= sz ; ++i )
{
if(mat[0][i-1])
{
dp[i] = 0;
continue;
}
for(int j = 1 ; j <= i-1 ; ++j)//整体不是回文,则需要一次根据前面的值进行判断
{
if( mat[j][i-1])
{
dp[i] = min(dp[i] , dp[j]+1);
}
}
}
return dp[sz];
}
};
9. 编辑距离
class Solution {
public:
int minDistance(string word1, string word2)
{
int row = word1.size();
int col = word2.size();
if(row == 0)
return col;
if(col == 0)
return row;
vector<vector<int>> dp(row+1 , vector<int>(col+1,0));
for(int i = 0 ; i <= row ; ++i)
dp[i][0] = i;
for(int j = 0 ; j <= col ; ++j)
dp[0][j] = j;
for(int i = 1 ; i <= row ; ++i)
{
for(int j = 1 ; j <= col ; ++j)
{
//插入 删除
dp[i][j] = min(dp[i-1][j]+1 , dp[i][j-1]+1 );
//替换操作
if(word1[i-1] == word2[j-1])
{
dp[i][j] = min(dp[i][j] , dp[i-1][j-1]);
}
else
{
dp[i][j] = min(dp[i][j] , dp[i-1][j-1]+1);
}
}
}
return dp[row][col];
}
};
10.不同子序列
class Solution {
public:
int numDistinct(string s, string t)
{
int row = s.size();
int col = t.size();
if(row == 0 || col == 0)
return 0;
vector<vector<size_t>> dp(row+1 , vector<size_t>(col+1 , 0));
for(int i = 0 ; i <= row ; ++i) //初始化
dp[i][0] = 1;
for(int i =1 ; i <= row ; ++i )
{
for(int j = 1 ; j <= col ;++j)
{
if(i < j)
{
dp[i][j] = 0;
}
else
{
if( s[i-1] == t[j-1] )
{
dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
}
else
{
dp[i][j] = dp[i-1][j];
}
}
}
}
return dp[row][col];
}
};
此题也可优化空间复杂度为O(n)
f[i][j] 只和 f[i - 1][j], f[i - 1][j - 1]有关
类似于背包问题,可以用一维数组保存上一行的结果,每次从最后一列更新元素值
class Solution {
public:
int numDistinct(string s, string t)
{ //因为我们只使用了上一行的数据,所以我们只需要一行即可
int row = s.size();
int col = t.size();
if(row == 0 || col == 0)
return 0;
vector<unsigned int> dp(col+1, 0);
dp[0] = 1;
for(int i = 1 ; i <= row ; ++i)
{
for(int j = col ; j >= 1 ; --j) //一般使用一行就需要从后向前更新,防止值被覆盖
{
if(i < j)
{
dp[j] = 0;
}
else
{
if( s[i-1] == t[j-1] )
{
dp[j] = dp[j-1] + dp[j];
}
}
}
}
return dp[col];
}
};
总结