算法训练营day30

本文介绍了使用Java编程实现的旅行行程重新安排问题(使用回溯法),N皇后问题(深度优先搜索)以及解数独问题(递归策略)。通过实例展示了如何在算法中处理行程规划和经典逻辑问题的求解。
摘要由CSDN通过智能技术生成

一、重新安排行程
class Solution {
    private LinkedList<String> result;
    // Map<出发机场, map<到达机场, 航班次数>> targets
    private Map<String, Map<String, Integer>> targets;

    public List<String> findItinerary(List<List<String>> tickets) {
        targets = new HashMap<String, Map<String, Integer>>();
        result = new LinkedList<>();
        // 把tickets的所有航班信息转换为:Map<出发机场, map<到达机场, 航班次数>> 的map集合
        for(List<String> t : tickets){
            Map<String, Integer> temp;
            if(targets.containsKey(t.get(0))){
                temp = targets.get(t.get(0));
                temp.put(t.get(1), temp.getOrDefault(t.get(1), 0) + 1);
            }else{
                // 升序Map,底层红黑树,保证了根据出发机场获取的map<到达机场, 航班次数>是有序的
                temp = new TreeMap<>();
                temp.put(t.get(1), 1);
            }
            targets.put(t.get(0), temp);
        }
        result.add("JFK"); // 起始机场
        backtracking(tickets.size()); // 传入机票的张数,用于终止条件判断
        return new ArrayList<>(result);
    }

    // 由于根据出发机场获取的map<到达机场, 航班次数>是有序的,所以找到的第一个路径就是需要的字典排序最小的行程组合。
    // 所以设置布尔类型返回值,找到结果停止遍历所有树,立马返回答案。
    private boolean backtracking(int ticketNum){
        if(result.size() == ticketNum + 1){
            return true;
        }
        // 获取起始位置
        String last = result.getLast();
        // 根据起始位置 确定能找到出发地为起始位置的机票
        if(targets.containsKey(last)){
            // 遍历该起始位置能到达的entry键值对<到达机场, 航班次数>
            for(Map.Entry<String, Integer> target : targets.get(last).entrySet()){
                // count为 该<起始位置, 到达机场> 的机票张数
                int count = target.getValue();
                // count>0(有票)的情况
                if(count > 0){
                    result.add(target.getKey()); // 该目的地存入result中
                    target.setValue(count - 1); // 票数 - 1
                    if(backtracking(ticketNum)) {
                        return true;
                    }
                    target.setValue(count); // 回溯
                    result.removeLast(); // 回溯
                }
            }
        }
        return false;
    }
}

二、N皇后

注:

row.append(“.”.repeat(Math.max(0, n))); 中 String.repeat是jdk11的方法

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;

public class Solution {

    private int n;
    /**
     * 记录某一列是否放置了皇后
     */
    private boolean[] col;
    /**
     * 记录主对角线上的单元格是否放置了皇后
     */
    private boolean[] main;
    /**
     * 记录了副对角线上的单元格是否放置了皇后
     */
    private boolean[] sub;
    private List<List<String>> res;

    public List<List<String>> solveNQueens(int n) {
        res = new ArrayList<>();
        if (n == 0) {
            return res;
        }

        // 设置成员变量,减少参数传递,具体作为方法参数还是作为成员变量,请参考团队开发规范
        this.n = n;
        this.col = new boolean[n];
//这里的 2 * n - 1 刚好是 主对角线或者副对角线的数量
        this.main = new boolean[2 * n - 1];
        this.sub = new boolean[2 * n - 1];
        Deque<Integer> path = new ArrayDeque<>();
        dfs(0, path);
        return res;
    }

    private void dfs(int row, Deque<Integer> path) {
        if (row == n) {
            // 深度优先遍历到下标为 n,表示 [0.. n - 1] 已经填完,得到了一个结果
            List<String> board = convert2board(path);
            res.add(board);
            return;
        }

        // 针对下标为 row 的每一列,尝试是否可以放置
        for (int j = 0; j < n; j++) {
//对于当前行的每一列,判断是否可以放置皇后。判断的条件是当前列未被占据,并且当前位置所在的主对角线和副对角线上都没有皇后
            
//主对角线特点 左上 -> 右下:横坐标 - 纵坐标的值固定,为了保证值非负
//计算逻辑为 row - j + n - 1
//副对角线特点 左下 -> 右上:横纵坐标之和固定
            if (!col[j] && !main[row - j + n - 1] && !sub[row + j]) {
// 如果符合条件,将当前列号保存,并将对应的列,主对角线,副对角线设置为true
                path.addLast(j);
                col[j] = true;
                main[row - j + n - 1] = true;
                sub[row + j] = true;

//进入下一层递归,也是下一行递归;
                dfs(row + 1, path);
//返回递归的时候移除递归的影响
                sub[row + j] = false;
                main[row - j + n - 1] = false;
                col[j] = false;
                path.removeLast();
            }
        }
    }

    private List<String> convert2board(Deque<Integer> path) {
        List<String> board = new ArrayList<>();
        for (Integer num : path) {
            StringBuilder row = new StringBuilder();
            row.append(".".repeat(Math.max(0, n)));
    //左闭右开,包含左边,但是不包含右边
            row.replace(num, num + 1, "Q");
            board.add(row.toString());
        }
        return board;
    }
}
三、解数独
class Solution {
    public void solveSudoku(char[][] board) {
        solveSudokuHelper(board);
    }

    private boolean solveSudokuHelper(char[][] board){
        //「一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,
        // 一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!」
        for (int i = 0; i < 9; i++){ // 遍历行
            for (int j = 0; j < 9; j++){ // 遍历列
                if (board[i][j] != '.'){ // 跳过原始数字
                    continue;
                }
                for (char k = '1'; k <= '9'; k++){ // (i, j) 这个位置放k是否合适
                    if (isValidSudoku(i, j, k, board)){
                        board[i][j] = k;
                        if (solveSudokuHelper(board)){ // 如果找到合适一组立刻返回
                            return true;
                        }
                        board[i][j] = '.';
                    }
                }
                // 9个数都试完了,都不行,那么就返回false
                return false;
                // 因为如果一行一列确定下来了,这里尝试了9个数都不行,说明这个棋盘找不到解决数独问题的解!
                // 那么会直接返回, 「这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去!」
            }
        }
        // 遍历完没有返回false,说明找到了合适棋盘位置了
        return true;
    }

    /**
     * 判断棋盘是否合法有如下三个维度:
     *     同行是否重复
     *     同列是否重复
     *     9宫格里是否重复
     */
    private boolean isValidSudoku(int row, int col, char val, char[][] board){
        // 同行是否重复
        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;
            }
        }
        // 9宫格里是否重复
        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] == val){
                    return false;
                }
            }
        }
        return true;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值