Note: 子序列,可以不连续;子字符串,必须连续。
以下题目按在我看看来的难度从易到难排列:
最大和子序列(Maximum sum subsequence)
这道题纯属娱乐...应该不会有人出这种题吧。方案是贪心法,遇到正数就放入序列。
vector<int> msseq(vector<int> &num) { vector<int> result; for(int i : num) if(i > 0) result.push_back(i); return result; }
最大和子字符串(Maximum sum substring)
暴力方案TC为O(n^2)。更好的方案为,贪心法, 设i从0到n遍历,用gmax代表全局最大和,cursum代表当前子字符串的和。cursum为负,则放弃之前的子字符串;为正则继续向后加,每遇到一个正数,都更新一次gmax。TC = O(n),SC = O(1)。
double msstr(vector<double> &nums) { double gmax = 0, cur = 0; for(int i : nums) { if(cur > 0) cur += i; else cur = i; gmax = max(gmax, cur); } return gmax; }
最长递增子字符串(Longest increasing substring)
暴力方案TC为O(n^2)。更好的方案为,与最大和子字符串一样,贪心法,设i从0到n遍历,用gmax代表全局最长长度,len代表当前递增substring的长度。遇到递增,则len++;遇到非递增,则更新gmax,并重置len为1。TC = O(n),SC = O(1)
int listr(vector<double> &nums) { if(nums.empty()) return 0; int gmax = 1, cur = 1; for(int i = 1; i < nums.size(); i++) { if(nums[i] > nums[i - 1]) { cur++; gmax = max(gmax, cur); } else cur = 1; } return gmax; }
最大乘积子字符串(Maximum product subsequence)
基本操作与最大乘积子序列差不多,不过是需要连续。用邻点动态规划,
double mpstr(vector<double> &nums) { double gmax, cmax, cmin, pmax, pmin; gmax = cmax = cmin = pmax = pmin = 1; for(double i : nums) { cmax = max(i, max(pmax * i, pmin * i)); cmin = min(i, min(pmax * i, pmin * i)); pmax = cmax; pmin = cmin; gmax = max(gmax, cmax); } return gmax; }
最大乘积子序列(Maximum product subsequence)
这题与最大和子序列差不多吧...如果无负数,遇到大于1的数就放入序列;不过需要考虑正负号,用邻点动态规划,
double mpseq(vector<double> &nums) { double cmax, cmin, pmax, pmin; cmax = cmin = pmax = pmin = 1; for(double i : nums) { cmax = max(max(i, pmax), max(pmax * i, pmin * i)); cmin = min(min(i, pmin), min(pmax * i, pmin * i)); pmax = cmax; pmin = cmin; } return pmax; }
最长递增子序列(Longest increasing subsequence)
暴力方案TC为O(2^n)。如果用贪心法,分析知道根据第i个包含的信息,无法覆盖前i个数的情况,故第i+1个数的决策没法做;更好的方案为,全局动态规划,用opt[i]记录到i为止最长的且包含 i上字符的最长递增subsequence,opt[i]初始为1,动态转移方程为opt[i] = max(opt[j] + 1, opt[i])。
vector<int> miseq(vector<int> &num) { vector<int> result; if(num.empty()) return num; vector<int> opt(num.size(), 1), record(num.size(), -1); for(int i = 0; i < num.size(); i++) { for(int j = 0; j < i; j++) { if(num[i] > num[j] && opt[j] + 1 > opt[i]) { opt[i] = opt[j] + 1; record[i] = j; } } } int last = -1, gmax = 0; for(int i = 0; i < num.size(); i++) { if(opt[i] > gmax) { gmax = opt[i]; last = i; } } while(last >= 0) { result.push_back(num[last]); last = record[last]; } reverse(result.begin(), result.end()); return result; }
最长匹配子字符串(Longest common substring)
暴力方案TC为O(m*n*max(m,n))。更好的方案为,邻点动态规划,用opt[i][j]记录到M的i - 1位置与N的j - 1位置为止,且包含i - 1,j - 1的最长匹配子字符串。将0位置设为岗哨,其中opt[0][j] = opt[i][0] = 0,动态转移方程为opt[i][j] = M[i] == N[j] ? opt[i - 1][j - 1] + 1 : 0。TC = O(m*n),SC = O(m * n),用滚动数组可以压缩到O(m) or O(n)。
string lcstr(string M, string N) { //找到最长匹配子字符串的长度 int maxlen = INT_MIN, ri = -1; vector<vector<int> > opt(M.size() + 1, vector<int>(N.size() + 1, 0)); for(int i = 1; i <= M.size(); i++) for(int j = 1; j <= N.size(); j++) { opt[i][j] = M[i - 1] == N[j - 1] ? opt[i - 1][j - 1] + 1 : 0; if(opt[i][j] > maxlen) { maxlen = opt[i][j]; ri = i; } } //将最大值对应的匹配子字符串输出 string result = ""; int starti = ri - maxlen; for(int i = starti; i < ri; i++) result += M[i]; return result; }
最长匹配子序列(Longest common subsequence)
更好的方案为,与最长匹配子字符串类似,邻点动态规划,用opt[i][j]记录到M的i - 1位置与N的j - 1位置为止,且包含i - 1,j - 1的最长匹配子字符串。将0位置设为岗哨,其中opt[0][j] = opt[i][0] = 0,动态转移方程为opt[i][j] = M[i] == N[j] ? opt[i - 1][j - 1] + 1 : max(opt[i - 1][j], opt[i][j - 1])。TC = O(m*n),SC = O(m*n),滚动数组同样可以压缩到O(m) or O(n)。
//只是找长度很简单... int lenlcstr(string M, string N) { //找到最长匹配子字符串的长度 vector<vector<int> > opt(M.size() + 1, vector<int>(N.size() + 1, 0)); for(int i = 1; i <= M.size(); i++) for(int j = 1; j <= N.size(); j++) opt[i][j] = M[i - 1] == N[j - 1] ? opt[i - 1][j - 1] + 1 : max(opt[i - 1][j], opt[i][j - 1]); return opt[M.size()][N.size()]; } //还要返回一个最长匹配字符串 string lcstr(string M, string N) { //记录路径 vector<vector<int> > track(M.size() + 1, vector<int>(N.size() + 1, -1)); //找到最长匹配子字符串的长度 vector<vector<int> > opt(M.size() + 1, vector<int>(N.size() + 1, 0)); for(int i = 1; i <= M.size(); i++) for(int j = 1; j <= N.size(); j++) { if(M[i - 1] == N[j - 1]) { opt[i][j] = opt[i - 1][j - 1] + 1; track[i][j] = 0; } else { if(opt[i - 1][j] > opt[i][j - 1]) { track[i][j] = 1; opt[i][j] = opt[i - 1][j]; } else { track[i][j] = 2; opt[i][j] = opt[i][j - 1]; } } } int i = int(M.size()), j = int(N.size()); string result = ""; while(track[i][j] > -1) { if(track[i][j] == 0) { result += M[i - 1]; i--; j--; } else if(track[i][j] == 1) i--; else j--; } reverse(result.begin(), result.end()); return result; }
编辑距离(Edit distance)
更好的方案,与LCS类似,邻点动态规划
int edist(string s1, string s2) { int m = int(s1.length()), n = int(s2.length()); vector<vector<int> > opt(m + 1, vector<int> (n + 1, 0)); for(int i = 0; i <= m; i++) opt[i][0] = i; for(int j = 0; j <= n; j++) opt[0][j] = j; for(int i = 1; i <= m; i++) for(int j = 1; j <= n; j++) { opt[i][j] = min(min(opt[i - 1][j], opt[i][j - 1]), opt[i - 1][j - 1]) + 1; if(s1[i - 1] == s2[j - 1]) opt[i][j] = min(opt[i][j], opt[i - 1][j - 1]); } return opt[m][n]; }
最长无重复子字符串(Longest substring with no duplicate characters)
用一个unordered_set<char>里存已有的char,遇到加入一个新的char,则在set里找,如果没找到,则加入set,并且count++;如果找到了,则在substring的循环弹出,更新set,并循环count--,直到弹出新加入的char的重复字符。