Bootstrap

【代码随想录】第七章-回溯算法


第七章-回溯算法

1 组合

77.组合

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
输入:n = 4, k = 2 输出:[[2,4] , [3,4], [2,3], [1,2], [1,3], [1,4],]

思路:
正常回溯,注意形参是否为引用,如果引用状态则需要手动进行增加删除。

class Solution {
    List<List<Integer>> list=new ArrayList<>();
    int[] visited;
    public List<List<Integer>> combine(int n, int k) {
        List<Integer> level=new ArrayList<>();
        visited=new int[n+1];
        dfs(1,n,level,k);
        return list;
    }
    public void dfs(int start,int n, List<Integer>level,int k){
        if(level.size()==k){
            list.add(new ArrayList(level));
            return;
        }
        for(int i=start;i<=n;i++){
            if(visited[i]==0){
                visited[i]=1;
                level.add(i);
                dfs(i,n,level,k);
                visited[i]=0;
                level.remove(level.size()-1);
            }
        }
    }
}

39.组合总和

给你一个无重复元素的整数数组candidates和一个目标整数target,找出candidates中可以使数字和为目标数target的所有不同组合,并以列表形式返回。你可以按任意顺序返回这些组合。candidates中的同一个数字可以无限制重复被选取。如果至少一个数字的被选数量不同,则两种组合是不同的。对于给定的输入,保证和为target的不同组合数少于150个。
输入:candidates = [2,3,6,7], target = 7 输出:[[2,2,3],[7]]

思路:
注意快速剪枝

class Solution {
    List<List<Integer>> list = new ArrayList<>();
    List<Integer> level = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        // Arrays.sort(candidates);
        int sum = 0;
        dfs(candidates, sum, target, 0);
        return list;
    }
    public void dfs(int[] candidates, int sum, int target, int index) {
        if (sum > target)   return;
        if (sum == target)  list.add(new ArrayList(level));
        for (int i = index; i < candidates.length; i++) {
            level.add(candidates[i]);
            dfs(candidates, sum + candidates[i], target, i);
            level.remove(level.size() - 1);
        }
    }
}

40.组合总和II

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。
输入:root = [3,9,20,null,null,15,7] 输出:[[3],[9,20],[15,7]]

思路:

Method1:使用标志数组去重遍历

先对candidates进行排序,然后用index来指名重复使用,其他就是正常回溯。与39不同的地方是,这里只能使用一次,那就需要用访问标志数组来表示有没有访问过。i>0&&candidates[i]==candidates[i-1]&&visited[i-1]==0,为什么visited[i-1]==0,因为如果前面有个元素和他一致,前面先访问,然后走不通回溯之后,前面的元素的访问标志就变为0了。这就是和前一个能区别开的原因。

class Solution {
    List<List<Integer>> list = new ArrayList<>();
    int[] visited;
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        int len = candidates.length;
        visited = new int[len + 1];
        List<Integer> level = new ArrayList<>();
        int sum = 0;
        dfs(sum, candidates, target, level, 0);
        return list;
    }
    public void dfs(int sum, int[] candidates, int target, List<Integer> level, int index) {
        if (sum > target) return;
        if (sum == target) {
            list.add(new ArrayList(level));
            return;
        }
        for (int i = index; i < candidates.length; i++) {
            if (i > 0 && candidates[i] == candidates[i - 1] 
                    && visited[i - 1] == 0) {
                continue;
            }
                visited[i] = 1;
                level.add(candidates[i]);
                dfs(sum + candidates[i], candidates, target, level, i+1);
                level.remove(level.size() - 1);
                visited[i] = 0;
        }
    }
}

Method2:使用HashSet去重遍历

class Solution {
    List<List<Integer>> list = new ArrayList<>();
    List<Integer> level = new ArrayList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        int len = candidates.length;
        dfs(0, candidates, target, 0);
        return list;
    }
    public void dfs(int sum, int[] candidates, int target, int index) {
        if (sum > target)   return;
        if (sum == target) {
            list.add(new ArrayList(level));
            return;
        }
        HashSet<Integer> used = new HashSet<>();
        for (int i = index; i < candidates.length; i++) {
            if (used.contains(candidates[i]))   continue;
            used.add(candidates[i]);
            level.add(candidates[i]);
            dfs(sum + candidates[i], candidates, target,i+1);
            level.remove(level.size() - 1);
        }
    }
}

