动态规划--

跳台阶扩展问题

第一次跳的是一阶,那么剩下的是n-1个台阶,跳法是f(n-1);第N个台阶肯定是要跳的,前面的台阶有2种可能(跳or不跳):有n阶时:f(n)=2^(n-1);f(n):总共的跳法、跳上n级台阶的方法个数;f(n - step) :step表示青蛙最后—跳跳了几个台阶.f(0):站在原地,一次跳到要的台阶数,
F(i): F(i - 1) + F(i - 2)+…F(0)
F(i - 1): F(i - 2) +F(i - 3) + … F(0)
F(i) : F(i - 1)+F(i - 1)= 2*F(i - 1)
在这里插入图片描述

class Solution{
public:
	int jumpFloorII(int number){
		if (number == 1)
		{
			return 1;一阶的时候 f(1) = 1 
		}
		return 2*jumpFloorII(number - 1);
	}
};

斐波那契数列变形:

一次只能跳1级或者2级

F(i): F(i- 1) + F(i - 2)

短形覆盖

添加链接描述
在这里插入图片描述
是从实际问题中提炼 出我们的解决问题的数学模型
f(n) : 总方法数 //状态递推:f(n) = f(n-1)【最后一个竖着放】 + f(n-2)【最后两个横着放】 f(0)我们这个可以不考虑,如果考虑值设为1,

int rectCover(int number) {number:最后1个的下标
	if (number < 2){ 
			return number; }
		int *dp = new int[number+1]; 
		dp[1] = 1; dp[2] = 2;初始化
		for( int i = 3; i <= number; i++)
		{ dp[i] = dp[i-1]+dp[i-2]; }
		int num = dp[number]; 
		delete dp; 
		return num;
} 

跳石板

多种解,最优解,分解规模–〉前面答案要能被后面的问题所利用,被后面利用不了,则为分治。
在这里插入图片描述

#include<iostream>
#include<vector>
#include<limits.h>//INT_MAX
#include<math.h>
using:namespace std;
void get_div_num(int v, vector<int> &a)
{求约数
		for (int i = 2; i <= sqrt(v); ++i){
		if (v% i == 0)
		{
			a.push_back(i);
			if (v / i != i)
			a.push_back(v / i);
		}
		}
}
int Jump(int n, int m){//int_ max:不可到达,m个值都初始化为int_ max
	vector<int> step(m + 1, INT_MAX); 
	step[n] = 0; //当前位置初始化
	for (int i = n; i < m; ++i)
	{
		if (step[i] == INT_MAX)continue;

		vector<int> a;		
		get_div_num(i, a);//i位置的约数保存到数组a
		for (int j = 0; j < a.size(); ++j){ //a.size():有多少个约数 = 有多少种跳法
			if (a[j] + i <= m  && step[a[j] + i] != INT_MAX)
			{//需要挑选一个最小值
				step[a[j] + i] = step[a[j] + i] < step[i] + 1 ? step[a[j] + i] : step[i] + 1;
			}约数 + 当前位置i = a[j] + i
			else if (a[j] + i <= m){
				step[a[j] + i] = step[i] + 1;
			}
		}
	}// step[m]:当前位置i到起始位置N需要跳的步数
	return step[m] == INT_MAX ? -1 : step[m];
}
int main(){
	int n, m, min_step;
	while (cin >> n >> m){
		min_step = Jump(n, m);
		cout << min_step << endl;
	}
	return 0;
}

HJ52 字符串的编辑距离

添加链接描述
为了有初始值,5个字符,开6个空间,
F(i,j):word1的前i个字符转成word2的前j个字符的最小操作次数
F(i,j) = min { F(i-1,j)+1, F(i,j-1) +1, F(i-1,j-1) +(w1[i]==w2[j]?0:1) }
上式表示从删除,增加和替换操作中选择一个最小操作数;
初始化: 初始化一定要是确定的值,如果这里不加入空串,初始值无法确定
F(i,0) = i :word与空串的编辑距离:删除i个字符 ;F(0,i) = i :空串与word的编辑距离,增加j个字符在这里插入图片描述

