单词的 广义缩写词
可以通过下述步骤构造:先取任意数量的 不重叠、不相邻 的子字符串,再用它们各自的长度进行替换。
例如,“abcde” 可以缩写为:
“a3e”(“bcd” 变为 “3” )
“1bcd1”(“a” 和 “e” 都变为 “1”)
“5” (“abcde” 变为 “5”)
“abcde” (没有子字符串被代替)
然而,这些缩写是 无效的 :
“23”(“ab” 变为 “2” ,“cde” 变为 “3” )是无效的,因为被选择的字符串是相邻的
“22de” (“ab” 变为 “2” , “bc” 变为 “2”) 是无效的,因为被选择的字符串是重叠的
给你一个字符串 word
,返回 一个由 word
的所有可能 广义缩写词 组成的列表 。按 任意顺序
返回答案。
1、自己的解法
官解是用了位运算,但是我对位运算的题目写得不多,也就不太敏感,就没想到,所以用的就是一种很朴素的思路–回溯。
思路
1、 首先我一开始看到题目想到这个题目和普通的回溯能做出来的字符串切割有很大的关联,可以说是字符串分割的加强版。
2、 如果不看数字的话,可以发现,第一步就是需要把字符串分割的所有情况都能列举出来,如:w o r d
、w o rd
、w ord
、word
、 wo rd
、 wor d
、 w or d
。
所以,我一开始是完成了第一步的代码,并且每一种分割都是用一个vector来保存,里面每个元素是字符串,这一步只是为了先实现一下枚举所有分割情况,最终的代码是在此基础上进行修改的。
class Solution {
public:
vector<vector<string>> res;
vector<string> arr;
void dfs(string word,int start){
if(start == word.size()){
// 如果已经取到最后一位了,表示 字符串分割完成,放入结果数组即可
res.push_back(arr);
return;
}
// 非常朴素且经典的回溯算法模版,i表示从某个下表开始取字符串的长度,最多只能取 原字符串的长度
for(int i = 1;i<=word.size();i++){
if(start+i>word.size()) continue;
string sub_word = word.substr(start,i);
arr.push_back(sub_word);
dfs(word,start+i);
arr.pop_back();
}
}
vector<string> generateAbbreviations(string word) {
dfs(word,0);
vector<string> ans;
// 打印结果
for(auto arr:res){
for(auto x:arr){
cout<<x<<'\t';
}
cout<<endl;
}
return ans;
}
};
力扣面板打印如下,可以看出已经实现了之前的“第一步”,枚举了所有分割情况,接下来就要思考,和这个题目的关联在哪里。
根据题意,他其实就是把下面的数组中的相邻元素,一个用字符串的长度表示,一个就用字符串本身表示,并且题目强调了,数字后一定接字符串,字符串后面一定接数字。
然后我就想到了,可以用一个bool值去控制,如果flag此时为0,那么我只追加这段子字符串的长度,如果flag为1,我就直接追加,这样就实现了。
完整代码如下:
class Solution {
public:
vector<string> res;
string arr;
void dfs(string word,int start,bool flag){
if(start == word.size()){
res.push_back(arr);
return;
}
int len;
// 全程都需要记得使用flag来控制状态
// flag用来控制 此次是放入子字符串还是数字
for(int i = 1;i<=word.size();i++){
if(start+i>word.size()) continue;
string sub_word = word.substr(start,i);
len = sub_word.size();
// len_len 表示 子字符串的长度变成数字字符串后的长度,有点拗口,
// 举例: 如果len =10,那么这个len的长度就等于2
int len_len = to_string(len).size();
if(flag == 0){
// 算长度
arr += to_string(len);
// 当前放入的是字符串长度,那么我下一步,就是要放入字符串本身
// 因此flag置为1
flag = 1;
}else {
arr += sub_word;
flag = 0;
}
dfs(word,start+i,flag);
// 如果flag为0,说明最后一步放入的是字符串,
if(flag == 0){
// 使用erase表示删去字符串的删除
arr.erase(arr.size()-len,len);
// 我最后一步放入的是字符串,上面一行代码删掉了字符串
// 说明现在字符串的末尾是数字,那么我下一步需要的是字符串,
// 因此把flag置为1,这样进入下一次循环,就是去放入字符串
flag = 1;
}else {
// 如果flag是1,表示最后一步放入的数字
// 最后一步放入的数字的话,如果我要pop掉,那么我需要把最后放入的数字的长度减去
// 而不能只是想当然地减去1个字符就可以了
arr.erase(arr.size()-len_len,len_len);
flag = 0;
}
}
}
vector<string> generateAbbreviations(string word) {
dfs(word,0,0);// 起始是先放入数字
dfs(word,0,1);// 起始是先放入子字符串
for(auto arr:res){
cout<< arr<<endl;
}
return res;
}
};
当然,这段代码其实用了两次dfs,为了控制开头是先用数字还是先用子字符串,所以提交后发现时间很长,代码不够好。
这只是自己的思路记录,使用了官解的位运算可以更快更高效。因为官解在网站上有,复制粘贴过来也没啥意思,所以就到这里啦~