216.组合总和III

找出所有相加之和为n的k个数的组合。组合中只允许含有1-9的正整数,并且每种组合中不存在重复的数字。
输入: k = 3, n = 7 输出: [[1,2,4]]

思路:
正常递归遍历,注意入列条件有两个。

class Solution {
    List<List<Integer>> list=new ArrayList<>();
    int []visited;
    public List<List<Integer>> combinationSum3(int k, int n) {
        List<Integer> level=new ArrayList<>();
        visited=new int[10];
        int sum=0;
        dfs(n,sum,level,1,k);
        return list;
    }
    public void dfs(int n,int sum,List<Integer> level,int index,int k){
        if(sum>n)   return;
        if(sum==n&&level.size()==k){
            list.add(new ArrayList(level));
            return;
        }
        for(int i=index;i<=9;i++){
            if(visited[i]==0){
                visited[i]=1;
                level.add(i);
                dfs(n,sum+i,level,i,k);
                level.remove(level.size()-1);
                visited[i]=0;
            }
        }
    }
}

17.电话号码的字母组合

给定一个仅包含数字2-9的字符串,返回所有它能表示的字母组合。给出数字到字母的映射如下(与电话按键相同)。注意1不对应任何字母。
输入:“23” 输出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].

思路:
针对digits的每个数字进行深搜,用index标记访问到哪个,然后递归遍历搜索,在搜索完后把当前删掉就能进行下一次搜索。比如那“23”来举例,先搜索2对应的s即abc,用char c遍历abc,在a的层级时,c当前是a,进行深度遍历,当前index在0,也就是在数字2的位置,然后通过递归index+1遍历到3,3对应的str是def,d加入level,也就是说level是ad,然后level的长度跟“23”的长度一样了,退出循环,接下来把d删除,再遍历d后面的e,f,一次类推,level变为ae,退出遍历的时候把e删除…

class Solution {
    HashMap<Integer, String> map = new HashMap<>();
    List<String> list=new ArrayList<>();
    public List<String> letterCombinations(String digits) {
        map.put(2,"abc");
        map.put(3,"def");
        map.put(4,"ghi");
        map.put(5,"jkl");
        map.put(6,"mno");
        map.put(7,"pqrs");
        map.put(8,"tuv");
        map.put(9,"wxyz");
        List<Character> level=new ArrayList<>();
        char[] nums=digits.toCharArray();
        int len=nums.length;
        if(len==0)  return list;
        dfs(nums,len,level,0);
        return list;
    }
    public void dfs(char[] nums,int len,List<Character> level,int index){
        if(level.size()==len){
            StringBuilder sb=new StringBuilder();
            for(Character c:level)  sb.append(c);
            list.add(sb.toString());
            return;
        }
        for(int i=index;i<len;i++){
            String s=map.get(nums[i]-'0');
            for(char c:s.toCharArray()){
                level.add(c);
                dfs(nums,len,level,i+1);
                level.remove(level.size()-1);
            }
        }
    }
}

2 分割回文串

131.分割回文串

给你一个字符串s,请你将s分割成一些子串,使每个子串都是回文串。返回s所有可能的分割方案。
输入:s = “aab” 输出:[[“a”,“a”,“b”],[“aa”,“b”]]

思路:
注意sb的创建,如果当前是回文串,为了一一检查,在当前的字符串上依旧用index+1创建新的字符串,只有在回溯退出时,才会往sb中添加新的字符串。
退出条件时当index遍历到最后一个字符的时候将当前层的level退出。

class Solution {
    List<List<String>> list=new ArrayList<>();
    public List<List<String>> partition(String s) {
        List<String> level =new ArrayList<>();
        dfs(s,level,0,new StringBuffer());
        return list;
    }
    public boolean isHuiWen(String s){
        int len=s.length();
        int low=0;int high=len-1;
        while(low<high){
            if(s.charAt(low)==s.charAt(high)){
                low++;
                high--;
            }
            else	return false;
        }
        return true;
    }
    public void dfs(String s,List<String> level,int index,StringBuffer sb){
        if(index==s.length()){
            list.add(new ArrayList(level));
            return;
        }
        for(int i=index;i<s.length();i++){
            sb.append(s.charAt(i));
            if(isHuiWen(sb.toString())){
                level.add(sb.toString());
                dfs(s,level,i+1,new StringBuffer());
                level.remove(level.size()-1);
            }
        }
    }
}

