【刷题系列】回溯法

系列汇总:《刷题系列汇总》



——————《剑指offeer》———————

1. 机器人的运动范围(重复)

  • 题目描述:地上有一个rows行和cols列的方格。坐标从 [0,0][rows-1,cols-1]。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于threshold的格子。 例如,当threshold18时,机器人能够进入方格[35,37],因为3+5+3+7 = 18。但是,它不能进入方格[35,38],因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
  • 优秀思路(我的):dfs
public class Solution{
    boolean[][] vis;
    public int movingCount(int threshold, int rows, int cols){
        if(rows == 0 || cols == 0) return 0;
        if(threshold == 0) return 1;
        vis = new boolean[rows][cols];
        return move(threshold,rows,cols,0,0);// 从(0,0)开始
    }
    // dfs
    private int move(int threshold, int rows, int cols, int i_cur, int j_cur){
        if(sum(i_cur,j_cur) > threshold || i_cur >= rows || j_cur >= cols || vis[i_cur][j_cur]) return 0;
        vis[i_cur][j_cur] = true;
        int count = 0;
        count++;
        // 只需要搜索右边和下面
        int[] dirs = new int[]{1,0,1}; // 方向矩阵
        for(int i = 0;i < 2;i++) count += move(threshold,rows,cols,i_cur + dirs[i],j_cur + dirs[i+1]);
        return count;
    }
    // 计算坐标数字之和
    private int sum(int i, int j) {
        int sum = 0;
        while(i != 0) {
            sum += i%10;
            i = i/10;
        }
        while(j != 0) {
            sum += j%10;
            j = j/10;
        }
        return sum;
    }
}

——————《LeectCode》———————

1. 括号生成(重复)

  • 题目描述:数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
  • 我的思路(9% - 80%):利用括号生成规则进行递归,并利用一个数字统计当前括号数量之差,遇到( 加一,否则减一。
class Solution {
    List<String> res = new ArrayList<>();
    public List<String> generateParenthesis(int n) {
        if(n == 0) return res;
        Parenthesis(0,'(',"",n,n);
        return res;
    }
    // addChar——需要添加的括号,n1——左括号数,n2——右括号数
    private void Parenthesis(int numP,char addChar,String cur,int n1,int n2){
        if(addChar == '('){
            numP++;
            n1--;
        }else{
            numP--;
            n2--;
        }
        cur += addChar;

        if(n2 == 0){
            res.add(cur);
            return;
        }

        if(numP == 0){ //只能加(
            Parenthesis(numP,'(',cur,n1,n2);
        }else{
            if(n1 > 0){
                Parenthesis(numP,'(',cur,n1,n2);
                Parenthesis(numP,')',cur,n1,n2);
            }else{
                Parenthesis(numP,')',cur,n1,n2);
            }
        }
    }
}
  • 优秀思路:整体思路一致,不过避免了numP的使用和一大堆判断
class Solution {
    List<String> res = new ArrayList<>();
    public List<String> generateParenthesis(int n) {
        if(n == 0) return res;
        Parenthesis('(',"",n,n);
        return res;
    }
    private void Parenthesis(char addChar,String cur,int left,int right){
        if(right == 0){
            res.add(cur);
            return;
        }

        if(left == right){ //这样就避免了出现多加右括号,相当于我程序中的numP
            Parenthesis('(',cur+'(',left-1,right);
        }else{
            if(left > 0) Parenthesis('(',cur+'(',left-1,right);
            if(right > 0) Parenthesis(')',cur+')',left,right-1);
        }
    }
}

2. 电话号码的字母组合

  • 题目描述:给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
  • 我的思路(10% - 25%):暴力遍历法
class Solution {

    HashMap<Character,String> map = new HashMap(){{
        put('2',"abc");
        put('3',"def");
        put('4',"ghi");
        put('5',"jkl");
        put('6',"mno");
        put('7',"pqrs");
        put('8',"tuv");
        put('9',"wxyz");
    }};

    public List<String> letterCombinations(String digits) {
        List<String> res = new ArrayList<>();
        if(digits.length() == 0) return res;
        for(char c:map.get(digits.charAt(0)).toCharArray()){
            res.add(c+"");
        }
        
        for(int i = 1;i < digits.length();i++){
            List<String> temp = new ArrayList<>();
            for(String s : res){
                for(char c:map.get(digits.charAt(i)).toCharArray()){
                    temp.add(s+c);
                }
            }
            res = temp;
        }
        return res;
    }
}
  • 优秀思路:溯过程中维护一个字符串,表示已有的字母排列(如果未遍历完电话号码的所有数字,则已有的字母排列是不完整的)。该字符串初始为空。每次取电话号码的一位数字,从哈希表中获得该数字对应的所有可能的字母,并将其中的一个字母插入到已有的字母排列后面,然后继续处理电话号码的后一位数字,直到处理完电话号码中的所有数字,即得到一个完整的字母排列。然后进行回退操作,遍历其余的字母排列。(注意代码重要标记语句)
class Solution {

    HashMap<Character,String> map = new HashMap(){{
        put('2',"abc");
        put('3',"def");
        put('4',"ghi");
        put('5',"jkl");
        put('6',"mno");
        put('7',"pqrs");
        put('8',"tuv");
        put('9',"wxyz");
    }};

    public List<String> letterCombinations(String digits) {
        List<String> res = new ArrayList<>();
        if(digits.length() == 0) return res;
        helper(res,new StringBuilder(),digits,0);
        return res;
    }

    private void helper(List<String> res,StringBuilder sb,String digits,int index){
        if(index == digits.length()) res.add(sb.toString());
        else{
            String letters = map.get(digits.charAt(index));
            for(char c : letters.toCharArray()){
                sb.append(c);
                helper(res,sb,digits,index+1);
                sb.deleteCharAt(index); // 重要!!!由于要保证循环中传入的sb不同,故需删除暂时添加在最后位置的元素
            }
        }
    }
}

3. 全排列

  • 题目描述:给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
  • 优秀思路(我的)
import java.util.*;
class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        if(nums == null || nums.length == 0) return res;
        helper(res,nums,new ArrayList<Integer>(),0,nums.length);
        return res;
    }
    // 注意参数里面List<Integer> temp 不能写作ArrayList<Integer> temp
    // 否则最终直接add temp为空值
    private void helper(List<List<Integer>> res,int[] nums,List<Integer> temp,int index,int len){
        if(index == len) res.add(new ArrayList<Integer>(temp));
        else{
            for(int i = 0;i < nums.length;i++){
                temp.add(nums[i]);
                // 更新剩下的数组
                int[] arr = new int[nums.length-1];
                for(int j = 0;j<arr.length;j++){
                    if(j < i) arr[j] = nums[j];
                    else arr[j] = nums[j+1];
                }
                helper(res,arr,temp,index+1,len);
                temp.remove(index); // 抛掉最后一位新加的
            }
        }
    }
}