#include<iostream> 
#include<string>
#include<vector> 
using namespace std;
int minDistance(const string &str1, const string &str2) 
{	// word与空串之间的编辑距离为word的长度 
	if(str1.empty() || str2.empty()) 
		return max(str1.size(), str2.size()); 
	int len1 = str1.size(); 
	int len2 = str2.size(); 
	vector<vector<int>> f(len1+1, vector<int>(len2+1, 0)); //初始化距离 
	for(int i=0; i<=len1; ++i) 
		f[i][0] = i; 
	for(int j=0; j<=len2; ++j) 
		f[0][j] = j;
	for(int i=1; i<=len1; ++i) 
	{
		for(int j=1; j<=len2; ++j)
		{ 
// 判断word1的第i个字符是否与word2的第j个字符相等 
			if(str2[j-1] == str1[i-1])
			{删除,增加
			f[i][j] = 1 + min(f[i-1][j], f[i][j-1]);
			//字符相同,距离不发生变化 替换
			f[i][j] = min(f[i][j], f[i-1][j-1]);
			}
			else
			{删除,增加
				f[i][j] = 1 + min(f[i-1][j], f[i][j-1]); 
			//由于字符不相同,所以距离+1 替换
			f[i][j] = min(f[i][j], 1+f[i-1][j-1]); 
			}
		}
	}
	return f[len1][len2]; 
}
int main() 
{
	string str1, str2;
	while(cin >> str1 >> str2) 
	cout<<minDistance(str1, str2)<<endl; 
	return 0; 
}

JD1 年终奖

class Bonus {
public:
	int getMost(vector<vector<int> > board) {
		int length = board.size();
		int wideth = board[0].size();
		vector<vector<int>> allPrice;
		for (int i = 0; i < length; i++)
		{
			vector<int> tmp(wideth, 0);
			allPrice.push_back(tmp);
		}
		allPrice[0][0] = board[0][0];
		for (int i = 0; i < length; i++)
		{
			for (int j = 0; j < wideth; j++)
			{
				//如果是起点坐标,不做任何处理。
				if (i == 0 && j == 0)
					continue;
				//如果走在行的临界边,也就是第一行,那么他只能向右走
				//向右走的时候该点就要将后面的值加起来。
				else if (i == 0) {
					allPrice[i][j] = allPrice[i][j - 1] + board[i][j];
				}
				//如果走在列的临界边,也就是第一列,那么他只能向下走
				//向下走的时候该点就要将上面的值加起来。
				else if (j == 0) {
					allPrice[i][j] = allPrice[i - 1][j] + board[i][j];
				}
				else {
					//除去两个临界边,剩下的就是既能向右走,也能向下走,
					//这时候就要考虑走到当前点的所有可能得情况,也就是走到当前点
					//各自路径的和是不是这些所有到达该点路径当中最大的了。
					allPrice[i][j] = max(allPrice[i][j - 1],
						allPrice[i - 1][j]) + board[i][j];
				}
			}
		}
		// 返回最后一个坐标点的值,它就表示从左上角走到右下角的最大奖励
		return allPrice[length - 1][wideth - 1];
	}
};

在这里插入图片描述
表1:;礼物价值;表2:每一步的最优解,最右下角是要的答案。

CC12 拆分词句

添加链接描述
F(i): 前i个字符能否根据词典中的词被成功分词
F(i): true{j <i && F(j) && substr[j+1,i]能在词典中找到}
在j小于i中,只要能找到一个F(j)为true,并且从j+1到i之间的字符能在词典中找到,则F(i)为true
初始值: 对于初始值无法确定的,可以引入一个不代表实际意义的空状态,作为状态的起始 空状态的值需要保证状态递推可以正确且顺利的进行,到底取什么值可以通过简单 的例子进行代入 F(0) = true
在这里插入图片描述

bool wordBreak(string s, unordered_set<string> &dict){
	if (s.empty()){ return false; }
	if (dict.empty()){ return false; }
size是指向vector中数组长度后的那一个位置,即size处没有元素
	vector<bool> can_break(s.size() + 1, false); 
	// 初始化F(0) = true 
	can_break[0] = true;
	for (int i = 1; i <= s.size(); i++)
	{ 
		for (int j = i - 1; j >= 0; j--)
		{ 
		// 第j+1个字符的索引为j 
		if (can_break[j] && dict.find(s.substr(j, i - j)) != dict.end())
		{ can_break[i] = true;
		break;	}
		}
	}
	return can_break[s.size()];
} 