93.复原IP地址

有效IP地址正好由四个整数(每个整数位于0到255之间组成,且不能含有前导0),整数之间用’.‘分隔。给定一个只包含数字的字符串s,用以表示一个IP地址,返回所有可能的有效IP地址,这些地址可以通过在s中插入’.'来形成。你不能重新排序或删除s中的任何数字。你可以按任何顺序返回答案。
输入:s = “25525511135” 输出:[“255.255.11.135”,“255.255.111.35”]

思路:
两点需要注意,一、退出条件,当句号为三个的时候检查最后一组数字,如果符合就加入list,否则返回,二、用StringBuffer的insert加入句号后,i的增值应该越过句号,为i+2。

class Solution {
    List<String> list=new ArrayList<>();
    public List<String> restoreIpAddresses(String s) {
        if(s.length()>12) return list;
        StringBuffer sb=new StringBuffer(s);
        dfs(sb,0,0);
        return list;
    }
    public void dfs(StringBuffer sb,int index,int count){
        if(count==3){
            if(isVaild(sb,index,sb.length()-1)){
                list.add(sb.toString());
            }
            return;
        }
        for(int i=index;i<sb.length();i++){
            if(isVaild(sb,index,i)){
                sb.insert(i+1,".");
                dfs(sb,i+2,count+1);
                sb.deleteCharAt(i+1);
            }
            else    break;
        }
    }
    public boolean isVaild(StringBuffer s,int low,int high){
        if(low>high)    return false;
        if(s.charAt(low)=='0'&&low!=high)   return false;
        String sub=s.substring(low,high+1);
        if(Integer.parseInt(sub)>255)   return false;
        return true;
    }
}

78.子集

给你一个整数数组nums,数组中的元素互不相同。返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。你可以按任意顺序返回解集。
输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
思路:
正常递归

class Solution {
    List<List<Integer>> list=new ArrayList<>();
    List<Integer> level=new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        if(nums.length==0)  return list;
        dfs(nums,0);
        return list;
    }
    public void dfs(int[] nums,int index){
        list.add(new ArrayList(level));
        if(index>=nums.length)  return;
        for(int i=index;i<nums.length;i++){
            level.add(nums[i]);
            dfs(nums,i+1);
            level.remove(level.size()-1);
        }
    }
}

90.子集II

给你一个整数数组nums,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。返回的解集中,子集可以按任意顺序排列。
输入:nums = [1,2,2] 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

思路:

Method1:使用标志数组去重遍历

如果不要重复的,检查前一个的标志数组,如果和前一个的值一样并且为访问就跳过。i>0&&nums[i]== nums [i-1]&&visited[i-1]==0,为什么visited[i-1]==0,因为如果前面有个元素和他一致,前面先访问,然后走不通回溯之后,前面的元素的访问标志就变为0了。这就是和前一个能区别开的原因。

class Solution {
    List<List<Integer>> list = new ArrayList<>();
    List<Integer> level = new ArrayList<>();
    int[] visited;
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        visited = new int[nums.length + 1];
        dfs(nums, 0);
        return list;
    }
    public void dfs(int[] nums, int index) {
        list.add(new ArrayList(level));
        if(index>=nums.length)  return;
        for (int i = index; i < nums.length; i++) {
            if (i > 0 && nums[i-1] == nums[i] && visited[i-1] == 0) {
                continue;
            }
            visited[i]=1;
            level.add(nums[i]);
            dfs(nums, i + 1);
            level.remove(level.size() - 1);
            visited[i]=0;
        }
    }
}

Method2:使用Hashset去重

class Solution {
    List<List<Integer>> list = new ArrayList<>();
    List<Integer> level = new ArrayList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        dfs(nums, 0);
        return list;
    }
    public void dfs(int[] nums, int index) {
        list.add(new ArrayList(level));
        if(index>=nums.length)  return;
        HashSet<Integer> used = new HashSet<>();
        for (int i = index; i < nums.length; i++) {
            if (used.contains(nums[i])) continue;
            used.add(nums[i]);
            level.add(nums[i]);
            dfs(nums, i + 1);
            level.remove(level.size() - 1);
        }
    }
}

491.非递减子序列

