分割回文串
链接: 131.分割回文串
思路
本题涉及两个关键问题:
- 切割问题,有不同的切割方式
- 回文问题
切割问题类似组合问题
例如对于字符串abcdef:
- 组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中再选取第三个…
- 切割问题:切割一个a之后,再bcdef中再去切割第二段,切割b之后在cdef中再切割第三段。
所以切割问题,也可以抽象为一棵树形结构,如图:
递归用来纵向遍历,for循环用来横向遍历,切割线(就是图中的红线)切割到字符串的结尾位置,说明找到了一个切割方法
此时可以发现,切割问题的回溯搜索的过程喝组合问题的回溯搜索过程是差不多的
回溯三部曲
-
递归函数参数
全局变量数组path存放切割后回文的字串,二维数组result存放结果集
本题递归函数参数还需要startIndex,因为切割过的地方,不能重复切割,和组合问题也是保持一致的
-
递归函数终止条件
从树形结构的图中可以看出:切割线切到了字符串最后面,说明找到了一种切割方法,此时就是本层递归的终止条件那么在代码里什么是切割线呢?
在处理组合问题的时候,递归参数需要传入startIndex,表示下一轮递归遍历的起始位置,这个startIndex就是切割线。
所以终止条件代码如下:
-
单层搜索逻辑
来看看在递归循环中如何截取子串呢?
在
for (int i = startIndex; i < s.size(); i++)
循环中,我们 定义了起始位置startIndex,那么 [startIndex, i] 就是要截取的子串。首先判断这个子串是不是回文,如果是回文,就加入在
vector<string> path
中,path用来记录切割过的回文子串。代码如下:
for (int i = startIndex; i < s.size(); i++) { if (isPalindrome(s, startIndex, i)) { // 是回文子串 // 获取[startIndex,i]在s中的子串 string str = s.substr(startIndex, i - startIndex + 1); path.push_back(str); } else { // 如果不是则直接跳过 continue; } backtracking(s, i + 1); // 寻找i+1为起始位置的子串 path.pop_back(); // 回溯过程,弹出本次已经填在的子串 }
注意切割过的位置,不能重复切割,所以,backtracking(s, i + 1); 传入下一层的起始位置为i + 1。
代码
class Solution {
List<String> path = new ArrayList<>();
List<List<String>> results = new ArrayList<>();
void backTracking(String s,int startIndex){
if (startIndex >= s.length()){
results.add(new ArrayList<>(path));
return;
}
for (int i = startIndex;i < s.length();i++){
if (isPalindrome(s,startIndex,i)){
//左闭右开区间
path.add(s.substring(startIndex,i + 1));
}else continue;
backTracking(s,i + 1);
path.remove(path.size() - 1);
}
}
boolean isPalindrome(String s,int left,int right){
if (left > right) return false;
while (left < right){
if (s.charAt(left) != s.charAt(right)) return false;
left++;
right--;
}
return true;
}
public List<List<String>> partition(String s) {
backTracking(s,0);
return results;
}
}
复原IP地址
链接: 93.复原IP地址
思路
[我自己的思路,想了很多,但很混乱,思路清晰比得上方法简便]
切割问题都可以使用回溯搜索法把所有可能性搜出来
切割问题可以抽象为树形结构
【先自己写出来一下子,边写边找思路】
回溯三部曲
-
递归参数
startIndex一定是需要的,因为不能重复分割,记录下一层递归分割的起始位置。
本题我们还需要一个变量pointNum,记录添加逗点的数量。
代码如下:
List<String> results = new ArrayList<>(); void backTracking(String s,int startIndex,int pointSum){ }
-
终止条件
终止条件和131.分割回文串 (opens new window)情况就不同了,本题明确要求只会分成4段,所以不能用切割线切到最后作为终止条件,而是分割的段数作为终止条件。
pointNum表示逗点数量,pointNum为3说明字符串分成了4段了。
然后验证一下第四段是否合法,如果合法就加入到结果集里
代码如下:
if (pointSum == 3){
if (isValid(s,startIndex,s.size() - 1)){
results.add(s);
}
return;
}
-
单层搜索的逻辑
在131.分割回文串 (opens new window)中已经讲过在循环遍历中如何截取子串。
在
for (int i = startIndex; i < s.size(); i++)
循环中 [startIndex, i] 这个区间就是截取的子串,需要判断这个子串是否合法。如果合法就在字符串后面加上符号
.
表示已经分割。如果不合法就结束本层循环,如图中剪掉的分支:
然后就是递归和回溯的过程:
递归调用时,下一层递归的startIndex要从i+2开始(因为需要在字符串中加入了分隔符.
),同时记录分割符的数量pointNum 要 +1。
回溯的时候,就将刚刚加入的分隔符.
删掉就可以了,pointNum也要-1。
判断子串是否合法
最后就是在写一个判断段位是否是有效段位了。
主要考虑到如下三点:
- 段位以0为开头的数字不合法
- 段位里有非正整数字符不合法
- 段位如果大于255了不合法
代码
class Solution {
List<String> result = new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
if (s.length() > 12) return result; // 算是剪枝了
backTrack(s, 0, 0);
return result;
}
// startIndex: 搜索的起始位置, pointNum:添加逗点的数量
private void backTrack(String s, int startIndex, int pointNum) {
if (pointNum == 3) {// 逗点数量为3时,分隔结束
// 判断第四段⼦字符串是否合法,如果合法就放进result中
if (isValid(s,startIndex,s.length()-1)) {
result.add(s);
}
return;
}
for (int i = startIndex; i < s.length(); i++) {
if (isValid(s, startIndex, i)) {
s = s.substring(0, i + 1) + "." + s.substring(i + 1); //在str的后⾯插⼊⼀个逗点
pointNum++;
backTrack(s, i + 2, pointNum);// 插⼊逗点之后下⼀个⼦串的起始位置为i+2
pointNum--;// 回溯
s = s.substring(0, i + 1) + s.substring(i + 2);// 回溯删掉逗点
} else {
break;
}
}
}
// 判断字符串s在左闭⼜闭区间[start, end]所组成的数字是否合法
private Boolean isValid(String s, int start, int end) {
if (start > end) {
return false;
}
if (s.charAt(start) == '0' && start != end) { // 0开头的数字不合法
return false;
}
int num = 0;
for (int i = start; i <= end; i++) {
if (s.charAt(i) > '9' || s.charAt(i) < '0') { // 遇到⾮数字字符不合法
return false;
}
num = num * 10 + (s.charAt(i) - '0');
if (num > 255) { // 如果⼤于255了不合法
return false;
}
}
return true;
}
}
重新敲一遍