LeetCode刷题day30|332.重新安排行程、51. N皇后、37. 解数独

文章目录

  • 一、332.重新安排行程
  • 51. N皇后
  • 三、37. 解数独

一、332.重新安排行程

做这道题最大的收获就是如何通过数据结构(Map)存储地图上的信息,如何对信息进行搜索遍历。

如果求的结果有多个,一般递归函数的返回值为void,通常要遍历整个树;如果求的结果只有一个,即仅存在一个正确解,则递归函数的返回值一般为bool类型,当满足解的条件时返回true。

以下是代码部分:

public class 重新安排行程332 {

    //结果集
    Deque<String> res = new LinkedList<>();
    //map集合,放地图信息
    Map<String, Map<String, Integer>> map;

    public List<String> findItinerary(List<List<String>> tickets) {

        map = new HashMap<>();

        for (List<String> ticket : tickets) {

            Map<String, Integer> temp;

            //判断当前的起点在map中是否存在
            if(map.containsKey(ticket.get(0))){
                 temp = map.get(ticket.get(0));
                 //放入的值: 终点,以及次数
                 temp.put( ticket.get(1), temp.getOrDefault(ticket.get(1), 0) + 1);
            }else {
                temp = new TreeMap<>();
                temp.put( ticket.get(1), 1);
            }
            //将entry 放入 map
            map.put(ticket.get(0), temp);
        }

        //踩坑,这里没将 起点 放入 res
        res.add("JFK");

        backtracking(tickets.size());

        return new ArrayList<>(res);
    }

    //回溯法使用boolean类型的,一般都是只有一个解,找到那个唯一解就返回
    private boolean backtracking(int n){

        //边界条件
        if(res.size() == n + 1)
            return true;

        String last = res.getLast();
        if(map.containsKey(last)){
            for(Map.Entry<String, Integer> entry : map.get(last).entrySet()){

                int count = entry.getValue();
                if(count > 0){
                    res.add(entry.getKey());
                    entry.setValue(count - 1);
                    if(backtracking(n))
                        return true;
                    //踩坑,这里忘记写了
                    res.removeLast();
                    entry.setValue(count);
                }
            }
        }
        return false;
    }

}

51. N皇后

思路上相较其他两题简单一些。这里我踩了两个坑,需要大家注意一下:

  1. 当前的子集如果不满足解的形式,则 continue,而不是 return 。
			if(isValid(index, j, n))
                flag[index][j] = true;
            else
                //这里踩坑,应该continue,而不是return
                continue;
  1. 在判断左右对角线的时候,不需要取余。因为既然它符合条件,就一定是在[0,n)之间的值。为了防止数组越界,这里要判断当前求得的下标是否在[0,n)之间。
		//判断左对角线,如果i、j相加与另一个相等就在一个左对角线: i+j == k+l
        for (int k = 0; k < i; k++) {
            if(i+j-k >=0 && i+j-k<n && flag[k][ i+j-k ])
                return false;
        }

        //踩坑:这里可能会有溢出边界的情况,所以要用到 取余
        //踩坑:不能取余——>既然会取到负数或者超过n,就证明它肯定是不在一条直线上的
        //判断右对角线,如果 i - j == k - l
        for (int k = 0; k < i; k++) {
            if(k+j-i>=0 && k+j-i<n && flag[k][ k+j-i ])
                return false;
        }

以下是完整代码:

public class N皇后51 {

    @Test
    public void test(){

        solveNQueens(4);
    }

    //定义格子是否占用
    boolean[][] flag;

    //定义结果集
    List<List<String>> result = new ArrayList<>();

    public List<List<String>> solveNQueens(int n) {

        //初始化
        flag = new boolean[n][n];
        backtracking(n, 0);

        return result;
    }