给你一个整数数组nums,找出并返回所有该数组中不同的递增子序列,递增子序列中至少有两个元素。你可以按任意顺序返回答案。
输入:nums = [4,6,7,7] 输出:[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]

思路:
使用hashset去重。

class Solution {
    List<List<Integer>> list = new ArrayList<>();
    List<Integer> level = new ArrayList<>();
    public List<List<Integer>> findSubsequences(int[] nums) {
        if (nums.length == 0)   return list;
        dfs(nums, 0);
        return list;
    }
    public void dfs(int[] nums, int index) {
        if (level.size() >= 2)  list.add(new ArrayList(level));
        if (index >= nums.length)   return;
        HashSet<Integer> used = new HashSet<>();
        for (int i = index; i < nums.length; i++) {
            if ((!level.isEmpty() && nums[i] < level.get(level.size() - 1))
                    || used.contains(nums[i])) {
                continue;
            }
            used.add(nums[i]);
            level.add(nums[i]);
            dfs(nums, i + 1);
            level.remove(level.size() - 1);
        }
    }
}

3 全排列

46.全排列

给定一个不含重复数字的数组nums,返回其所有可能的全排列。你可以按任意顺序返回答案。
输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

思路:
回溯遍历,注意全排列,需要使用visisted来针对,所以需要每次从0开始

class Solution {
    List<List<Integer>> list = new ArrayList<>();
    List<Integer> level = new ArrayList<>();
    int[] visited;
    public List<List<Integer>> permute(int[] nums) {
        visited = new int[nums.length];
        dfs(nums);
        return list;
    }
    public void dfs(int[] nums) {
        if (level.size()==nums.length)		list.add(new ArrayList(level));
        for (int i = 0; i < nums.length; i++) {
            if (visited[i] == 0) {
                visited[i] = 1;
                level.add(nums[i]);
                dfs(nums);
                level.remove(level.size() - 1);
                visited[i] = 0;
            }
        }
    }
}

47.全排列II

给定一个可包含重复数字的序列nums,按任意顺序返回所有不重复的全排列。
输入:nums = [1,1,2] 输出:[[1,1,2],[1,2,1],[2,1,1]]

思路:
回溯遍历,注意这个会回头,所以需要每次从0开始

class Solution {
    List<List<Integer>> list = new ArrayList<>();
    List<Integer> level = new ArrayList<>();
    int[] visited;
    public List<List<Integer>> permuteUnique(int[] nums) {
        Arrays.sort(nums);
        visited = new int[nums.length];
        dfs(nums);
        return list;
    }
    public void dfs(int[] nums) {
        if (level.size() == nums.length) {
            list.add(new ArrayList(level));
        }
        for (int i = 0; i < nums.length; i++) {
            if (i > 0 && nums[i - 1] == nums[i] 
            && visited[i - 1] == 0) {
                continue;
            }
            if (visited[i] == 0) {
                visited[i] = 1;
                level.add(nums[i]);
                dfs(nums);
                level.remove(level.size() - 1);
                visited[i] = 0;
            }
        }
    }
}

4 N皇后

51.N皇后

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n皇后问题研究的是如何将n个皇后放置在n×n的棋盘上,并且使皇后彼此之间不能相互攻击。给你一个整数n,返回所有不同的n皇后问题的解决方案。每一种解法包含一个不同的n皇后问题的棋子放置方案,该方案中’Q’和’.'分别代表了皇后和空位。
输入:n = 4
输出:[[“.Q…”,“…Q”,“Q…”,“…Q.”],[“…Q.”,“Q…”,“…Q”,“.Q…”]]

思路:
注意维护行、列、上对角线、下对角线四个set去重即可

class Solution {
    List<List<String>>list=new ArrayList<>();
    HashSet<Integer> rowSet=new HashSet<>();
    HashSet<Integer> colSet=new HashSet<>();
    HashSet<Integer> shangDuiJiaoSet=new HashSet<>();
    HashSet<Integer> xiaDuiJiaoSet=new HashSet<>();
    public List<List<String>> solveNQueens(int n) {
        char [][] arr=new char[n][n];
        for(int i=0;i<n;i++){
            Arrays.fill(arr[i],'.');
        }
        dfs(arr,0,n);
        return list;
    }
    public void dfs(char [][] arr,int index,int n){
        if(index==n){
            List<String> level=new ArrayList<>();
            for(int i=0;i<n;i++){
                level.add(new String(arr[i]));
            }
            list.add(new ArrayList(level));
            return;
        }
        for(int j=0;j<n;j++){
            if(rowSet.contains(index)||colSet.contains(j)
   ||shangDuiJiaoSet.contains(index-j)||xiaDuiJiaoSet.contains(index+j)){
                continue;
            }
            arr[index][j]='Q';
            rowSet.add(index);
            colSet.add(j);
            shangDuiJiaoSet.add(index-j);
            xiaDuiJiaoSet.add(index+j);
            dfs(arr,index+1,n);
            arr[index][j]='.';
            rowSet.remove(index);
            colSet.remove(j);
            shangDuiJiaoSet.remove(index-j);
            xiaDuiJiaoSet.remove(index+j);
        }
    }
}