4.正则表达式匹配(暂留)

  • 题目描述:给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.''*' 的正则表达式匹配。所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
    • '.' 匹配任意单个字符
    • '*' 匹配零个或多个前面的那一个元素
  • 优秀思路:动态规划

5. 组合总和

  • 题目描述:给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
    candidates 中的数字可以无限制重复被选取。
  • 我的思路(5% - 5%):寻找所有组合,符合条件则对list排序后加入res(避免重复)
import java.util.*;
class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        if(candidates.length == 0 || candidates == null) return res;
        helper(res,candidates,target,new ArrayList(),0);
        return res;
    }
    private void helper(List<List<Integer>> res,int[] candidates, int target,List<Integer> temp,int sum){
        if(sum == target){
            ArrayList<Integer> tempRes = new ArrayList(temp);
            Collections.sort(tempRes);
            if(!res.contains(tempRes)) res.add(tempRes);
            return;
        }else if(sum > target) return;
        for(Integer c:candidates){
            temp.add(c);
            sum += c;
            helper(res,candidates,target,temp,sum);
            sum -= c;
            temp.remove(c);
        }
    }
}
  • 优秀思路(改造的自己的代码):本题的难点在于如何避免重复,只需要让下一轮的对数组的遍历起点从当前位置开始就可以,比如对于nums = [2,3,6,7],如本论遍历到了3,则3的后一论只能从3开始,因为 3,2 一定与 2,3 重复
class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        if(candidates.length == 0 || candidates == null) return res;
        helper(res,candidates,target,new ArrayList(),0,0);
        return res;
    }
    private void helper(List<List<Integer>> res,int[] candidates, int target,List<Integer> temp,int sum,int start){
        if(sum == target) res.add(new ArrayList(temp));
        else if(sum > target) return;
        else{
            for(int i = start;i < candidates.length;i++){
                temp.add(candidates[i]);
                helper(res,candidates,target,temp,sum+candidates[i],i); // 下次搜索从当前位置开始即可避免重复
                temp.remove(temp.size()-1);
            }
        }
    }
}