CC31 三角形

F(i,j): 从(0,0)到(i,j)的最短路径和
状态递推: F(i,j) = min( F(i-1, j-1), F(i-1, j)) + triangle[i][j]
初始值: F(0,0) = triangle[0][0]
添加链接描述

int minimumTotal(vector<vector<int>> &triangle) {
	if (triangle.empty()){ return 0; }
	//初始化 
	vector<vector<int>> min_sum(triangle); 
	int line = triangle.size();
	for (int i = 1; i < line; i++)
	{
		for (int j = 0; j <= i; j++)
		{ 
			// 处理左边界和右边界 
			if (j == 0)
			{ min_sum[i][j] = min_sum[i - 1][j]; }
			else if (j == i)
			{ min_sum[i][j] = min_sum[i - 1][j - 1]; }
			else
			{min_sum[i][j] = min(min_sum[i - 1][j], min_sum[i - 1][j - 1]); }
// F(i,j) = min( F(i-1, j-1), F(i-1, j)) + triangle[i][j]
			min_sum[i][j] = min_sum[i][j] + triangle[i][j];
		} 
	}
int result = min_sum[line - 1][0]; 
// min(F(n-1, i)) 
for (int i = 1; i < line; i++)
{ result = min(min_sum[line - 1][i], result); }
return result; }

方法2:
F(i,j): 从(i,j)到最后一行的最短路径和
F(i,j) = min( F(i+1, j), F(i+1, j+1)) + triangle[i][j]
F(n-1,0) = triangle[n-1][0], F(n-1,1) = triangle[n-1][1],…, F(n-1,n-1) = triangle[n-1] [n-1]
逆向思维不需要考虑边界,也不需要最后寻找最小值,直接返回F(0,0)即可

int minimumTotal(vector<vector<int>> &triangle) {
	if (triangle.empty()){ return 0; }
	// F[n-1][n-1],...F[n-1][0]初始化
	vector<vector<int>> min_sum(triangle); 
	int line = triangle.size(); 
	// 从倒数第二行:line - 2开始
	for (int i = line - 2; i >= 0; i--)
	{ for (int j = 0; j <= i; j++){ 
// F(i,j) = min( F(i+1, j), F(i+1, j+1)) + triangle[i][j] 
		min_sum[i][j] = min(min_sum[i + 1][j], min_sum[i + 1][j + 1]) + triangle[i] [j];
	}
	}
	return min_sum[0][0]; }

CC86 带权值的最小路径和

添加链接描述
子状态:从(0,0)到达(1,0),(1,1),(2,1),…(m-1,n-1)的最短路径
F(i,j): 从(0,0)到达F(i,j)的最短路径
状态递推: F(i,j) = min{F(i-1,j) , F(i,j-1)} + (i,j)
初始化: F(0,0) = (0,0)
特殊情况:第0行和第0列 F(0,i) = F(0,i-1) + (0,i)
F(i,0) = F(i-1,0) + (i,0)

int minPathSum(vector<vector<int> > &grid) 
{ // 如果为空或者只有一行,返回0
	if (grid.empty() || grid[0].empty()) 
	{ return 0; }
	// 获取行和列大小 
	const int M = grid.size(); 
	const int N = grid[0].size(); 
	// F(i,j)
	vector<vector<int> > ret(M, vector<int>(N, 0));
	// F(0,0), F(0,i), F(i,0)初始化
	ret[0][0] = grid[0][0]; 
	for (int i = 1; i != M; ++i)
	{ ret[i][0] = grid[i][0] + ret[i - 1][0]; }
	for (int i = 1; i != N; ++i) 
	{ ret[0][i] = grid[0][i] + ret[0][i - 1]; }
	// F(i,j) = min{F(i-1,j) , F(i,j-1)} + (i,j)
	for (int i = 1; i < M; ++i)
	{ for (int j = 1; j < N; ++j)
		{ ret[i][j] = grid[i][j] + min(ret[i - 1][j], ret[i][j - 1]); 
		} 
	}
	return ret[M - 1][N - 1]; }

