第三节课:

本文探讨了利用动态规划和递归策略解决计算机科学中的复杂问题,如蛇的最大长度、数学表达式的计算以及字符串的最长公共子串和子序列。通过代码实现和优化,展示了如何利用记忆化搜索和空间压缩来提高算法效率。文章还介绍了如何处理负数和优先级结合的问题,以及如何找到最长公共子串和子序列的解决方案。
摘要由CSDN通过智能技术生成


题目一

在这里插入图片描述
蛇从最左侧某个点出发,来到(i,j)位置,获得的最大长度;分是否使用能力获得的最大长度;如果结果是负数,表示没有该答案;蛇有可能在任意位置获得最大长度;遍历所有位置,调用递归函数,取每个位置的最大值;basecase是蛇在最左列的情况;一般情况,三种情况:从左上、从左下、从左侧;
记忆化搜索对于没有枚举的情况,与最后的动态规划时间复杂度一样,只是空间复杂度可以再优化;如果存在枚举,从记忆化搜索到严格表结构就有一样了,可以优化枚举
代码实现:

class Info {
public:
	int yes;
	int no;
	Info(int yes, int no) {
		this->yes = yes;
		this->no = no;
	}
};

//从最左侧来到(i,j)位置,获得最大长度,存在两种情况:用/不用能力
//结果为0,表示没有答案
Info* process(vector<vector<int>>& arr, int i, int j) {
	if (j == 0) {
		return new Info(-arr[i][0], arr[i][0]);
	}

	//之前的路径和(获得的最大长度)
	int preno = -1;
	int preyes = -1;
	if (i > 0) {//有左上
		//里面的if可以省略,直接取最大值,因为-1是最大的负数
		Info* leftup = process(arr, i - 1, j - 1);
		if (leftup->no >= 0) {
			preno = leftup->no;
		}
		if (leftup->yes >= 0) {
			preyes = leftup->yes;
		}
	}
	Info* left = process(arr, i, j - 1);
	//if可以省略,直接取最大值,因为-1是最大的负数
	if (left->no >= 0) {
		preno = max(preno, left->no);
	}
	if (left->yes >= 0) {
		preyes = max(preyes, left->yes);
	}
	if (i < arr.size() - 1) {
		//里面的if可以省略,直接取最大值,因为-1是最大的负数
		Info* leftdown = process(arr, i + 1, j - 1);
		if (leftdown->no >= 0) {
			preno = max(preno, leftdown->no);
		}
		if (leftdown->yes >= 0) {
			preyes = max(preyes, leftdown->yes);
		}
	}


	//生成当前位置的信息
	int no = -1;
	int yes = -1;

	if (preno >= 0) {
		no = preno + arr[i][j];
		yes = preno - arr[i][j];
	}
	if (preyes >= 0) {
		yes = max(yes, preyes + arr[i][j]);
	}

	return new Info(yes, no);
}


//去除多余的判断
Info* process2(vector<vector<int>>& arr, int i, int j) {
	if (j == 0) {
		return new Info(-arr[i][0], arr[i][0]);
	}

	//之前的路径和(获得的最大长度)
	int preno = -1;
	int preyes = -1;
	if (i > 0) {//有左上
		Info* leftup = process(arr, i - 1, j - 1);
		preno = max(preno, leftup->no);
		preyes = max(preyes, leftup->yes);
	}

	Info* left = process(arr, i, j - 1);
	preno = max(preno, left->no);
	preyes = max(preyes, left->yes);

	if (i < arr.size() - 1) {
		Info* leftdown = process(arr, i + 1, j - 1);
		preno = max(preno, leftdown->no);
		preyes = max(preyes, leftdown->yes);
	}


	//生成当前位置的信息
	int no = -1;
	int yes = -1;

	if (preno >= 0) {
		no = preno + arr[i][j];
		yes = preno - arr[i][j];
	}
	if (preyes >= 0) {
		yes = max(yes, preyes + arr[i][j]);
	}

	return new Info(yes, no);
}

