Bootstrap

算法学习day09(N皇后/解数独)

一、复原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;
    }
}

;