吭哧做完了,心路历程:要动脑子啊好麻烦-写写吧-嗯嗯有思路了-我的思路真清晰啊-有漏洞改一下-有漏洞改一下-有漏洞改一下-到底还有几个漏洞-我累了不干了-通过了!
还有很多点待优化,先这么着吧:
1.关于回溯
思路重复:首先确定一个选择,然后进行递归,如果不符合条件,就回退到上一个状态,继续尝试其他选择。重点是:要先进入一个正确的选择才能递归,即在if(condition) 里进行递归
回溯的应用场景
- 组合问题:day1
- 分割问题
– 分割前提:分割不能乱序,如"aab"不能改成"aba",只能按次序逐个下刀
– 分割思路:想象成在哪个位置下刀切割,在哪个位置切第1刀,在哪个位置切第2刀,……,在哪里切第n刀,直到切的位置超出字符串的长度为止。
– 将“在i位置、切n刀”的组合想象成树结构,第一层就是第1刀的下刀排列组合,可能性有1~str.length()-1种;一共切的n刀就是树有n层,即要进行n次套娃遍历。 - <待施工>
2. 例题
lc131 分割回文串
前置知识点(字符串)
- 翻转字符串:string reverseStr = new StringBuilder(str).reverse().toString
- 判断字符串引用是否相等:reverseStr.equals(str)
- 提取字符串切片:str.substring(left,right) --注意:切片范围左闭右开,不包括right
思路
确定在哪个位置切第1刀,在哪个位置切第2刀,……,在哪里切第n刀,直到切的位置超出字符串的长度为止。
todo:
- 定义一个判断回文字符串的方法——如何判断?
- if true则生成切割后的子字符串——如何产生一个切片的字符串
- 定义一个回溯的递归方法:
- 定义参数:一个一维数组path储存每次的切刀结果,定义一个二维数组储存所有的path。
- 定义停止:切的位置超出字符串的长度
- 定义单层递归调用与录入path的逻辑:当确定为回文后,录入path,并进行递归,最后回退这种组合可能
易错点
把递归的作用域理解错了,需要<找到有效位置、能够产生回文>之后,才能在这个有效位置的基础上进行递归
代码实现
class Solution {
//定义需要的参数
public List<List<String>> result = new ArrayList<>();
public LinkedList<String> path = new LinkedList<>();
//主执行程序
public List<List<String>> partition(String s) {
backtrack(s,0);
return result;
}
//定义一个判断回文字符串的方法
public boolean isPalindrome(String str){
String reversedStr = new StringBuilder(str).reverse().toString();
return reversedStr.equals(str);
}
//定义一个回溯切割的递归方法。相当于start总是从0开始,end则需要套娃的时候决定
public void backtrack(String s, int start){
//停止:下一刀的位置超过字符串长
if(start>s.length()-1){
result.add(new ArrayList<>(path));
return;
}
//单层递归:在哪下刀?
for(int i=start; i<s.length();i++){
String temp = s.substring(start,i+1); //注意:.substring(left,right)的范围
if(isPalindrome(temp)==true){ //注意:只有在找到有效的切割位置才进行递归,注意递归的作用域
path.add(temp);
backtrack(s,i+1);
path.removeLast();//回退,看i+1位置下刀切割能不能产生新的回文组合
}
}
}
}
lc93. 复原 IP 地址
前置知识点(字符串)
- 注意双引号代表"字符串",而单引号代表’字符’,不要搞错
- 把字符串转为数字:
- 方法1:Integer.parseInt(str);
- 方法2:str-‘0’
- 把链表转为字符串的办法:
StringBuilder finalStr = new StringBuilder();
for(String str : path){
finalStr.append(str);
}
思路
- 所需参数:path-储存string,result-储存nums[],int start-确定下一层递归开始位置的游标
- 终止条件:遍历完整个字符串,即start > s.length()-1
- 无效字符串:若字符串长度≠4则字符串无效 (最后一种要怎么判断?)
- 单层递归调用条件:若符合“该地址字段valid”时,则:
- 录入path
- 进入下层递归
- 回退,在本层递归尝试其他组合
- 额外方法:符合“该地址字段valid”的判定办法
- 无效ip1:若ip地址中存在非数字
- 无效ip2:每个地址字段超过了(0,255)的范围
- 无效ip3:注意,只有多位数且开头为0才会判断为false
- 无效ip4:ip地址不是四个整数(这个没有放在方法里而是在终止条件里)
易错点
巨大漏洞!必须注意,有且只能有四段地址,不仅仅是超过四段地址的长度无效,如果只有三段也是illegal!
待优化方向
- 剪枝?
- 把所有对无效ip的检测都放入方法里
- 利用“只需要递归4次”来优化
待解决问题
-
无效ip1:为什么不能用正则?
前置知识点【正则】:用正则.matches(^和+)来检查字符串第1~n位是否匹配
if(s.matches(“^ [0-9]+”)) return false; -
无效ip3:为什么这个解法不行?超出了integer 2,147,483,647的范围?so为了避免出现这种情况,还是用for循环来计算ip转为数字的方法保险?
int num = Integer.parseInt(s);
return(num>=0 && num<255);
代码实现
class Solution {
public LinkedList<String> path = new LinkedList<>();
public List<String> result = new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
backtrack(s,0);
return result;
}
public void backtrack(String s, int start){
//终止:找到合规的字符串
if(start>s.length()-1){
//无效ip4:ip地址不是四个整数
if(path.size() !=8){
return;
}
//把path从链表转为字符串
StringBuilder finalStr = new StringBuilder();
for(String str : path){
finalStr.append(str);
}
//删掉最后一个"."
finalStr.deleteCharAt(finalStr.length()-1);
result.add(new String(finalStr.toString()));
return;
}
//单层递归调用逻辑
for(int i=start;i<s.length();i++){
//判断当前字符串组合能否通过isValid
String temp = new StringBuilder(s).substring(start,i+1).toString();
if(isValid(temp)){
//录入path:这里无脑放"数"+".",最后停止的时候再把path的最后一个"."删掉
path.add(temp);
path.add(".");
//开始递归下层
backtrack(s,i+1);
//回退:因为不确定新增字符串有几位,用stringbuilder还要先算index位置,还是用链表省事
path.removeLast();
path.removeLast();
}
}
}
public boolean isValid(String s){
int num=0;
for(int i=0;i<s.length();i++){
//无效ip1:若ip地址中存在非数字
if(s.charAt(i)>'9'||s.charAt(i)<'0'){
return false;
}
//无效ip2:超过了(0,255)的范围
num=num*10+(s.charAt(i)-'0');
if(num>255 || num<0){
return false;
}
}
//无效ip3:注意,只有*多位数*且开头为0才会判断为false
if(s.charAt(0)=='0' && s.length()>1){
return false;
}
return true;
}
}