1,字符串压缩
题目要求
字符串压缩。利用字符重复出现的次数,编写一种方法,实现基本的字符串压缩功能。比如,字符串aabcccccaaa会变为a2b1c5a3。若“压缩”后的字符串没有变短,则返回原先的字符串。你可以假设字符串中只包含大小写英文字母(a至z)。
示例1:
输入:“aabcccccaaa”
输出:“a2b1c5a3”
示例2:
输入:“abbccd”
输出:“abbccd”
解释:“abbccd"压缩后为"a1b2c2d1”,比原字符串长度更长。
提示:字符串长度在[0, 50000]范围内。
来源:力扣(LeetCode)
链接: https://leetcode-cn.com/problems/compress-string-lcci/.
题目分析
思路简单,从第二个字符开始,与第一个字符比较,若相等,则累加计数,若不等,将之前计数完成的字符+字符个数加到字符串末尾,并重新设置要比较的字符,且重新计数。见下面代码(C++)
代码
class Solution {
public:
string compressString(string S) {
int n=S.length();
if(n==0) return S;
string ans="";
int cnt=1;
char ch=S[0];
for(int i=1;i<n;i++)
{
if(S[i]==ch)
{
cnt++;
}
else{
ans+=ch+to_string(cnt);
ch=S[i];
cnt=1;
}
}
ans+=ch+to_string(cnt);//加上最后一个元素
int m=ans.length();
return m<n?ans:S;
}
};
``
2,字符串解码
题目要求
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
示例:
s = “3[a]2[bc]”, 返回 “aaabcbc”.
s = “3[a2[c]]”, 返回 “accaccacc”.
s = “2[abc]3[cd]ef”, 返回 “abcabccdcdcdef”.
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/decode-string/.
题目分析及代码
此题有两种解法,我看到题目最先想到的是用栈辅助,后来看题解发现还可以递归,或者说是dfs方法。下面分别介绍这两种方法及代码(C++)
(1) 用栈辅助
分析该题,发现要先处理最内的方括号(如果有方括号嵌套的话),这和栈先入后出的思想吻合。因为我用c++做题,很多童鞋用两个栈辅助,一个保存字符,一个保存数字,这是因为压入栈的时候,要把方括号 [ 前面的字符与数字同时压栈,留待稍后处理。我为了简便,参考了一个大佬的简明思想,用了pair,而pair与stack不太好一起用,所以用vector模拟栈。
思路如下:(做此题建议大家看 Krahets的精选题解,那个大佬分析的很好,代码也非常之简结,不过只有JAVA和Python)
挨个遍历字符串中每个字符,会遇到下面四种情况:
1,字符为数字
处理数字字符,将之转换为数字
2,字符为 ’ [ ’
首先将左括号前的字符res和数字num压入栈中,并将保存字符和数字的变量res和num 置为初始值,便于重新保存方括号里面的字符和数字
3,字符为 ’ ] ’
栈顶元素出栈,并拼接字符串,如3[a2[c]],遇到c右边的 ] 之后,当前栈顶元素为{a,2},分别保存在last_res和cur_num中,当前字符串变量res为c,num=0,则拼接好的字符串应该为res=last_res+cur_num*res
4,字符为字母
加到字符串res末尾
代码如下:
class Solution {
public:
typedef pair<int,string> pa;
string decodeString(string s) {
vector<pa> vestack;
int retis=0;
string res="";
for(auto i:s)
{
if(i=='[')
{
vestack.push_back({retis,res}); //[ 左边的字母和表示[]里面字母要重复的次数进栈
retis=0; //新的[]里的字符和数字置零,重新记录
res="";
}
else if(i==']')
{
pa tmp=vestack[vestack.size()-1]; //栈顶元素弹出
int cur_retis=tmp.first; //当前[]里面字符串要重复的次数
string last_res=tmp.second;//[]之前的字符串
vestack.pop_back();
for(int i=0;i<cur_retis;i++) //[]前的字符串加上cur_retis个[]里面的字符串
{
last_res +=res;
}
res=last_res;
}
else if('0'<=i&&i<='9')
{
retis=(retis*10)+(i-'0');//*10是为了遇到重复次数大于10的情况,该步骤将字符数字转换为数字
}
else
res+=i; //遇到字母,添加到当前res最后
}
return res;
}
};
其中,auto不太明白的可以参考这个博主的博客
https://blog.csdn.net/qq_34037046/article/details/85221622.
(2) 递归(DFS)
递归呢,思路其实和栈差不多,关键是要想清楚递归开始与终止条件。这里提供一种思路,即遇到 [ 开启递归,并处理跳出下一层递归之后的字符串拼接工作,遇到 ] 结束递归,返回上一层。
代码如下:
class Solution {
public:
string decodeString(string s) {
int i=0;
return digui(s,i);//dfs
}
string digui(const string& s,int& i) //如果这里i不是引用传递,在递归时i的值不变,
{ //导致跳出递归后难以定位i位置
string res="";
int renums=0;
while(i<s.length())
{
if(s[i]>='0'&&s[i]<='9')
{
renums=(renums*10)+(s[i]-'0');
}
else if(s[i]=='[')//ch为[时开启递归
{
string tmp=digui(s,++i);
while(renums)//跳出下一层递归后,令[]前的字符串+[]前数字renums个[]里的字符串
{
res+=tmp;
renums--;
}
}
else if(s[i]==']')//递归结束条件
{
return res;
}
else{
res+=s[i];
}
i++;
}
return res;
}
};
在写这个程序的时候,我因为digui 函数传入参数 i 的时候没有用引用传参,导致结束下一层循环之后,i 的值没有变化,无法定位 i 的具体数值,自己给 i 赋值,发现有一类样例过不了,感觉自己思路对,但是可能还是没想到位吧,所以直接引用传参还是方便清晰啊。此外,也可以不用 i ,有一个题解用了istringstream传参,直接传入每个字符,也很方便。
PS:补充一个类似这道题的字符解码题,是前几天腾讯一面时候要求写的,那个题目做了一点改动,给的字符串格式为 HG[3|B[2|CA]]F,加了 | 字符,展开为HGBCACABCACABCACAF。这个的代码待补充。。。