CC88 不同路径的数目(一)

F(i,j): 从(0,0)到达F(i,j)的路径数 状态递推: F(i,j) = F(i-1,j) + F(i,j-1)
添加链接描述

int uniquePaths(int m, int n) {
	if (m < 1 || n < 1) { return 0; }// 第0行和第0列
	vector<vector<int> > ret(m, vector<int>(n, 1)); 
	for (int i = 1; i < m; ++i) 
	{
		for (int j = 1; j < n; ++j) 
		{ 
		ret[i][j] = ret[i - 1][j] + ret[i][j - 1]; 
		}
	}
	return ret[m - 1][n - 1]; }

背包问题

F(i, j): 前i个物品放入大小为j的背包中所获得的最大价值
装不下:此时的价值与前i-1个的价值是一样的 F(i,j) = F(i-1,j)
装入:需要在两种选择中找最大的 F(i, j) = max{F(i-1,j), F(i-1, j - A[i]) + V[i]}
第0行和第0列都为0,没有装物品时的价值都为0
添加链接描述

int backPackII(int m, vector<int> A, vector<int> V) {
	if (A.empty() || V.empty() || m < 1)
	{ return 0; }
	//多加一行一列,用于设置初始条件
	const int N = A.size() + 1;
	const int M = m + 1;

	vector<vector<int> > result; 
	result.resize(N); //初始化所有位置为0
	方法1for (int i = 0; i != N; ++i) 
	{ result[i].resize(M, 0); }
	for (int i = 1; i < N; ++i)
	{
		for (int j = 1; j != M; ++j)
		{ 
//第i个商品在A中对应的索引为i-1: i从1开始
//如果第i个商品大于j,说明放不下, 所以(i,j)的最大价值和(i-1,j)相同 
			if (A[i - 1] > j) 
			{ result[i][j] = result[i - 1][j]; }
//如果可以装下,分两种情况,装或者不装 
//最后在装与不装中选出最大的价值 
F(i-1,j): 不把第i个物品放入背包中, 价值就是前i-1个物品放入大小为j的背包的最大价值
F(i-1, j-A[i])+V[i]:第i个物品放入背包中,价值增加V[i],腾出j-A[i]的大小放第i 个商品 
			else 
			{int newValue = result[i-1][j - A[i - 1]] + V[i - 1];
			result[i][j] = max(newValue, result[i - 1][j]);
			}
		}
	}
//返回装入前N个商品,物品大小为m的最大价值 
	return result[N-1][m]; 
}

方法2:

for (int i = 0; i != N; ++i)
{ 
	for (int j = M - 1; j > 0; --j) 
	{
		if (A[i] > j)
		{ result[j] = result[j]; }
		else
		{int newValue = result[j - A[i]] + V[i]; 
		result[j] = max(newValue, result[j]); 从后往前更新
		}
	} 
}
return result[m];

CC19 分割回文串-ii

添加链接描述
F(i): 到第i个字符需要的最小分割数
F(i) = min{F(i), 1 + F(j)}, where j<i && j+1到i是回文串
j+1到i判断为回文字符串,且已经知道从第1个字符 到第j个字符的最小切割数,再切一次,就可以保证 1–>j, j+1–>i都为回文串。如果整体为回文串[1,i]: F(i) = 0。
初始化: F(i) = i - 1: 到第i个字符需要的最大分割数 比如单个字符只需要切0次,因为单子符都为回文串, 2个字符最大需要1次,3个2次…
在这里插入图片描述

int minCut(string s) {
	if (s.empty()) return 0; 
	int len = s.size();
	vector<int> cut; // F(i)初始化 
	for (int i = 0; i < 1 + len; ++i)
	{
		cut.push_back(i - 1); 
	}
	for (int i = 1; i < 1 + len; ++i)
	{ 
		for (int j = 0; j < i; ++j) 
		{ 
		if (isPalindrome(s, j, i - 1)) 下标从0开始,1个字符到第i个字符之间
		{ cut[i] = min(cut[i], 1 + cut[j]);
		}
		}
	}

	return cut[len]; 
}//判断是否回文串 ,空串是回文串
bool isPalindrome(string s, int i, int j)
{ while (i<j) 
	{ if (s[i] != s[j])
		{ return false; }
	i++; j--;
	}
return true;
}