    private void backtracking(int n, int index){

        //收集结果
        if( index == n ){

            //收集每个正确结果
            List<String> path = new ArrayList<>();

            //构造结果
            for (int i = 0; i < n; i++) {

                //收集每一行的字符串
                StringBuilder s = new StringBuilder();

                for (int j = 0; j < n; j++) {
                    if(flag[i][j])
                        s.append('Q');
                    else
                        s.append('.');
                }
                path.add(s.toString());
            }

            result.add(path);
            return;
        }

        //在当前行中遍历每个列的情况
        for (int j = 0; j < n; j++) {

            if(isValid(index, j, n))
                flag[index][j] = true;
            else
                //这里踩坑,应该continue,而不是return
                continue;

            //继续下一行
            backtracking(n, index+1);
            flag[index][j] = false;

        }
    }

    private boolean isValid(int i, int j, int n){

        //判断列,j相同就位于同一列
        for (int k = 0; k < i; k++) {
            if( flag[k][j] )
                return false;
        }

        //判断左对角线,如果i、j相加与另一个相等就在一个左对角线: i+j == k+l
        for (int k = 0; k < i; k++) {
            if(i+j-k >=0 && i+j-k<n && flag[k][ i+j-k ])
                return false;
        }

        //踩坑:这里可能会有溢出边界的情况,所以要用到 取余
        //踩坑:不能取余——>既然会取到负数或者超过n,就证明它肯定是不在一条直线上的
        //判断右对角线,如果 i - j == k - l
        for (int k = 0; k < i; k++) {
            if(k+j-i>=0 && k+j-i<n && flag[k][ k+j-i ])
                return false;
        }
        return true;
    }
}

三、37. 解数独

这道题由于有数值的填充,所以比n皇后问题多一层循环。
这里使用二维递归的方式,两层for循环遍历位置,一层for循环遍历数值。然后进行下一个递归函数的求解。
这里注意,在递归时,i、j都是从0开始遍历的,同时每次选择一个位置后都要判断该位置是否已被填充。所以i、j不需要再参数列表中。
最后,在判断同一个九宫格时的计算方法:[i / 3 * 3, i / 3 * 3 + 3)
以下是完整代码:

public class 解数独_题解37 {

    public void solveSudoku(char[][] board) {

        backtracking(board);
    }

    //二维递归
    //这里在参数表中没有涉及到 +1 ,因为移到方法体里的for循环了
    private boolean backtracking(char[][] board){

        //遍历位置
        //这里i、j都是从0开始的,因为会有一个判断位置中是否填充的句子
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[i].length; j++) {

                //判断当前格子是否填充
                if(board[i][j] != '.')
                    continue;

                //遍历每个格子中的数值大小
                for (int k = 1; k < 10; k++) {
                    board[i][j] = (char)(k+'0');
                    if(isValid(board, i, j)){

                        //递归,如果递归到最后返回true,则向上返回true
                        if(backtracking(board)) return true;
                        //回溯
                        board[i][j] = '.';
                    }else {
                        board[i][j] = '.';
                        continue;
                    }
                }

                //到了这里,说明9个数字都不行,所以return false,换其他方式
                return false;
            }
        }

        //把所有的格子都遍历完了,说明是正解,return true
        return true;
    }

    private boolean isValid(char[][] board,int i, int j){

        //判断行
        for (int k = 0; k < 9; k++) {
            if(k != j && board[i][k] == board[i][j])
                return false;
        }

        //判断列
        for (int k = 0; k < 9; k++) {
            if(k != i && board[k][j] == board[i][j])
                return false;
        }

        //判断九宫格 取余判断位置
        //k = i%3*3 ————>踩坑,应该是 k = i/3*3
        for (int k = i/3*3; k < i/3*3+3; k++) {
            for (int l = j/3*3; l < j/3*3+3; l++) {

                //踩坑:不能并        k != i && l != j && board[k][l] == board[i][j]
                if((k != i || l != j )&& board[k][l] == board[i][j])
                    return false;
            }
        }

        return true;
    }
}

你可能感兴趣的:(leetcode,java,算法)