6. 解数独(需重写)

  • 题目描述:编写一个程序,通过填充空格来解决数独问题。数独的解法需 遵循如下规则
    • 数字 1-9 在每一行只能出现一次。
    • 数字 1-9 在每一列只能出现一次。
    • 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
    • 数独部分空格内已填入了数字,空白格用 '.' 表示
  • 我的思路(超出时间限制):本地验证正确。
    • 1.存储所有行、列、九宫格数据
    • 2.根据这些数据确定每个空位置的可能数字
    • 3.对只有一种可能数字的位置进行填入
    • 4.更新相关行列及九宫格内的数字可能
    • 5.删除填入的键值
    • 5.重复3、4、5直至map为空
class Solution {
    public void solveSudoku(char[][] board) {
        int row = board.length;
        int col = board[0].length;
        Map<Integer,HashSet<Character>> allRow = new HashMap<>();
        Map<Integer,HashSet<Character>> allCol = new HashMap<>();
        Map<Integer,HashSet<Character>> allBox = new HashMap<>();
        Map<String,HashSet<Character>> possiRes = new HashMap<>();
        // 初始化
        for(int i = 0;i < 9;i++){
            allRow.put(i,new HashSet<Character>());
            allCol.put(i,new HashSet<Character>());
            allBox.put(i,new HashSet<Character>());
        }
        // 记录所有行、列、九宫格已有数字
        for(int i = 0;i < row;i++){
            for(int j = 0;j < col;j++){
                if(board[i][j] != '.'){
                    allRow.get(i).add(board[i][j]);
                    allCol.get(j).add(board[i][j]);
                    allBox.get(whichBox(i,j)).add(board[i][j]);
                }
            }
        }
        // 根据行、列、九宫格,确定每个框里可能的数
        HashSet<Character> maybe= new HashSet<>();
        for (char i = '1'; i <= '9' ; i++) maybe.add(i);
        HashSet<Character> tempC;
        for(int i = 0;i < row;i++){
            for(int j = 0;j < col;j++){
                if(board[i][j] == '.'){
                    tempC = new HashSet<>();
                    tempC.addAll(maybe);
                    tempC.removeAll(allRow.get(i));
                    tempC.removeAll(allCol.get(j));
                    tempC.removeAll(allBox.get(whichBox(i,j)));
                    possiRes.put(String.valueOf(i)+String.valueOf(j),tempC);
                }
            }
        }
        // 先确定只有一种可能的
        while(!possiRes.isEmpty()){
            ArrayList<String> del = new ArrayList<>();
            for(String key:possiRes.keySet()){
                if(possiRes.get(key).size() == 1){
                    int tempRow = key.charAt(0)-'0';
                    int tempCol = key.charAt(1)-'0';
                    for(Character c:possiRes.get(key)){
                        board[tempRow][tempCol] = c;
                        del.add(key); // 需清除位置
                        // 更新相关行列九宫格的值
                        for(String key2:possiRes.keySet()){
                            if(!key2.equals(key)){
                                if(key2.charAt(0) == key.charAt(0)){ // 相关行
                                    possiRes.get(key2).remove(c);
                                }
                                if(key2.charAt(1) == key.charAt(1)){ // 相关列
                                    possiRes.get(key2).remove(c);
                                }
                                if(whichBox(key2.charAt(0)-'0',key2.charAt(1)-'0') == whichBox(tempRow,tempCol)){ //相关九宫格
                                    possiRes.get(key2).remove(c);
                                }
                            }
                        }
                    }
                }
            }
            for(String temps:del) possiRes.remove(temps);
        }
    }
    // 判断属于哪一个九宫格
    private int whichBox(int i,int j){
        if(i<= 2 && j <= 2) return 0;
        else if(i <= 2 && j <= 5) return 1;
        else if(i<= 2 && j <= 8) return 2;
        else if(i<= 5 && j <= 2) return 3;
        else if(i<= 5 && j <= 5) return 4;
        else if(i<= 5 && j <= 8) return 5;
        else if(i<= 8 && j <= 2) return 6;
        else if(i<= 8 && j <= 5) return 7;
        else if(i<= 8 && j <= 8) return 8;
        return -1;
    }
}
  • 优秀思路
    • 1.存储各行、列、宫格的数据:
      • 1.这里用了小技巧就是将数字转换为数组的列索引进行存储,每个表的 0-8 列分别代表数字 1-9
      • 2.计算宫格数使用了小技巧:k = (i / 3) * 3 + j / 3; // 宫格号 0 ~ 8
    • 2.从数独矩阵的第1个格子处开始搜索:需要注意出现了走不通的情况时,需要重新设置改格为空
