代码随想录算法训练营第28天

今日任务

● 491.递增子序列 ● 46.全排列 ● 47.全排列 II

● 332.重新安排行程 ● 51. N皇后 ● 37. 解数独 ● 总结

链接:https://docs.qq.com/doc/DUHZYbWhwSHRCRmp3 (opens in a new tab)

链接:https://docs.qq.com/doc/DUEdTVVhxbnJiY3BR (opens in a new tab)

491.递增子序列

自己想法

保证递增的判断只需判断1轮(代码15行),即在进入递归for之前,比较start到end的数是否大于等于path里最后一个数,否则标记,以便跳过。

保证没有重复的判断需要判断多轮,即在递归for之内,把当前后面和nums[i]相等的数都标记,以便跳过。

这样block就可以同时保证递增和去重,在递归for开头判断block[i]是否为true,为true则跳过。

需要注意,block的作用域是单个递归(函数)过程,而非全局生效。也就是去重和递增的判断限制在同一树层,而不是整个树。

class Solution {
    public List<List<Integer>> ans = new ArrayList<>();
    public List<Integer> path = new ArrayList<>();
    public List<List<Integer>> findSubsequences(int[] nums) {
        backtrack(nums, 0, nums.length);
        return ans;
    }
    public void backtrack(int[] nums, int start, int end) {
        if(path.size() >= 2) ans.add(new ArrayList<>(path));
        if(start == end) return;
        else {
            boolean[] block = new boolean[nums.length];
 
            // 保证递增
            for(int j = start; j < end; j++) if(path.size() > 0 && nums[j] < path.get(path.size() - 1)) block[j] = true;
 
            for(int i = start; i < end; i++) {
                
                // 利用block同时保证不重复且递增
                while(i < end && block[i]) i++;
 
                if(i < end) {
                    path.add(nums[i]);
                    backtrack(nums, i + 1, end);
                    path.remove(path.size() - 1);
 
                    // 去重
                    for(int j = i + 1; j < end; j++) if(nums[j] == nums[i]) block[j] = true;
                }
            }
        }
    }
}

46.全排列

自己想法

class Solution {
    List<List<Integer>> ans = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> permute(int[] nums) {
        boolean[] used = new boolean[nums.length];
        backtrack(nums, used);
        return ans;
    }
    public void backtrack(int[] nums, boolean[] used) {
        if(path.size() == nums.length) ans.add(new ArrayList<>(path));
        else {
            for(int i = 0; i < nums.length; i++) {
                // 没用过的数才能用
                while(i < nums.length && used[i]) i++;
                if(i < nums.length) {
                    path.add(nums[i]);
                    used[i] = true;
                    backtrack(nums, used);
                    path.removeLast();
                    used[i] = false;
                }
            }
        }
    }
}

47.全排列 II

自己想法

我这里又用了一个block数组来去重。

不过这个题也可以用排序的方法来去重,类似40.组合总和II (opens in a new tab)

class Solution {
    List<List<Integer>> ans = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> permuteUnique(int[] nums) {
        boolean[] used = new boolean[nums.length];
        backtrack(nums, used);
        return ans;
    }
    public void backtrack(int[] nums, boolean[] used) {
        if(path.size() == nums.length) ans.add(new ArrayList<>(path));
        else {
            boolean[] block = new boolean[nums.length];
            for(int i = 0; i < nums.length; i++) {
                // 没用过且不重复(while条件是反过来写的:用过或重复)
                while(i < nums.length && (used[i] || block[i])) i++;
                if(i < nums.length) {
                    path.add(nums[i]);
                    used[i] = true;
                    backtrack(nums, used);
                    path.removeLast();
                    used[i] = false;
                    // 去重
                    for(int j = 0; j < nums.length; j++) if(nums[i] == nums[j]) block[j] = true;
                }
            }
        }
    }
}

332.重新安排行程

自己想法

开始自己写没剪枝,会超时。稍微改了改不超时了,但还是比较慢(78ms,打败5%)

class Solution {
    public List<String> ans = new ArrayList<>();
    public List<String> path = new ArrayList<>();
    public List<String> findItinerary(List<List<String>> tickets) {
        // trick1:用于剪枝,保证第一个找到的就是字典序最小
        Collections.sort(tickets, (a, b) -> a.get(1).compareTo(b.get(1)));
        boolean[] used = new boolean[tickets.size()];
        path.add("JFK");
        solve(tickets, used, "JFK");
        return ans;
    }
    public void solve(List<List<String>> tickets, boolean[] used, String nowAt) {
        if(path.size() == tickets.size() + 1) ans = new ArrayList<>(path);
        else {
            // trick2:用于剪枝,跳过重复票
            boolean[] block = new boolean[tickets.size()];
            for(int i = 0; i < tickets.size(); i++) {
                // 没用过且票不重复且出发地是nowAt(while条件是用过或重复或不在nowAt)
                while(i < tickets.size() && (used[i] || block[i] || !nowAt.equals(tickets.get(i).get(0)))) i++;
                if(i < tickets.size()) {
                    path.add(tickets.get(i).get(1));
                    used[i] = true;
                    if(ans.size() == 0) solve(tickets, used, tickets.get(i).get(1));
                    path.remove(path.size() - 1);
                    used[i] = false;
                    for(int j = i + 1; j < tickets.size(); j++)    
                        if(tickets.get(j).get(0).equals(tickets.get(i).get(0)) &&
                           tickets.get(j).get(1).equals(tickets.get(i).get(1))) block[j] = true;
                }
            }
        }
    }
}

