题目一
蛇从最左侧某个点出发,来到(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];
}