//改记忆化搜索的dp
Info* process3(vector<vector<int>>& arr, int i, int j,vector<vector<Info*>>&dp) {
	if (dp[i][j] != nullptr) {
		return dp[i][j];
	}
	if (j == 0) {
		dp[i][0] = new Info(-arr[i][0], arr[i][0]);
		return dp[i][0];
	}

	//之前的路径和(获得的最大长度)
	int preno = -1;
	int preyes = -1;
	if (i > 0) {//有左上
		Info* leftup = process(arr, i - 1, j - 1);
		preno = max(preno, leftup->no);
		preyes = max(preyes, leftup->yes);
	}

	Info* left = process(arr, i, j - 1);
	preno = max(preno, left->no);
	preyes = max(preyes, left->yes);

	if (i < arr.size() - 1) {
		Info* leftdown = process(arr, i + 1, j - 1);
		preno = max(preno, leftdown->no);
		preyes = max(preyes, leftdown->yes);
	}


	//生成当前位置的信息
	int no = -1;
	int yes = -1;

	if (preno >= 0) {
		no = preno + arr[i][j];
		yes = preno - arr[i][j];
	}
	if (preyes >= 0) {
		yes = max(yes, preyes + arr[i][j]);
	}

	dp[i][j] = new Info(yes, no);
	return dp[i][j];
}


//每个位置都有可能是最终的结果
int snack(vector<vector<int>>& arr) {
	if (arr.size() == 0 || arr[0].size() == 0) {
		return 0;
	}
	int res = 0;
	vector<vector<Info*>>dp(arr.size(), vector<Info*>(arr[0].size(), nullptr));
	for (int i = 0; i < arr.size(); i++) {
		for (int j = 0; j < arr[0].size(); j++) {
			//Info* cur = process(arr, i, j);
			//3Info* cur = process2(arr, i, j);
			Info* cur = process3(arr, i, j, dp);
			res = max(res, max(cur->no, cur->yes));
		}
	}
	return res;
}

题目二

在这里插入图片描述
介绍一种类似这种优先级(括号)结合的递归策略:f(str,i)返回两个值:1) 从i位置开始,遇到‘(’或者终止就停,返回i到停止的位置算的结果;2) 返回停止的位置
首先看公式中不存在括号时,这么计算:用栈。分遇到的是数字和运算符两种情况。遇到数字将数字和它后面的运算符压入栈中,但是如果栈顶元素是*或/,需要先将当前数字和栈中第一个数字用栈顶的运算符算出一个结果,将该结果和当前数的运算符压入栈中;最后单独算栈里剩余的元素。

在这里插入图片描述
如果公式中存在括号:按照上述不存在括号的方式计算,当遇到‘(’时,递归调用‘(’的下一位置,从放回结果的下一位置开始继续计算
在这里插入图片描述
优先级结合的问题,用这种递归方法包打一切。
代码实现:

void addNum(int num, char c,stack<string>& sk) {
	if (!sk.empty()) {
		if (sk.top() == "+" || sk.top() == "-") {
			sk.push(to_string(num));
		}
		else {
			if (sk.top() == "*") {//  *
				sk.pop();
				int top = stoi(sk.top());
				sk.pop();
				sk.push(to_string(num * top));
			}
			else {//  /
				sk.pop();
				int top = stoi(sk.top());
				sk.pop();
				sk.push(to_string(top / num));
			}
		}
	}
	else {
		sk.push(to_string(num));
	}
	if (c != '\0') {
		string s = "";
		s += c;
		sk.push(s);
	}
}

int getNum(stack<string>& sk) {
	if (sk.empty()) {
		return 0;
	}
	int res = stoi(sk.top());
	sk.pop();
	while (!sk.empty()) {
		if (sk.top() == "+") {
			sk.pop();
			res += stoi(sk.top());
		}
		else {
			sk.pop();
			res = stoi(sk.top()) - res;
		}
		sk.pop();
	}
	return res;
}

vector<int> f(string& s, int i) {
	stack<string>sk;
	int num = 0;
	vector<int>bra(2);//‘(’左边计算的结果和遇到‘)’或者终止位置
	while (i < s.length() && s[i] != ')') {
		if (s[i] >= '0' && s[i] <= '9') {//i位置为数
			num = num * 10 + s[i] - '0';
			i++;
		}
		else if (s[i] != '(') {//i位置为运算符
			addNum(num, s[i], sk);
			i++;
			num = 0;
		}
		else {//i位置为‘(’
			bra = f(s, i + 1);
			num = bra[0];
			i = bra[1] + 1;
		}
	}
	addNum(num, '\0', sk);
	return vector<int>({ getNum(sk),i });
}

int calculate(string& s) {
	return f(s, 0)[0];
}


//解决负数的问题
//分两种情况:
//1.负数作为开头:负号前加一个0,并用括号,将负号后的数连同新加的0用括号括起来
//2.负号不在开头,那么一定再‘(’后,直接再负号前加0即可
void p(string& s) {
	if (s[0] == '-') {
		s.insert(0, "0");
		if (s[1] != '(') {
			s.insert(0, "(");
			int i = 3;
			while (i < s.length() && s[i] >= '0' && s[i] <= '9') {
				i++;
			}
			s.insert(i, ")");
		}
	}
	for (int i = 1; i < s.length(); i++) {
		if (s[i] == '('&&s[i+1]=='-') {
			s.insert(i + 1, "0");
		}
	}
}

题目三

在这里插入图片描述
左神的方法:dp[i][j]表示最长公共子串必须以s1[i]和s2[j]结尾的长度,很明显,如果s1[i]!=s2[j],则dp[i][j]=0;否则dp[i][j]=dp[i-1][j-1]+1,因此dp[i][j]之和它的左上角有关,可以使用有限几个变量进行空间压缩。
在这里插入图片描述
在这里插入图片描述
从右上角开始求解:

在这里插入图片描述
代码实现:

//dp[i][j]表示最长公共子串必须以s1[i]和s2[j]结尾的长度
string maxLenSubStr(string& s1, string& s2) {
	if (s1.length() == 0 || s2.length() == 0) {
		return "";
	}
	int maxLen = 0;
	int ed = -1;
	int row = 0;
	int col = s2.length() - 1;
	
	while (row < s1.length()) {
		int i = row;
		int j = col;
		int cur = 0;
		while (i < s1.length() && j < s2.length()) {
			if (s1[i] == s2[j]) {
				cur++;
			}
			else {
				cur = 0;
			}
			if (cur > maxLen) {
				maxLen = cur;
				ed = i;
			}
			i++;
			j++;
		}
		if (col == 0) {
			row++;
		}
		else {
			col--;
		}
	}

	return ed == -1 ? "" : s1.substr(ed + 1 - maxLen, maxLen);//substr(pos,n)
}

题目四

在这里插入图片描述

本题求的是最长公共子序列的长度。
dp[i][j]表示s1[0…i]和s2[0…j]的最长公共子序列的长度。上一题最长公共子串必须以s1[i]和s2[j]结尾,本题则不一定。四种可能:1) 最长公共子序列中不包含s1[i],包含s2[j]–>dp[i][j]=dp[i-1][j];2) 最长公共子序列中包含s1[i],不包含s2[j]–>dp[i][j]=dp[i][j-1];3) 最长公共子序列中不包含s1[i],也不包含s2[j]–>dp[i][j]=dp[i-1][j-1];4) 最长公共子序列中包含s1[i],也包含s2[j]–>dp[i][j]=dp[i-1][j-1]。四种情况求最大值即为dp[i][j]