class Solution {
    public void solveSudoku(char[][] board) {
        // 分别存储第i行、第j列、第k个box已有哪些数据
        boolean[][] row = new boolean[9][9];
        boolean[][] col = new boolean[9][9];
        boolean[][] box = new boolean[9][9];
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (board[i][j] == '.') continue;
                else{
                    int num = board[i][j] - '1'; // 0 ~ 8
                    int k = (i / 3) * 3 + j / 3; // 宫格号 0 ~ 8
                    // 将已有数据转为列索引进行存储
                    // 每个表的 0-8 列分别代表数字 1-9
                    row[i][num] = col[j][num] = box[k][num] = true;
                }
            }
        }
        solveSudokuHelper(board, 0, row, col, box);
    }
    // n 为已遍历位置数量
    // 返回值为
    private boolean solveSudokuHelper(char[][] board, int n, boolean[][] row, boolean[][] col, boolean[][] box) {
        if (n == 81) return true; // 最多为 80
        int i = n / 9, j = n % 9; // 行列值
        if (board[i][j] != '.') return solveSudokuHelper(board, n + 1, row, col, box);// 已存在数字,进入下一空格
        else{
            int k = (i / 3) * 3 + j / 3; // 宫格数
            for (char num = '1'; num <= '9'; num++) {
                if(row[i][num-'1'] || col[j][num-'1'] || box[k][num-'1']) continue;// 所在行、列、格里存在该数
                else{
	                board[i][j] = num;
	                row[i][num-'1'] = col[j][num-'1'] = box[k][num-'1'] = true;
	                // 若符合要求 return true,否则return false,自动回溯到上一步
	                if (solveSudokuHelper(board, n + 1, row, col, box)) return true;
	                row[i][num-'1'] = col[j][num-'1'] = box[k][num-'1'] = false;
                }
            }
            // 重要!!!进入到这里说明遇到了走不通的状态,恢复当前格board[i][j]为未填入状态,return false
            board[i][j] = '.';
            return false;
        }
    }
}

7. 单词搜索

  • 题目描述:给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
  • 优秀思路(我的)dfs + 回溯,位置的4条路都走不通时一定要恢复该节点的访问状态
class Solution {
    boolean flag = false;
    public boolean exist(char[][] board, String word) {
        int row = board.length, col = board[0].length; 
        if(board == null || row == 0 || col == 0) return false;
        for(int i = 0;i < row;i++){
            for(int j = 0;j < col;j++){
                if(board[i][j] == word.charAt(0)){
                    dfs(board,word,new boolean[row][col],0,i,j);
                }
                if(flag) return true;
            }
        }
        return flag;
    }
    private void dfs(char[][] board, String word,boolean[][] vis,int index,int i, int j){
        if(i < 0 || i >= board.length || j < 0 || j >= board[0].length || vis[i][j] || board[i][j] != word.charAt(index)) return;
        if(index == word.length()-1){
            flag = true;
            return;
        }
        vis[i][j] = true;
        dfs(board,word,vis,index+1,i+1,j);
        dfs(board,word,vis,index+1,i-1,j);
        dfs(board,word,vis,index+1,i,j+1);
        dfs(board,word,vis,index+1,i,j-1);
        vis[i][j] = false; // 该位置的4条路都走不通一定要恢复该节点的访问状态
    }
}

8. 子集

  • 题目描述:给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
  • 优秀思路(我的):回溯,避免重复要设置下一轮的起点为本轮的下一位置
class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        if(nums == null || nums.length == 0) return res;
        res.add(new ArrayList<>());
        dfs(res,new ArrayList<>(),nums,0);
        return res;
    }
    private void dfs(List<List<Integer>> res,List<Integer> temp,int[] nums,int start){
        if(start == nums.length) return;
        for(int i = start;i<nums.length;i++){
            temp.add(nums[i]);
            res.add(new ArrayList<>(temp));
            dfs(res,temp,nums,i+1);
            temp.remove(temp.size()-1);//移除最后一位
        }
    }
}

