一、复原ip地址
String.substring(int start):从start开始,到结尾。所有的字符组成的字符串。
String.substring(int begin,int end)。包头不包尾
判断是否是ip地址的函数:isValid(String s,int start,int end);
start<end(说明字符串不止一位)&&s.chartAt(start)=='0' ip地址的首字母不能为0(除非它是0)
必须在0-255范围之间。可以判断每一个字母是否在0-9之间。然后判断总和是否0-255
回溯法:
1.返回值:void 参数:String s,int startIndex(一次循环的起始位置),int countNum(.的个数)
2.终止条件:if(countNum==3)出现三个.的时候,证明已经切割到末尾,如果末尾也满足ip地址段的要求,就把它添加到result中
3.单层递归逻辑:
起点为startIndex,终点为i。[startIndex,i]; 返回为true,加一个.,然后继续向下递归,回溯。
代码:
class Solution {
List<String> result=new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
if(s.length()==0||s==null||s.length()>12)return result;
backTracing(s,0,0);
return result;
}
public void backTracing(String s,int startIndex,int countNum){
//countNum表示.的个数
//终止条件
if(countNum==3){
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);
countNum++;
backTracing(s,i+2,countNum);
countNum--;
s=s.substring(0,i+1)+s.substring(i+2);
}else{
break;
}
}
}
//判断是否是合法ip地址
public boolean isValid(String s,int start,int end){
if(start>end)return false;
if(start!=end&&s.charAt(start)=='0')return false;
int sum=0;
for(int i=start;i<=end;i++){
if(s.charAt(i)>'9'||s.charAt(i)<'0')return false;
sum=sum*10+(s.charAt(i)-'0');
if(sum>255)return false;
}
return true;
}
}
二、子集问题
给定一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
思路:这道题跟之前的回溯问题都不太一样,之前的问题都是在叶子节点收割结果,这道题是在每一个节点都要去收割结果,因此在处理单层递归逻辑的时候要注意。
终止条件:startIndex>=nums.length(nums.length-1的时候是最后一个节点还要进去处理,只有当>=的时候才终止);
单层递归逻辑:每次递归进来都要把path添加到result中。
代码:
class Solution {
List<Integer> path=new ArrayList<>();
List<List<Integer>> result=new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
backTracking(nums,0);
return result;
}
public void backTracking(int[] nums,int startIndex){
//子集是将每一个节点都添加到result中,因此每次递归一进来就要放进去
result.add(new ArrayList<>(path));
//终止条件
if(startIndex>=nums.length)return;
//单层递归逻辑
for(int i=startIndex;i<nums.length;i++){
path.add(nums[i]);
backTracking(nums,i+1);
path.remove(path.size()-1);
}
}
}
三、子集II
给你一个整数数组 nums
,其中可能包含重复元素,请你返回该数组所有可能的
子集(幂集)。解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
重点:如何去重。(和之前组合去重的逻辑是一样的)
当for循环非第一层时,即i>startIndex时,如果出现nums[i]==nums[i-1],continue;(确保是树层上的去重
因为这种情况在nums[i-1]的时候已经遇到过了
所以这道题就是在上一题的基础上加一个去重操作。
代码:
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
backTracking(nums, 0);
return result;
}
public void backTracking(int[] nums, int startIndex) {
// 子集是将每一个节点都添加到result中,因此每次递归一进来就要放进去
result.add(new ArrayList<>(path));
// 终止条件
if (startIndex >= nums.length)
return;
// 单层递归逻辑
for (int i = startIndex; i < nums.length; i++) {
//去重条件
if(i>startIndex&&nums[i]==nums[i-1])continue;
//正常递归逻辑
path.add(nums[i]);
backTracking(nums, i + 1);
path.remove(path.size() - 1);
}
}
}
四、非递减子序列
使用一个集合存储已经遍历过的元素
去重:当nums[i]<集合中的最后一个元素(筛除,但是前提是集合中不能为空)、如果遇到已经遍历过的元素(筛除)。---->:
if(!path.isEmpty()&&nums[i]<path.get(path.size()-1)||list.contains(nums[i]))continue:
终止条件:(path.size()>=2)result.add(path);if(startIndex>=nums.length)return;
单层递归逻辑:在for循环中先判断是否满足在去重条件----->然后进行正常操作。
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
backTracking(nums,0);
return result;
}
public void backTracking(int[] nums, int startIndex) {
// 结果分布在节点上
if (path.size() >= 2)
result.add(new ArrayList<>(path));
// 终止条件
if (startIndex >= nums.length)
return;
// 单层递归逻辑
List<Integer> exists = new ArrayList<>();
for (int i = startIndex; i < nums.length; i++) {
if(!path.isEmpty()&&path.get(path.size()-1)>nums[i]||exists.contains(nums[i]))
continue;
exists.add(nums[i]);
path.add(nums[i]);
backTracking(nums,i+1);
path.remove(path.size()-1);
}
}
}
五、全排列
给定一个不含重复数字的整数数组 nums
,返回其 所有可能的全排列 。可以 按任意顺序 返回答案。输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
排列后的数组不能重复。递归之后不需要indexStart来指明遍历的起始位置。
终止条件:if(nums.length==path.size())result.add(path);
单层递归逻辑:for(int i=0;i<num.length;i++){if(!path.contains(nums[i]))}
// 解法2:通过判断path中是否存在数字,排除已经选择的数字
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> permute(int[] nums) {
if (nums.length == 0) return result;
backtrack(nums, path);
return result;
}
public void backtrack(int[] nums, LinkedList<Integer> path) {
if (path.size() == nums.length) {
result.add(new ArrayList<>(path));
}
for (int i =0; i < nums.length; i++) {
// 如果path中已有,则跳过
if (path.contains(nums[i])) {
continue;
}
path.add(nums[i]);
backtrack(nums, path);
path.removeLast();
}
}
}
六、全排列II
给定一个可包含重复数字的整数集合 nums
,按任意顺序 返回它所有不重复的全排列。
因为数字可重复,所以该题就是在上一道题的基础上加一个去重的功能.但是之前去重的功能我都是通过i>startIndex来实现,这道题每次递归循环都从0开始的。因此不能使用那种方式。只能使用used[]数组来实现。
used[]数组,树层之间每次都是000,used是会更新的。树层之间的去重是必需的。条件是
if(i>0&&nums[i]==nums[i-1]&&used[i]==false)。如果遇到11这种情况,used[i]=false;代表它是一个树层之间的。
树枝之间是一个for循环中的一次遍历下来的,因此used不会更新。树枝之间的去重是不需要的,因为一个数组中可能存在一样的值。(纵向)
代码:
class Solution {
private List<Integer> path = new ArrayList<>();
private List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
boolean[] used = new boolean[nums.length];// 定义一个数组 记录是否使用过xxx
Arrays.fill(used, false);// 初始都是false
Arrays.sort(nums);
backTracking(nums, used);
return result;
}
public void backTracking(int[] nums,boolean[] used){
//终止条件
if(path.size()==nums.length){
result.add(new ArrayList<>(path));
return;
}
//单层递归逻辑
for(int i=0;i<nums.length;i++){
//去重
if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false)continue;
//正常
if(used[i]==false){
path.add(nums[i]);
used[i]=true;
backTracking(nums,used);
path.remove(path.size()-1);
used[i]=false;
}
}
}
}
七、N皇后(经典)
矩阵每一行的每一个元素都和其他元素组合,看看有没有满足n皇后规则的。满足一行就会往下递归一次,递归一次row就会加一次,直到row==n,(row是从0->n-1的),就可以把chessboard转为集合放到结果中了,
返回值:void。参数:char[][] chessboard,int n(矩阵的规模),int row
终止条件:if(row==n);
单层递归逻辑:循环,每一个点(x,y)都要判断一下,如果满足n皇后规则,就递归到下一行,继续选取某个点,如果满足,继续下一行,直到(row==n);
重点:判断是否满足n皇后:对角线(45°和135°)和行/列都不能有重复的
将二维数组转换成List<String>类型的。
代码:
class Solution {
List<List<String>> res = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
//创建一个棋盘
char[][] chessboard = new char[n][n];
for (char[] c : chessboard) {
Arrays.fill(c, '.');
}
backTrack(n, 0, chessboard);
return res;
}
//回溯
public void backTrack(int n, int row, char[][] chessboard) {
//当遍历到第n行的时候,说明已经结束了,因为行是从0->n-1
if (row == n) {
res.add(Array2List(chessboard));
return;
}
for (int col = 0; col < n; ++col) {
//如果该点符合n皇后规则
if (isValid(row, col, n, chessboard)) {
chessboard[row][col] = 'Q';
backTrack(n, row + 1, chessboard);
chessboard[row][col] = '.';
}
}
}
public List Array2List(char[][] chessboard) {
List<String> list = new ArrayList<>();
for (char[] c : chessboard) {
list.add(String.copyValueOf(c));
}
return list;
}
public boolean isValid(int row, int col, int n, char[][] chessboard) {
// 检查列
for (int i = 0; i < row; ++i) { // 相当于剪枝
if (chessboard[i][col] == 'Q') {
return false;
}
}
// 检查45度对角线
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
// 检查135度对角线
for (int i = row - 1, j = col + 1; i >= 0 && j <= n - 1; i--, j++) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
return true;
}
}
八、解数独(回溯暴搜)
整体思想:双层循环(对应一个具体的x/y),首先判断(x,y)是否为空,不为空直接continue;如果为空,然后进行for循环判断1-9,哪一个数字是合适的。找到一个合适的数字,向下递归,然后回溯。每向下递归一次,都要返回一个值,看是否合适。
重点:对数字进行判断,一行一列,以及所在的小三角都不能存在i。
返回值:boolean 参数:char[][]board
结束条件:无?
单层递归逻辑:整体思想中
代码:
class Solution {
public void solveSudoku(char[][] board) {
backTrack(board);
}
public boolean backTrack(char[][] board){
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
if(board[i][j]!='.') continue;
for(char n='1';n<='9';n++){
if(isValid(i,j,board,n)){
board[i][j]=n;
if(backTrack(board))return true;
board[i][j]='.';
}
}
return false;//9个数都不行的话 直接返回false
}
}
return true;
}
public boolean isValid(int row,int col,char[][] board,char val){
//对行检查
for(int i=0;i<9;i++){
if(board[row][i]==val)return false;
}
//对列检查
for(int j=0;j<9;j++){
if(board[j][col]==val)return false;
}
//对小三角进行检查
int startRow=(row/3)*3;//第一个三角是从0行开始的
int startCol=(col/3)*3;//同理 从第0列开始
for(int i=startRow;i<startRow+3;i++){
for(int j=startCol;j<startCol+3;j++){
if(board[i][j]==val)return false;
}
}
return true;
}
}