52.N皇后II

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n皇后问题研究的是如何将n个皇后放置在n×n的棋盘上,并且使皇后彼此之间不能相互攻击。给你一个整数n,返回所有不同的n皇后问题的解决方案。每一种解法包含一个不同的n皇后问题的棋子放置方案,该方案中’Q’和’.'分别代表了皇后和空位。
输入:n = 4
输出:2

思路:
把51题的list填充变为全局count++即可。


36.有效的数独

请你判断一个9x9的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
数字1-9在每一行只能出现一次;数字1-9在每一列只能出现一次;数字1-9在每一个以粗实线分隔的3x3宫内只能出现一次。
输入:board =
[[“5”,“3”,“.”,“.”,“7”,“.”,“.”,“.”,“.”]
,[“6”,“.”,“.”,“1”,“9”,“5”,“.”,“.”,“.”]
,[“.”,“9”,“8”,“.”,“.”,“.”,“.”,“6”,“.”]
,[“8”,“.”,“.”,“.”,“6”,“.”,“.”,“.”,“3”]
,[“4”,“.”,“.”,“8”,“.”,“3”,“.”,“.”,“1”]
,[“7”,“.”,“.”,“.”,“2”,“.”,“.”,“.”,“6”]
,[“.”,“6”,“.”,“.”,“.”,“.”,“2”,“8”,“.”]
,[“.”,“.”,“.”,“4”,“1”,“9”,“.”,“.”,“5”]
,[“.”,“.”,“.”,“.”,“8”,“.”,“.”,“7”,“9”]]
输出:true

思路:
横竖比较简单,困难的是33怎么进行区分,这个很考验数学,推导公式是第几个33矩阵是由(i/3)*3+(j/3)得来的。

class Solution {
    HashSet<Character>[] rowSet = new HashSet[9];
    HashSet<Character>[] columnSet = new HashSet[9];
    HashSet<Character>[] threeSet = new HashSet[9];
    public boolean isValidSudoku(char[][] board) {
        for(int i=0;i<9;i++){
            rowSet[i] = new HashSet<>();
            columnSet[i] = new HashSet<>();
            threeSet[i] = new HashSet<>();
        }
        for(int i=0;i<9;i++){
            for(int j=0;j<9;j++){
                if(board[i][j]=='.') continue;
                char c=board[i][j];
                if(rowSet[i].contains(c)) return false;
                if(columnSet[j].contains(c)) return false;
                if(threeSet[(i / 3) * 3 + (j / 3)].contains(c)) return false;
                rowSet[i].add(c);
                columnSet[j].add(c);
                threeSet[(i / 3) * 3 + (j / 3)].add(c);
            }
        }  
        return true;   
    }
}

Tip:大小矩阵公式推导

如果要8×8的大矩阵划分为2×4×的小矩阵,我们可以使用以下公式来确定元素属于第几个小矩阵:

x = ⌊ i k ⌋ × ( N k ) + ⌊ i k ⌋   x=\left\lfloor \frac{i}{k} \right\rfloor \times \left( \frac{N}{k} \right)+\left\lfloor \frac{i}{k} \right\rfloor\ x=ki×(kN)+ki 

i: 元素的行索引(从 0 开始)。
j: 元素的列索引(从 0 开始)。
k: 小矩阵的边长(例如 2 或 4)。
N: 大矩阵的边长(这里为 8)。


37.解数独