9. N皇后问题(需重写)

  • 题目描述n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q''.' 分别代表了皇后和空位。
  • 我的思路(只能求出部分解):【回溯递归】,每次放置一个皇后之后在可能的皇后位置继续搜索,即方向矩阵int[] dirs = new int[]{1,2,-1,-2,1,-2,-1,2,1};代表的位置
import java.util.*;
public class Solution {
    List<String> res = new ArrayList<>();
    public static void main(String[] args) {
        Solution solution = new Solution();
        System.out.println(solution.solveNQueens(8));
    }

    int[] dirs = new int[]{1,2,-1,-2,1,-2,-1,2,1};
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> res = new ArrayList<>();
        if(n==0 || n==2 || n== 3) return res;
        boolean[][] usedToPutQ = new boolean[n][n];
        for(int i = 0;i < n;i++){
            for(int j = 0;j < n;j++){
                if(!usedToPutQ[i][j]){ // 曾经没放过Q的地方
                    dfs(res,new char[n][n],new boolean[n][n],usedToPutQ,n,n,i,j,n*n);
                }
            }
        }
        return res;
    }
    private void dfs(List<List<String>> res,char[][] allChar, boolean[][] canAttack,boolean[][] usedToPutQ,int index,int size,int i_cur,int j_cur,int sum){
        if(index == 0){ // 已放置完所有皇后
            ArrayList<String> tempList = new ArrayList<String>();
            for(int i = 0;i < size;i++){
                String tempS= "";
                for(int j = 0;j < size;j++){
                    tempS += allChar[i][j]=='Q'? 'Q':'.';
                }
                tempList.add(tempS);
            }
            if(!res.contains(tempList)) res.add(tempList);
            return;
        }
        // 放过皇后/可被攻击到/越界/全部位置搜索完毕没有发现:不进行后续搜索
        if(i_cur<0 || i_cur>=size || j_cur<0 || j_cur>=size || usedToPutQ[i_cur][j_cur] || canAttack[i_cur][j_cur] || sum == 0 ){
            return;
        }
        // 可被攻击到:搜索理论上的不可攻击范围
        else{
            // 不可被攻击到,可放置皇后:搜索附近区域
            allChar[i_cur][j_cur] = 'Q';
            usedToPutQ[i_cur][j_cur] = true;
            int temp_sum = sum;
            boolean[][] temp_canAttack = canAttack;
            index--;
            // 更新可攻击范围:所在行、列、正反斜线
            for(int i = 0;i < size;i++){
                for(int j = 0;j < size;j++){
                    // i行、j列和两条所在斜线全部置为true
                    if(!canAttack[i][j] && (i == i_cur || j == j_cur || Math.abs(i-i_cur) == Math.abs(j - j_cur))){
                        sum--;
                        canAttack[i][j] = true;
                    }
                }
            }
            // 搜索搜索理论上的不可攻击范围
            for(int i = 0;i <= 7;i++){
                dfs(res,allChar,canAttack,usedToPutQ,index,size,i_cur+dirs[i],j_cur+dirs[i+1],sum);
            }
            // 恢复状态
            sum = temp_sum;
            allChar[i_cur][j_cur] = '.';
            canAttack = temp_canAttack;
            index++;
        }
    }
}
  • 优秀思路:使用一个数组记录每行放置的皇后的列下标,依次在每一行放置一个皇后。每次新放置的皇后都不能和已经放置的皇后之间有攻击:即新放置的皇后不能和任何一个已经放置的皇后在同一列以及同一条斜线上,并更新数组中的当前行的皇后列下标。当 NN 个皇后都放置完毕,则找到一个可能的解。