51. N皇后

自己想法

纯暴力,不剪枝,一个格子一个格子地试,178ms

class Solution {
    public List<List<String>> ans = new ArrayList<>();
    public boolean[][] one_ans;
    public List<List<String>> solveNQueens(int n) {
        one_ans = new boolean[n][n];
        backtrack(0, n, 0);
        return ans;
    }
    /*  placed: 已经放置的棋子数
        n: n
        coordinate: 当前待测试的第一个位置,其范围为[0, n*n)
    */
    public void backtrack(int placed, int n, int coordinate) {
        if(placed == n) ans.add(queenArray2list(one_ans));
        else {
            for(int i = coordinate; i < n * n; i++) {
                if(canPlace(n, i)) {
                    one_ans[i / n][i % n] = true;
                    backtrack(placed + 1, n, i + 1);
                    one_ans[i / n][i % n] = false;
                }
            }
        }
    }
    public List<String> queenArray2list(boolean[][] array) {
        List<String> resu = new ArrayList<>();
        for(int i = 0; i < array.length; i++) {
            StringBuilder strBuilder = new StringBuilder("");
            for(int j = 0; j < array[i].length; j++) {
                if(array[i][j]) {
                    strBuilder.append('Q');
                    // array[i][j] = 0;
                }
                else strBuilder.append('.');
            }
            resu.add(strBuilder.toString());
        }
        return resu;
    }
 
    public boolean canPlace(int n, int coordinate) {
        int x = coordinate / n;
        int y = coordinate % n;
        // 同行
        for(int i = 0; i < n; i++) if(i != y && one_ans[x][i]) return false;
        // 同列
        for(int i = 0; i < n; i++) if(i != x && one_ans[i][y]) return false;
        // 斜线
        // 遍历右上角到左下角的斜线
        for (int i = 0; i < n; i++) {
            int j = x + y - i;
            if (j != y && j >= 0 && j < n && one_ans[i][j]) return false;
        }
        // 遍历左上角到右下角的斜线
        for (int i = 0; i < n; i++) {
            int j = y - x + i;
            if (j != y && j >= 0 && j < n && one_ans[i][j]) return false;
        }
        return true;
    }
}

剪枝:一行只能放一个,那么backtrack直接跳到下一行。3ms

class Solution {
    public List<List<String>> ans = new ArrayList<>();
    public boolean[][] one_ans;
    public List<List<String>> solveNQueens(int n) {
        one_ans = new boolean[n][n];
        backtrack(n, 0);
        return ans;
    }
    /*  n: n
        row: 当前测试的行
    */
    public void backtrack(int n, int row) {
        if(row == n) ans.add(queenArray2list(one_ans));
        else {
            // 测试列
            for(int i = 0; i < n; i++) {
                if(canPlace(n, row, i)) {
                    one_ans[row][i] = true;
                    backtrack(n, row + 1);
                    one_ans[row][i] = false;
                }
            }
        }
    }
    public List<String> queenArray2list(boolean[][] array) {
        List<String> resu = new ArrayList<>();
        for(int i = 0; i < array.length; i++) {
            StringBuilder strBuilder = new StringBuilder("");
            for(int j = 0; j < array[i].length; j++) {
                if(array[i][j]) {
                    strBuilder.append('Q');
                    // array[i][j] = 0;
                }
                else strBuilder.append('.');
            }
            resu.add(strBuilder.toString());
        }
        return resu;
    }
 
    public boolean canPlace(int n, int x, int y) {
        // 同列
        for(int i = 0; i < n; i++) if(i != x && one_ans[i][y]) return false;
        // 斜线
        for (int i = 0; i < n; i++) {
            int j = x + y - i;
            if (j != y && j >= 0 && j < n && one_ans[i][j]) return false;
        }
        for (int i = 0; i < n; i++) {
            int j = y - x + i;
            if (j != y && j >= 0 && j < n && one_ans[i][j]) return false;
        }
        return true;
    }
}

37. 解数独

自己想法

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(int k = 1; k <= 9; k++) {
                    if(sudokuCheck(i, j, k, board)) {
                        board[i][j] = (char) ('0' + k);
                        if(backtrack(board)) return true;
                        board[i][j] = '.';
                    }
                }
                // 试了9个数都不行说明当前尝试已经导致没有解了,无需继续尝试,应该返回上一层
                return false;
            }
        }
        return true;
    }
    public boolean sudokuCheck(int row, int col, int try_num, char[][] board) {
        // 行
        for(int i = 0; i < 9; i++) if(board[row][i] - '0' == try_num) return false;
        // 列
        for(int j = 0; j < 9; j++) if(board[j][col] - '0' == try_num) return false;
        // 宫内
        int startRow = (row / 3) * 3;
        int startCol = (col / 3) * 3;
        for(int i = startRow; i < startRow + 3; i++) {
            for(int j = startCol; j < startCol + 3; j++) {
                if(board[i][j] - '0' == try_num) return false;
            }
        }
        return true;
    }
}