编写一个程序,通过填充空格来解决数独问题。只需要根据以下规则,验证已经填入的数字是否有效即可。数字1-9在每一行只能出现一次;数字1-9在每一列只能出现一次;数字1-9在每一个以粗实线分隔的3x3宫内只能出现一次。
输入:board =
[[“5”,“3”,“.”,“.”,“7”,“.”,“.”,“.”,“.”],
[“6”,“.”,“.”,“1”,“9”,“5”,“.”,“.”,“.”],
[“.”,“9”,“8”,“.”,“.”,“.”,“.”,“6”,“.”],
[“8”,“.”,“.”,“.”,“6”,“.”,“.”,“.”,“3”],
[“4”,“.”,“.”,“8”,“.”,“3”,“.”,“.”,“1”],
[“7”,“.”,“.”,“.”,“2”,“.”,“.”,“.”,“6”],
[“.”,“6”,“.”,“.”,“.”,“.”,“2”,“8”,“.”],
[“.”,“.”,“.”,“4”,“1”,“9”,“.”,“.”,“5”],
[“.”,“.”,“.”,“.”,“8”,“.”,“.”,“7”,“9”]]
输出:
[[“5”,“3”,“4”,“6”,“7”,“8”,“9”,“1”,“2”],
[“6”,“7”,“2”,“1”,“9”,“5”,“3”,“4”,“8”],
[“1”,“9”,“8”,“3”,“4”,“2”,“5”,“6”,“7”],
[“8”,“5”,“9”,“7”,“6”,“1”,“4”,“2”,“3”],
[“4”,“2”,“6”,“8”,“5”,“3”,“7”,“9”,“1”],
[“7”,“1”,“3”,“9”,“2”,“4”,“8”,“5”,“6”],
[“9”,“6”,“1”,“5”,“3”,“7”,“2”,“8”,“4”],
[“2”,“8”,“7”,“4”,“1”,“9”,“6”,“3”,“5”],
[“3”,“4”,“5”,“2”,“8”,“6”,“1”,“7”,“9”]]

思路:
对三个set回溯操作,遍历i和j,将1到9的数字全部试完。
先分别创建横竖和3*3,并且把已经存在的数字先放进去,然后开始深搜,i表示行,j表示列。如果i==9,说明整个数独都看完了,都符合,返回true即可;如果j==9,说明这一行没问题,就要进行下一行,从下一行的第一个开始,也就是(i+1, 0)这个位置。
如果移动的时候碰见已经填进去的数字自然跳过,只看空着的从1到9挨个尝试,看看横竖和3*3,也就是!rowSet[i].contains(c) && !columnSet[j].contains(c) && !threeSet[(i / 3) * 3 + (j / 3)].contains(c)
如果能放进去,就放进去,然后递归的尝试同一行的下一个元素,也就是(i, j+1),如果一直递归的下一个都是ok的那就也返回ok。如果哪一步不行返回false了,我们就要回溯该路径的所有,也就是退格

class Solution {
    HashSet<Character>[] rowSet = new HashSet[9];
    HashSet<Character>[] columnSet = new HashSet[9];
    HashSet<Character>[] threeSet = new HashSet[9];
    public void solveSudoku(char[][] board) {
        for(int i=0;i<9;i++){
            rowSet[i] = new HashSet<>();
            columnSet[i] = new HashSet<>();
            threeSet[i] = new HashSet<>();
        }
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (board[i][j] != '.') {
                    rowSet[i].add(board[i][j]);
                    columnSet[j].add(board[i][j]);
                    threeSet[(i / 3) * 3 + (j / 3)].add(board[i][j]);
                }
            }
        }
        dfs(board,0,0); 
    }
    public boolean dfs(char[][] board,int i,int j){
        if(i==9)    return true;
        if(j==9)    return dfs(board,i+1,0);
        if(board[i][j]!='.')    return dfs(board,i,j+1);
        for(int k=1;k<=9;k++){
            char c=(char)('0'+k);
            if(!rowSet[i].contains(c)&&!columnSet[j].contains(c)
            &&!threeSet[(i / 3) * 3 + (j / 3)].contains(c)){
                board[i][j]=c;
                rowSet[i].add(c);
                columnSet[j].add(c);
                threeSet[(i / 3) * 3 + (j / 3)].add(c);
                if(dfs(board,i,j+1))    return true;
                board[i][j]='.';
                rowSet[i].remove(c);
                columnSet[j].remove(c);
                threeSet[(i / 3) * 3 + (j / 3)].remove(c);
            }
        }
        return false;
    }
}

;