在这里插入图片描述
代码实现:

//dp[i][j]:s1[0..i]和s2[0..j]最长公共子序列的长度
int maxLenSub(string& s1, string& s2) {
	vector<vector<int>>dp(s1.length(), vector<int>(s2.length()));
	dp[0][0] = s1[0] == s2[0] ? 1 : 0;
	for (int i = 1; i < s1.length(); i++) {
		dp[i][0] = (dp[i - 1][0] == 1 || s1[i] == s2[0] ? 1 : 0);
	}
	for (int j = 1; j < s2.length(); j++) {
		dp[0][j] = (dp[0][j - 1] == 1 || s2[j] == s1[0] ? 1 : 0);
	}
	for (int i = 1; i < s1.length(); i++) {
		for (int j = 1; j < s2.length(); j++) {
			dp[i][j] = dp[i - 1][j - 1] + (s1[i] == s2[j] ? 1 : 0);
			dp[i][j] = max(dp[i][j], dp[i - 1][j]);
			dp[i][j] = max(dp[i][j], dp[i][j - 1]);
		}
	}
	return dp[s1.length() - 1][s2.length() - 1];
}
//可以进行空间压缩
int maxLenSub2(string& s1, string& s2) {
	vector<vector<int>>dp(2, vector<int>(s2.length()));
	dp[0][0] = s1[0] == s2[0] ? 1 : 0;

	for (int j = 1; j < s2.length(); j++) {
		dp[0][j] = (dp[0][j - 1] == 1 || s2[j] == s1[0] ? 1 : 0);
	}
	for (int i = 1; i < s1.length(); i++) {
		dp[i % 2][0] = s1[i] == s2[0] ? 1 : 0;
		for (int j = 1; j < s2.length(); j++) {
			dp[i % 2][j] = dp[(i - 1) % 2][j - 1] + (s1[i] == s2[j] ? 1 : 0);
			dp[i % 2][j] = max(dp[i % 2][j], dp[(i - 1) % 2][j]);
			dp[i % 2][j] = max(dp[i % 2][j], dp[i % 2][j - 1]);
		}
	}
	return dp[(s1.length() - 1) % 2][s2.length() - 1];
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值