动态规划算法
动态规划算法的核心思想是将复杂的问题拆解成子类问题,采用二维数组的方法模拟网格单元进行处理。如下所示:
0 | 0 | 0 | 0 | 0 | 0 | ... |
0 | ||||||
0 | ||||||
0 | ||||||
0 | ||||||
... |
通过上面的表格可以看到有两种区域,分别使用红色和绿色进行表示,红色表示新增的区域,而且该域内的值都是0;绿色区域表示实际的需要进行动态规划的区域。从上面的表格左上角开始,行、列所以你都是从0开始。
要理解动态规划算法,核心有两点:
- 动态规划算法是一个优化问题,那么上图中的绿色区域中的值应该就是要优化的值或者与之相关的值,即首先要搞明白我们要求什么最优解。举个例子背包问题中的绿色区域某个行列对应的网格的值就是对应的背包最大价值;最长公共子串问题,则需要遍历所有的网格单元求解最大值才能找到对应的最优解。
- 动态规划算法的一个思想就是将问题拆解为子问题与当前行列的问题进行求解。举个大家都知道的例子,求解最长公共子串问题可以拆分为不包含当列对应的字母区域的长度与这两个字母是否等的组合问题。
下面将我实现的一些动态规划算法写到下面,供大家参考理解。
#include <string>
#include <iostream>
#include <vector>
using namespace std;
//using namepsace std::literals
//使用动态规划算法实现两个字符串最长子串的长度
/*
* 用例设计
* 1. 没有任何公共子串
* 2.仅包含一个公共子串
* 3.公共子串在头部
* 4.公共子串在尾部
* 5.公共子串在中间
* 6.仅包含一个或者多个字符长度的公共子串
* 7.两个字符串完全相同
* 8.包含两段不同长度的公共子串
*/
int longetsSubstringLength(string str1, string str2)
{
int m = str1.length(); //求解第一个字符串的长度
int n = str2.length(); //求解第二个字符串的长度
vector<vector<int>> dp(m+1, vector<int>(n+1, 0)); //这里进行一个初始化,生成一个二维数组,形成动态规划特有的网格
//进行双层遍历,遵从自底向上的策略,dp[0][x]的值都是0,dp[x][0]的值也都是0,网格单元的索引是从dp[1][1]开始的,dp[0][x]与
//dp[x][0]都是默认值0
for(int i = 1; i <= m; i++)
{
for(int j = 1; j <= n; j++)
{
//如果该位置字符相等,那么就要获取其上一级的最大公共子串的值,即dp[i-1][j-1],如果i为1,那么就是dp[0][j-1];
//如果j为1,那么就是dp[i-1][0],这些值都是0
//如果这两个字符相等,那么需要获取它的左上角的值,取得该值后进行加1操作
if(str1[i-1] == str2[j-1])
{
dp[i][j] = dp[i-1][j-1] + 1;
}
//如果不相等,则需要dp[i][j]的值等于dp[i-1][j-1]
else{
dp[i][j] = 0;
}
}
}
int maxNumber = 0;
for(int i = 0; i <= m; i++)
{
for(int j = 0; j <= n; j++)
{
cout<<dp[i][j]<<" ";
if(dp[i][j] > maxNumber)
maxNumber = dp[i][j];
}
cout<<endl;
}
return maxNumber;
}
//两个字符串最长公共子串,返回的不是长度而是一个字符串,测试用同上
string longestSubstring(string str1, string str2)
{
int m = str1.length();
int n = str2.length();
int endIndex = 0; //公共字符串的最后一位
int maxLength = 0; //字符串最大长度<
vector<vector<int>> dp(m+1, vector<int>(n+1, 0)); //初始化二维数组
//动态规划算法,算法思想如下,问题分解为不包含这个字符串的字符串的最大公共字符串长度求解,每个网格内的数字的含义是
//若是相同的字符串则从左上角加一操作;若该位置两个字符不相等,则将该单元格数字设置为0,表示该位置的最长公共子串的长度为0
//这个最大公共子串的长度不是右下角最后一个网格的数据,而是要从所有的网格中找到最大的值。
for(int i = 1; i <= m; i++)
{
for(int j = 1; j <= n; j++)
{
//如果这个位置的字符相等,那么
if(str1[i-1] == str2[j-1])
{
dp[i][j] = dp[i-1][j-1] + 1;
if(dp[i][j] > maxLength)
{
maxLength = dp[i][j];
endIndex = i; //设置最后一个索引
}
}
//如果不相等,则设置为0
else
{
dp[i][j] = 0;
}
}
}
int maxNumber = 0;
for(int i = 0; i <= m; i++)
{
for(int j = 0; j <= n; j++)
{
cout<<dp[i][j]<<" ";
if(dp[i][j] > maxNumber)
maxNumber = dp[i][j];
}
cout<<endl;
}
cout <<__FUNCTION__<<" maxLength = "<<maxLength<<" endIndex = "<<endIndex <<endl;
if(maxLength == 0)
return "";
string str = str1.substr(endIndex-maxLength,maxLength);
return str;
}
//获取两个字符串最长公共子序列的长度
/*
****************************************************
*一.测试用例
*1.空字符串返回0
*2.没有公共序列的字符串
*3.仅有一个公共序列的情况,分别位于头部,中间和尾部
*4.包含多个相同字符串的情况,位置分别位于头部,中间和尾部
*5.两个字符串完全相同
*/
int longestCommonSequnenceLength(string str1, string str2)
{
int m = str1.length();
int n = str2.length();
if((m == 0) ||(n == 0))
return 0;
vector<vector<int>> dp(m+1, vector<int>(n+1, 0)); //创建二维数组,用于表示背包问题的网格
for(int i = 1; i <= m ; i++)
{
for(int j = 1; j <= n; j++)
{
//分解问题为不包含这两个字母的块的公共序列长度,和包含这两个字母的情况,这是一种组合
//如果dp[i-1][j-1]为当前网格左上角的数值,它的含义是不包含这两个字符的情况下公共序列的长度
//在前面的前提下,如果这两个字母相等,则需要在前面的左上角网格的基础上进行加1操作
//如果两个字母不相等,这时候有两种情况,一种是在左上角的网格基础上只包含一个字母的情况,即dp[i][j-1], dp[i-1][j],判断这两个网格哪个大就选大的
if(str1[i-1] == str2[j-1])
{
dp[i][j] = dp[i-1][j-1] + 1;
}
else
{
dp[i][j] = max(dp[i][j-1], dp[i-1][j]);
}
}
}
return dp[m][n];
}
//删除一个字符串中相同的字符
string remveSameCharacterInStr(string str)
{
vector<char> vchar;
int m = str.length();
for(int i = 0; i < m; i++)
{
int n = vchar.size();
int j;
for(j = 0; j < n; j++)
{
if(str[i] == vchar.at(j))
break;
}
if(j == n)
vchar.push_back(str[i]);
}
vchar.push_back(0);
string str1 = vchar.data();
return str1;
}
//求两个字符串有多少个相同的字符
int getSameCharacterLength(string str1, string str2)
{
string newStr1 = remveSameCharacterInStr(str1);
string newStr2 = remveSameCharacterInStr(str2);
int m = newStr1.length();
int n = newStr2.length();
int number = 0;
for(int i = 0; i < m; i++)
{
for(int j = 0; j < n; j++)
{
if(newStr1[i] == newStr2[j])
{
number++;
break;
}
}
}
return number;
}
int main(int argc, char *argv[])
{
int length = longetsSubstringLength("11333344444444", "111115555444444");
cout <<" length = "<<length <<endl;
string substr = longestSubstring("61333344444444", "611155554");
cout <<" substr = "<<substr<<endl;
int len = longestCommonSequnenceLength("1fcb", "1fcb");
cout<<" len = "<<len<<endl;
string str = remveSameCharacterInStr("121");
cout<<"str = "<< str<<str.length()<<endl;
int sameLen = getSameCharacterLength("aaaaaab","aaaabbb");
cout<<"sameLen = "<<sameLen<<endl;
return 0;
}