中级提升班-8
题目二
递归范围尝试模型、改动态规划
给定一个只由 0(假)、1(真)、&(逻辑与)、|(逻辑或)和^(异或)五种字符组成
的字符串express,再给定一个布尔值 desired。返回express能有多少种组合
方式,可以达到desired的结果。
【举例】
express=“1^0|0|1”,desired=false
只有 1^((0|0)|1)和 1^(0|(0|1))的组合可以得到 false,返回 2。
express=“1”,desired=false
无组合则可以得到false,返回0
思路:在一段L…R上,遍历该字符,以每个逻辑运算符为最后结合的方式,选取其中的方法数,并根据不同逻辑运算符制定不同计数法则
int process(int L, int R, bool desire, string str) {
if (L == R) {
return desire ? (str[L] == '1' ? 1 : 0) : (str[L] == '1' ? 0 : 1);
}
int res = 0;
for (int cur = L + 1; cur < R; cur += 2) {
switch (str[cur])
{
case '&':
res = desire ? process(L, cur - 1, true, str) * process(cur + 1, R, true, str) :
process(L, cur - 1, false, str) * process(cur + 1, R, true, str) +
process(L, cur - 1, true, str) * process(cur + 1, R, false, str) +
process(L, cur - 1, false, str) * process(cur + 1, R, false, str);
break;
case '|':
res += desire ? process(L, cur - 1, false, str) * process(cur + 1, R, true, str) +
process(L, cur - 1, true, str) * process(cur + 1, R, false, str) +
process(L, cur - 1, true, str) * process(cur + 1, R, true, str) :
process(L, cur - 1, false, str) * process(cur + 1, R, false, str);
break;
case '^':
res += desire ? process(L, cur - 1, false, str) * process(cur + 1, R, true, str) +
process(L, cur - 1, true, str) * process(cur + 1, R, false, str) :
process(L, cur - 1, true, str) * process(cur + 1, R, true, str) +
process(L, cur - 1, false, str) * process(cur + 1, R, false, str);
break;
default:
break;
}
}
return res;
}
bool isValid(string str) {
if ((str.length() & 1) == 0) {
return false;
}
for (int i = 0; i < str.length(); i += 2) {
if ((str[i] != '0') && (str[i] != '1')) {
return false;
}
}
for (int j = 1; j < str.length(); j += 2) {
if ((str[j] != '&') && (str[j] != '|') && (str[j] != '^')) {
return false;
}
}
return true;
}
void Problem02_ExpressionNumber(string str, bool desire) {
if (str.length() < 1) {
return;
}
if (!isValid(str)) {
return;
}
cout << process(0, str.length() - 1, desire, str) << endl;
}
利用动态规划
使用例子,并建立true和false两张表。
由于L不可能小于R,L和R不可能在奇数行上,则上述不符合的均打叉。L==R时,根据base case填写。剩下的格子从左往右,从下往上填。
int Problem02_ExpressionNumber2(string str, bool desire) {
if (str.length() < 3) {
return -1;
}
if (!isValid(str)) {
return -1;
}
vector<vector<int>> t(str.length(), vector<int>(str.length()));
vector<vector<int>> f(str.length(), vector<int>(str.length()));
// 填写对角线
for (int i = 0; i < str.length(); i += 2) {
t[i][i] = str[i] == '1' ? 1 : 0;
f[i][i] = str[i] == '0' ? 1 : 0;
}
for (int i = 2; i < str.length(); i += 2) {
for (int j = i - 2; j >= 0; j -= 2) {
for (int k = j; k < i; k += 2) { // 从L到R开始遍历,以每个逻辑运算符都为最后一步试一遍
if (str[k + 1] == '&') {
t[j][i] += t[j][k] * t[k + 2][i];
f[j][i] += f[j][k] * t[k + 2][i] + t[j][k] * f[k + 2][i] + f[j][k] * f[k + 2][i];
}
else if (str[k + 1] == '|') {
t[j][i] += f[j][k] * t[k + 2][i] + t[j][k] * f[k + 2][i] + t[j][k] * t[k + 2][i];
f[j][i] += f[j][k] * f[k + 2][i];
}
else if (str[k + 1] == '^') {
t[j][i] += f[j][k] * t[k + 2][i] + t[j][k] * f[k + 2][i];
f[j][i] += t[j][k] * t[k + 2][i] + f[j][k] * f[k + 2][i];
}
}
}
}
return desire ? t[0][str.length() - 1] : f[0][str.length() - 1];
}
题目三
在数据加密和数据压缩中常需要对特殊的字符串进行编码。给定的字母表A由26个小写英文字母组成,即
A={a, b…z}。该字母表产生的长序字符串是指定字符串中字母从左到右出现的次序与字母在字母表中出现
的次序相同,且每个字符最多出现1次。例如,a,b,ab,bc,xyz等字符串是升序字符串。对字母表A产生
的所有长度不超过6的升序字符串按照字典排列编码如下:a(1),b(2),c(3)……,z(26),ab(27),
ac(28)……对于任意长度不超过16的升序字符串,迅速计算出它在上述字典中的编码。
输入描述:
第1行是一个正整数N,表示接下来共有N行,在接下来的N行中,每行给出一个字符串。输出描述:
输出N行,每行对应于一个字符串编码。
示例1:
输入
3
a
b
ab
输出
1
2
27
- 最终要的是构造出两个辅助函数
- f(int len)函数表示,长度为len的字符串有多少个
- g(char, int len)表示以char开头长度为len的子序列有多少个
- 输入一串字符串后,为了找到其位置,首先找到所有长度小于string的子序列个数;接着在和string长度相等的子序列中,找到开头小于string的所有子序列;接着找到第i个位置字符小于string的子序列。
int g1(int ch, int len) {
if (len == 1) {
return 1;
}
int sum = 0;
for (int i = ch + 1; i <= 26; i++) {
sum += g1(i, len - 1);
}
return sum;
}
int f1(int len) {
int sum = 0;
for (int i = 1; i <= 26; i++) {
sum += g1(i, len);
}
return sum;
}
void Problem03_StringToKth(string str)
{
if (str.length() < 1) {
return;
}
int sum = 0;
for (int i = 1; i < str.length(); i++) {// 比str短的全部加上
sum += f1(i);
}
int len = str.length();
// 找出长度为len但开头字符小于str[0]的子序列
for (int j = 1; j < str[0] - 'a' + 1; j++) {
sum += g1(j, len);
}
// 前i个固定
for (int i = 1; i < len; i++) {
for (int j = str[i - 1] - 'a' + 1 + 1; j < str[i] - 'a' + 1; j++) {
sum += g1(j, len - i);
}
}
cout << sum + 1 << endl;
}
题目四
子串题目
在一个字符串中找到没有重复字符子串中最长的长度。
例如:
abcabcbb没有重复字符的最长子串是abc,长度为3
bbbbb,答案是b,长度为1
pwwkew,答案是wke,长度是3
要求:答案必须是子串,“pwke” 是一个子字符序列但不是一个子字符串
自己想的答案代码
void Problem04_LongestNoRepeatSubstring(string str) {
if (str.length() < 2) {
return;
}
vector<int> dp(str.length());
int res = 1;
dp[0] = 1;
dp[1] = str[1] != str[0] ? 2 : 1;
for (int i = 2; i < str.length(); i++) {
int temp = dp[i - 1];
dp[i] = temp + 1;
for (int cur = i - temp; cur < i; cur++) {
if (str[cur] == str[i]) {
dp[i] = 1;
break;
}
}
res = max(res, dp[i]);
}
cout << res << endl;
}
存在两个瓶颈:
- i位置前一位对应的最长不相同子串长度
- i位置对应字符是否在前面出现时对应的位置
1和2取最小值则为最终瓶颈,即为该位置答案,和自己想的方法类似
public static int maxUnique(String str) {
if (str == null || str.equals("")) {
return 0;
}
char[] chas = str.toCharArray();
int[] map = new int[256];
for (int i = 0; i < 256; i++) {
map[i] = -1;
}
int len = 0;
int pre = -1;
int cur = 0;
for (int i = 0; i != chas.length; i++) {
pre = Math.max(pre, map[chas[i]]);
cur = i - pre;
len = Math.max(len, cur);
map[chas[i]] = i;
}
return len;
}
题目五
编辑距离问题
给定两个字符串str1和str2,再给定三个整数ic、dc和rc,分别代表插入、删
除和替换一个字符的代价,返回将str1编辑成str2的最小代价。
【举例】
str1=“abc”,str2=“adc”,ic=5,dc=3,rc=2
从"abc"编辑成"adc",把’b’替换成’d’是代价最小的,所以返回2
str1=“abc”,str2=“adc”,ic=5,dc=3,rc=100
从"abc"编辑成"adc",先删除’b’,然后插入’d’是代价最小的,所以返回8
str1=“abc”,str2=“abc”,ic=5,dc=3,rc=2
不用编辑了,本来就是一样的字符串,所以返回0
dp[i]{j}表示,将str1中前缀长度为i的字符串编辑为str2中长度为j的字符串
第一种可能:将str1中i-1中的字符变为str2中前j个字符,再将str1中第i个字符删掉。dp[i]{j}=dp[i-1][j} + delete
第二种可能:将str1种前i个变成str2种j-1的字符,再加上j字符 dp[i]{j}=dp[i][j-1} + add
第三种可能:将str1中前i-1个变成str2中j-1中的字符,再根据str2中j位置字符替换str1中i位置字符 dp[i]{j}=dp[i-1][j-1} + replace
第四种可能:相当于第三种的特例,当i位置和j位置字符相同,不需要加replace
void Problem05_EditCost(string str1, string str2, int ic, int dc, int rc) {
if (str1.length() < 1 || str2.length() < 1) {
return;
}
vector<vector<int>> dp(str1.length() + 1, vector<int>(str2.length() + 1));
// 第0行表示从空字符到j个字符需要的cost
dp[0][0] = 0;
for (int i = 1; i < dp[0].size(); i++) {
dp[0][i] = i * ic;
}
// 第0列表示从i个字符需要到空字符的最小cost,即直接delete
for (int i = 1; i < dp.size(); i++) {
dp[i][0] = i * dc;
}
for (int i = 1; i < dp.size(); i++) {
for (int j = 1; j < dp[0].size(); j++) {
if (str1[i - 1] == str2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
}
else {
dp[i][j] = dp[i - 1][j - 1] + rc;
}
dp[i][j] = min(dp[i][j], dp[i][j - 1] + ic);
dp[i][j] = min(dp[i][j], dp[i - 1][j] + dc);
}
}
cout << dp[dp.size() - 1][dp[0].size() - 1];
}
题目六
贪心,string函数的用法
给定一个全是小写字母的字符串str,删除多余字符,使得每种字符只保留一个,并让
最终结果字符串的字典序最小
【举例】
str = “acbc”,删掉第一个’c’,得到"abc",是所有结果字符串中字典序最小的。
str = “dbcacbca”,删掉第一个’b’、第一个’c’、第二个’c’、第二个’a’,得到"dabc",
是所有结 果字符串中字典序最小的。
首先遍历字符串,建立词频表。再次遍历数组,每个位置对应词频减一,直到有字符词频为0时停止,则从开头到当前位置中,选择任何一个字符并删除其左边的所有字符都不会使字符串中缺少某种字符。则在该范围内选择ASC码最小的保留,删除其左边所有字符和所有选定的字符,重新建立词频表,重复上述操作直至字符串为空.
string Problem06_RemoveDuplicateLettersLessLexi(string str) {
if (str.length() < 2) {
return str;
}
vector<int> map(26);
for (char s : str) {
map[s - 'a']++;
}
int minAsciiIndex = 0;
for (int i = 0; i < str.length(); i++) {
minAsciiIndex = str[i] < str[minAsciiIndex] ? i : minAsciiIndex;
if (--map[str[i] - 'a'] == 0) {
break;
}
}
char res = str[minAsciiIndex];
str.erase(0, minAsciiIndex);//erase(int pos, int n):删除从Pos开始的n个字符
int i = str.find(res);// minAsciiIndex对应的位置,后续需要把该字符全部删除
while (i != -1) {// find函数没有找到会返回-1
str.erase(i, 1);
i = str.find(res);
}
return res + Problem06_RemoveDuplicateLettersLessLexi(str);
}