class Solution {
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> solutions = new ArrayList<List<String>>();
        int[] queens = new int[n]; // 存储皇后的位置
        Arrays.fill(queens, -1);
        Set<Integer> columns = new HashSet<Integer>();
        Set<Integer> diagonals1 = new HashSet<Integer>();
        Set<Integer> diagonals2 = new HashSet<Integer>();
        backtrack(solutions, queens, n, 0, columns, diagonals1, diagonals2);
        return solutions;
    }

    public void backtrack(List<List<String>> solutions, int[] queens, int n, int row, Set<Integer> columns, Set<Integer> diagonals1, Set<Integer> diagonals2) {
        if (row == n) {
            List<String> board = generateBoard(queens, n);
            solutions.add(board);
        } else {
            for (int i = 0; i < n; i++) {
                if (columns.contains(i)) {
                    continue;
                }
                int diagonal1 = row - i;
                if (diagonals1.contains(diagonal1)) {
                    continue;
                }
                int diagonal2 = row + i;
                if (diagonals2.contains(diagonal2)) {
                    continue;
                }
                queens[row] = i;
                columns.add(i);
                diagonals1.add(diagonal1);
                diagonals2.add(diagonal2);
                backtrack(solutions, queens, n, row + 1, columns, diagonals1, diagonals2);
                queens[row] = -1;
                columns.remove(i);
                diagonals1.remove(diagonal1);
                diagonals2.remove(diagonal2);
            }
        }
    }

    public List<String> generateBoard(int[] queens, int n) {
        List<String> board = new ArrayList<String>();
        for (int i = 0; i < n; i++) {
            char[] row = new char[n];
            Arrays.fill(row, '.');
            row[queens[i]] = 'Q';
            board.add(new String(row));
        }
        return board;
    }
}
  • 更优秀思路:基于位运算判断后一行可放置皇后的位置,如图所示。根据三个遍历的值相与即可判断后一行哪些位置可放置皇后,若没放完却无地可放时返回空,若放完则统计结果
    在这里插入图片描述
class Solution {
    public List<List<String>> solveNQueens(int n) {
        int[] queens = new int[n];
        Arrays.fill(queens, -1);
        List<List<String>> solutions = new ArrayList<List<String>>();
        solve(solutions, queens, n, 0, 0, 0, 0);
        return solutions;
    }

    public void solve(List<List<String>> solutions, int[] queens, int n, int row, int columns, int diagonals1, int diagonals2) {
        if (row == n) {
            List<String> board = generateBoard(queens, n);
            solutions.add(board);
        } else {
            int availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2));
            while (availablePositions != 0) {
                int position = availablePositions & (-availablePositions);
                availablePositions = availablePositions & (availablePositions - 1);
                int column = Integer.bitCount(position - 1);
                queens[row] = column;
                solve(solutions, queens, n, row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1);
                queens[row] = -1;
            }
        }
    }

    public List<String> generateBoard(int[] queens, int n) {
        List<String> board = new ArrayList<String>();
        for (int i = 0; i < n; i++) {
            char[] row = new char[n];
            Arrays.fill(row, '.');
            row[queens[i]] = 'Q';
            board.add(new String(row));
        }
        return board;
    }
}