斐波那契数列

添加链接描述
递归:时间复杂度为O(2^n),随着n的增大呈现指数增长,效率低下 当输入比较大时,可能导致栈溢出,有大量的重复计算,

int Fibonacci(int n){ 方法1
	// 初始值 
	if (n <= 0){ return 0; }
	if (n == 1 || n == 2) 
	{ return 1; }递归:大量的函数调用
	return Fibonacci(n - 2) + Fibonacci(n - 1); }

动态规划:

int Fibonacci(int n){ // 初始值 方法2
	if (n <= 0)
	{ return 0; }
	if (n == 1 || n == 2)
	{ return 1; }// 申请一个数组,保存子问题的解,题目要求从第0项开始 
	int* record = new int[n + 1]; 
	record[0] = 0; record[1] = 1; 
	for (int i = 2; i <= n; i++)
	{ 
		record[i] = record[i - 1] + record[i - 2]; 
}
	return record[n]; 
	delete[] record; }

上述解法的空间复杂度为O(n) 其实F(n)只与它相邻的前两项有关,所以没有必要保存所有子问题的解 只需要保存两个子问题的解就可以 下面方法的空间复杂度将为O(1)

int Fibonacci(int n){ // 初始值 方法3
	if (n <= 0)
	{ return 0; }
	if (n == 1 || n == 2) 
	{ return 1; }
	int fn1 = 1; int fn2 = 1;
	int result = 0; 
	for (int i = 3; i <= n; i++){
	result = fn2 + fn1; 
	// 更新值 
		fn1 = fn2; 
		fn2 = result; 
	}
	return result; }

方法4:map进行“剪枝”,因为枝太多,类似方法2

private: unordered_map<int, int> filter; 
public: int Fibonacci(int n) { 
	if (n == 0 || n == 1)
	{ return n; }
		int pre = 0; 对应n-1
	if (filter.find(n - 1) == filter.end())
	{ pre = Fibonacci(n - 1); 如果不存在(没有找到),则计算
	filter.insert({ n - 1, pre }); 插入容器
	}
	else
	{ pre = filter[n - 1]; }找到了
	int ppre = 0; 对应n-2
	if (filter.find(n - 2) == filter.end())
	{ ppre = Fibonacci(n - 2); 
	 filter.insert({ n - 2, ppre }); } 
	else
	{ ppre = filter[n - 2]; }
	return pre + ppre;
}

不同子序列

不能把字符串写死,每次都是发生变化的
S[1:m]中的子串与T[1:n]相同的个数 由S的前m个字符组成的子串与T的前n个字符相同的个数 状态:子状态:由S的前1,2,…,m个字符组成的子串与T的前1,2,…,n个字符相同的个数 F(i,j): S[1:i]中的子串与T[1:j]相同的个数 状态递推: 在F(i,j)处需要考虑S[i] = T[j] 和 S[i] != T[j]两种情况 当S[i] = T[j]: 1>: 让S[i]匹配T[j],则 F(i,j) = F(i-1,j-1) 2>: 让S[i]不匹配T[j],则问题就变为S[1:i-1]中的子串与T[1:j]相同的个数,则 F(i,j) = F(i-1,j) 故,S[i] = T[j]时,F(i,j) = F(i-1,j-1) + F(i-1,j) 当S[i] != T[j]: 问题退化为S[1:i-1]中的子串与T[1:j]相同的个数 故,S[i] != T[j]时,F(i,j) = F(i-1,j) 初始化:引入空串进行初始化 F(i,0) = 1 —> S的子串与空串相同的个数,只有空串与空串相同

连续子数组的最大和

添加链接描述

int FindGreatestSumOfSubArray(vector<int> array) {
    if (array.empty())
		return 0;

	vector<int> maxSum(array);
	int ret = maxSum[0];
	for (int i = 1; i < array.size(); ++i){
		maxSum[i] = max(maxSum[i-1] + array[i], array[i]);局部最大
		ret = max(ret, maxSum[i]);全局最大
    } return ret;
    }

F(i):以第i个元素结尾的最大连续子序列和
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值