LeetCode 回溯篇(46、77、78、51)

46. 全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

solution1

class Solution {
    List<List<Integer>> res = new LinkedList<>();
    public List<List<Integer>> permute(int[] nums) {
        LinkedList<Integer> track = new LinkedList<>();
        backtrack(nums,track);
        return res;
    }
    public void backtrack(int[] nums, LinkedList<Integer> track){
        //满足结束条件
        if (track.size() == nums.length){
            res.add(new LinkedList(track));
            return;
        }
        for (int i = 0; i < nums.length; i++){
            if(track.contains(nums[i])) continue;
            // 路径选择
            track.add(nums[i]);
            backtrack(nums,track);
            // 撤销选择
            track.removeLast();
        }
    }
}
//回溯模版 遍历一个回溯树
//result = []
// def backtrack(路径, 选择列表):
//     if 满足结束条件:
//         result.add(路径)
//         return

//     for 选择 in 选择列表:
//         做选择
//         backtrack(路径, 选择列表)
//         撤销选择

77. 组合

给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。

示例:

输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

solution1

class Solution {
    private List<List<Integer>> res = new LinkedList<>(); 
    public List<List<Integer>> combine(int n, int k) {
        if (k <= 0 || n <= 0) return res;
        backtrack(new LinkedList<>(),1, n, k);
        return res;
    }
    public void backtrack(List<Integer> list, int l, int n, int k){
        if (list.size() == k){
            res.add(new LinkedList<Integer>(list));
        }
        for (int i = l; i <= n; i++){
            list.add(i);
            backtrack(list,i+1,n,k); //i+1,不是l+1,不然会重复出现[2,2]的情况
            list.remove(list.size()-1);
        }
    }
}

78. 子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]

思路

//递归与回溯(试错)
//思想:将大问题分解为小问题/分层 代码:先终止再 大问题分成小问题/当前层和下一层/不断试错
//1回溯 类似于组括号 nums有多少个就有多少层,每一层生成要加入还是不加两个情况
//2回溯 遍历nums,加入该数后进入该数下一位的挑选,到挑选最后一位跳出递归
//3迭代 n的子集=(n-1的子集)加上(n-1的子集和n的并集),代码由空和1开始不断迭代

solution 1 回溯1

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        ArrayList ans =  new ArrayList();
        if(nums == null) return ans;
        dfs(ans,nums,new ArrayList<Integer>(),0);
        return ans;
    }
    public void dfs(List<List<Integer>> ans,int[] nums,List<Integer> l,int index){
        //终止条件
        if (index == nums.length) {
            ans.add(new ArrayList(l));// 引用的加入
            return;
        }
        //大问题分成小问题/当前层和下一层/不断试错
        dfs(ans,nums,l,index+1);
        l.add(nums[index]);
        dfs(ans,nums,l,index+1);
        //清理当前层 (递归返回给上一层时必须保证list不变)
        l.remove(l.size()-1);
    }
}

solution2 回溯2

// 套用回溯模版
class Solution {
    private List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        List<Integer> list = new LinkedList<>();
        if (nums == null || nums.length == 0) return res;
        backtrack(list,nums,0);
        return res;
    }
    public void backtrack(List<Integer> list, int[] nums,int level){
        res.add(new LinkedList<Integer>(list));
        for(int i = level; i < nums.length; i++){
            list.add(nums[i]);
            backtrack(list,nums,i+1);//必须为i+1,i++先赋值再自增
            list.remove(list.size()-1);
        }
    }
}

solution3 迭代

//时间复杂度为(N*(2^N))生成子集并复制到输出
class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> output = new ArrayList();
        output.add(new ArrayList<Integer>());
        for (int num:nums){
            List<List<Integer>> newSubsets = new ArrayList();
            for (List<Integer> curr:output) {
                newSubsets.add(new ArrayList<Integer>(curr){{add(num);}}); //新建列表的同时加入对象
            }
            for (List<Integer> curr:newSubsets){
                output.add(curr);
            }
        }
        return output;
    }
}

51. N皇后

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

上图为 8 皇后问题的一种解法。

给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。

每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

示例:

输入: 4
输出: [
[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],

["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]
]
解释: 4 皇后问题存在两个不同的解法。

提示:

皇后,是国际象棋中的棋子,意味着国王的妻子。皇后只做一件事,那就是“吃子”。当她遇见可以吃的棋子时,就迅速冲上去吃掉棋子。当然,她横、竖、斜都可走一到七步,可进可退。(引用自 百度百科 - 皇后 )

思路

//回溯1 先填满'.', 递归放q时,计算该点的对角线是否存在q,存在即回溯
//回溯1 对列、主次对角线的点保存起来,主对角线x+y相等,次对角线x-y相等
//hill 山丘 上升 主对角线
//dale 山谷 下降 次对角线
//List<String> curr = new ArrayList<>(Collections.nCopies(n,"...."));新建填满数的list<String>
//Arrays.fill(charArray, '.');新建填满数的char[]

solution1

class Solution {
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> res = new ArrayList<>();
        char[][] curr = new char[n][n];
        for (int i =0;i<n;i++){
            for(int j = 0;j<n;j++){
                curr[i][j] = '.';
            }
        }
        helper(res,curr,0,n);
        return res;
    }
    public void helper(List<List<String>> res,char[][] curr,int col,int n){
        if (col == n){
            res.add(construt(curr));
            return;
        }
        for(int row=0; row < n; row++){
            if (isValid(curr,row,col,n)){
                curr[row][col] = 'Q';
                helper(res,curr,col+1,n);
                curr[row][col] = '.';
            }
        }
    }
    public boolean isValid(char[][] curr,int row,int col,int n){
        for (int i = 0;i<=col;i ++) {
            if (curr[row][i] == 'Q') return false;
            if (row-i>=0 && curr[row-i][col-i] == 'Q') return false;
            if (row+i<n && curr[row+i][col-i] == 'Q') return false;
        }
        // for (int i = 0; i<n; i++){
        //     for(int j = 0;j < col; j++){
        //         if (curr[i][j] == 'Q' && (row+col == i+j || row-col == i-j || row==i))
        //          {return false;}
        //         }
        // }
        return true;
        
    }
    public List<String> construt(char[][] curr){
        List<String> list = new ArrayList<>();
        for (int i=0;i<curr.length;i++){
            String s = new String(curr[i]);
            list.add(s);
        }
        return list;  
    }
}

solution2 回溯

class Solution {
    private Set<Integer> col = new HashSet<>();
    private Set<Integer> hill = new HashSet<>();
    private Set<Integer> dale = new HashSet<>();

    public List<List<String>> solveNQueens(int n) {
        List<List<String>> res = new ArrayList<>();
        
        helper(res,new ArrayList<String>(),0,n);
        return res;
    }
    public void helper(List<List<String>> res,List<String> curr,int y,int n){
        if (y == n){
            res.add(new ArrayList<>(curr));
            return;
        }
        for (int x = 0;x<n;x++){
            if (col.contains(x) || hill.contains(y+x) || dale.contains(x-y)) continue;
            //确定行char转string
            char[] charArray = new char[n];
            Arrays.fill(charArray, '.');
            charArray[x] = 'Q';
            String str = new String(charArray);
            curr.add(str);
            //攻击距离
            col.add(x);
            hill.add(x+y);
            dale.add(x-y);
            //下一层
            helper(res,curr,y+1,n);
            //清理当前层(回溯)
            curr.remove(curr.size()-1);
            col.remove(x);
            hill.remove(x+y);
            dale.remove(x-y);   
        }
    }
}

37. 解数独

编写一个程序,通过已填充的空格来解决数独问题。

一个数独的解法需遵循如下规则:

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
空白格用 '.' 表示。

Note:

给定的数独序列只包含数字 1-9 和字符 '.' 。
你可以假设给定的数独只有唯一解。
给定数独永远是 9x9 形式的。

solution1

class Solution {
    public void solveSudoku(char[][] board) {
        backtrack(board,0,0);
    }
    public boolean backtrack(char[][] board, int i, int j){
        int m = 9, n = 9;
        //换行
        if (j == m){
            return backtrack(board,i+1,0);
        }
        //base case
        if (i == n){
            return true;
        }
        // 已填数字
        if (board[i][j] != '.'){
            return backtrack(board,i,j+1);
        }
        for (char ch = '1'; ch <= '9'; ch ++){
            // 不合法数字跳过
            if(!isValid(board,i,j,ch)){
                continue;
            }
            board[i][j] = ch;
            if(backtrack(board,i,j+1)){
                return true;
            }
            board[i][j] = '.';
        }
        return false;
    }
    public boolean isValid(char[][] board, int i,int j, char ch){
        for (int p = 0;p < 9;p++){
            if (board[p][j] == ch){
                return false;
            }
            if (board[i][p] == ch){
                return false;
            }
            if (board[(i/3)*3 + p/3][(j/3)*3 + p%3] == ch){
                return false;
            }
        }
        return true;
    }
}
posted @ 2020-08-30 20:23  gg12138  阅读(181)  评论(0编辑  收藏  举报