10. 复原 IP 地址

  • 题目描述:给定一个只包含数字的字符串s,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 IP 地址 。你可以按任何顺序返回答案。有效 IP 地址 正好由四个整数(每个整数位于 0255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。例如:"0.1.2.201""192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.245""192.168.1.312""192.168@1.1" 是 无效 IP 地址。
  • 我的思路(79% - 64%):【递归+回溯】特殊情况分开处理:出现0(该位整数只加一个0)或者最后一个整数(不加’.’)
    • 边界条件:剩下的字符一定不够放或者放不下
    • 实现:利用StringBuufer进行字符串的增加和删除操作
class Solution {
    public List<String> restoreIpAddresses(String s) {
        List<String> res = new ArrayList<>();
        if(s == null || s.length() < 4 || s.length() > 12) return res;
        dfs(res,s,new StringBuffer(),0);
        return res;
    }
    private void dfs(List<String> res,String restS,StringBuffer tempS,int index){
        int len = restS.length();
        if(index == 4){
            if(len == 0) res.add(tempS.toString());// 刚好放完
            return;// 提前放完字符
        }else{
            if(len < 4-index || len/(4-index) > 3) return;// 剩下字符一定不够放/放不下
        }

        // 寻找当前整数
        if(restS.charAt(0)=='0'){ // 若为0只能添加1个0
            tempS.append(restS.charAt(0));
            if(index != 3) tempS.append('.');
            dfs(res,restS.substring(1),tempS,index+1);
            // 恢复
            if(index != 3) tempS.delete(tempS.length()-2, tempS.length());
            else tempS.delete(tempS.length()-1, tempS.length());
        }else{
            for(int i = 1;i <= Math.min(3,restS.length());i++){ // 注意:为了应对最后一个整数时,字符串可能只有1位/2位的情况,要写作i <= Math.min(3,restS.length())
                String temp = restS.substring(0,i);
                if(Integer.parseInt(temp) >= 0 && Integer.parseInt(temp) <= 255){
                    tempS.append(temp);
                    if(index != 3) tempS.append('.');
                    dfs(res,restS.substring(i),tempS,index+1);
                    // 恢复
                    if(index != 3) tempS.delete(tempS.length()-i-1, tempS.length()); // 删除最后 i+1 位
                    else tempS.delete(tempS.length()-i, tempS.length()); // 删除最后 i 位
                }
            }
        }
    }
}
  • 我的思路——另一分代码:起点式,不裁剪字符串
class Solution {
    public List<String> restoreIpAddresses(String s) {
        List<String> res = new ArrayList<>();
        if(s == null || s.length() < 4 || s.length() > 12) return res;
        dfs(res,s,new StringBuffer(),0,0,s.length());
        return res;
    }
    private void dfs(List<String> res,String S,StringBuffer tempS,int index,int start,int len){
        if(index == 4){
            if(start == len) res.add(tempS.toString());// 刚好放完
            return;
        }else{
            if(len-start < 4-index || (len-start)/(4-index) > 3) return;// 剩下字符一定不够放/或放不下
        }

        // 寻找当前整数
        if(S.charAt(start)=='0'){ // 若为0只能添加1个0
            tempS.append('0');
            if(index != 3) tempS.append('.');
            dfs(res,S,tempS,index+1,start+1,len);
            // 恢复
            if(index != 3) tempS.delete(tempS.length()-2, tempS.length());
            else tempS.delete(tempS.length()-1, tempS.length());
        }else{
            for(int i = 1;i <= Math.min(3,len-start);i++){
                String temp = S.substring(start,start+i);
                if(Integer.parseInt(temp) >= 0 && Integer.parseInt(temp) <= 255){
                    tempS.append(temp);
                    if(index != 3) tempS.append('.');
                    dfs(res,S,tempS,index+1,start+i,len);
                    // 恢复tempS
                    if(index != 3) tempS.delete(tempS.length()-i-1, tempS.length()); // 删除最后 i+1 位
                    else tempS.delete(tempS.length()-i, tempS.length()); // 删除最后 i 位
                }
            }
        }
    }
}
  • 优秀思路:差不多,主要优化的地方在于
    • 1.遇到”255...“的情况,怎么取出2/25/255最快,addr = addr * 10 + new_addr;
    • 2.建立一个长度为4数组存储各个整数,在需要添加结果的时候统一转换一次即可。而不需要像我上面那样需要一直转换
class Solution {
    static final int SEG_COUNT = 4;
    List<String> ans = new ArrayList<String>();
    int[] segments = new int[SEG_COUNT];

    public List<String> restoreIpAddresses(String s) {
        segments = new int[SEG_COUNT];
        dfs(s, 0, 0);
        return ans;
    }

    public void dfs(String s, int segId, int segStart) {
        // 如果找到了 4 段 IP 地址并且遍历完了字符串,那么就是一种答案
        if (segId == SEG_COUNT) {
            if (segStart == s.length()) {
                StringBuffer ipAddr = new StringBuffer();
                for (int i = 0; i < SEG_COUNT; ++i) {
                    ipAddr.append(segments[i]);
                    if (i != SEG_COUNT - 1) {
                        ipAddr.append('.');
                    }
                }
                ans.add(ipAddr.toString());
            }
            return;
        }

        // 如果还没有找到 4 段 IP 地址就已经遍历完了字符串,那么提前回溯
        if (segStart == s.length()) {
            return;
        }

        // 由于不能有前导零,如果当前数字为 0,那么这一段 IP 地址只能为 0
        if (s.charAt(segStart) == '0') {
            segments[segId] = 0;
            dfs(s, segId + 1, segStart + 1);
        }

        // 一般情况,枚举每一种可能性并递归
        int addr = 0;
        for (int segEnd = segStart; segEnd < s.length(); ++segEnd) {
            addr = addr * 10 + (s.charAt(segEnd) - '0');
            if (addr > 0 && addr <= 0xFF) {
                segments[segId] = addr;
                dfs(s, segId + 1, segEnd + 1);
            } else {
                break;
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值