代码随想录算法训练营第三十天| 332.重新安排行程, 51. N皇后, 37. 解数独,总结

题目与题解

参考资料:回溯总结

332.重新安排行程

题目链接:332.重新安排行程

代码随想录题解:332.重新安排行程

视频讲解:带你学透回溯算法(理论篇)| 回溯法精讲!_哔哩哔哩_bilibili

解题思路:

        这题有两个难点,一个是要求返回的结果是按升序排列最小的,另一个是行程要保证使用每一张机票,并且假设机票行程存在环路,不能困在环中。

        针对第一个难点,可以提前对tickets进行排序,利用list.sort方法重写其中的comparator,排序的key为tickets里面每一张ticket的目的地,也就是tickets.get(i).get(1),这样保证有结果时,第一个结果一定是升序最小。

        针对第二个难点,这里是树枝的去重问题,所以可以用一个usedTickets数组,记录每一张ticket的使用情况,用过就设置为true,保证不会重复使用。

        然后就可以用回溯三部曲:

        参数有 - 用于存放结果的result,用于记录路线的path,输入tickets,usedTickets数组和每次递归时的起点start(这里也可以不需要这个参数,用path.getLast()代替)

        终止条件 - 题目要求每张机票都使用,那么每有N张机票,途径地点就有N+1个,所以当path的元素到达n+1后,路径就完成了,可以返回结果。

        单层遍历逻辑:循环遍历tickets里面的每一个元素,如果usedTickets[i]为true,或者tickets.get(i).get(1) 不等于start,则跳过该循环;否则将tickets.get(i).get(1)加入path,usedTickets[i]设置为true,调用回溯方法,修改start为tickets.get(i).get(1),出来后修改usedTickets[i]为false并弹出path最后一个元素即可。

class Solution {
	List<String> result = new ArrayList<>();
	LinkedList<String> path = new LinkedList<>();
    public List<String> findItinerary(List<List<String>> tickets) {
		boolean[] usedTickets = new boolean[tickets.size()];
		// tickets.sort((list1, list2) -> list1.get(0).compareTo(list2.get(0)));
		tickets.sort(Comparator.comparing(list -> list.get(1)));
		path.add("JFK");
		findItinerary(tickets, usedTickets, "JFK");
		return result;
    }

	boolean findItinerary(List<List<String>> tickets, boolean[] usedTickets, String start) {
		if (path.size() == tickets.size() + 1) {
			result = new ArrayList<>(path);
			return true;
		}
		for (int i = 0; i < tickets.size(); i++) {
			if (usedTickets[i] || !tickets.get(i).get(0).equals(start)) continue;
			path.add(tickets.get(i).get(1));
			usedTickets[i] = true;
			if (findItinerary(tickets, usedTickets, tickets.get(i).get(1)))
				return true;
			usedTickets[i] = false;
			path.pollLast();
		}
		return false;
	}
}

但是这样写在leetcode上不能ac,会提示超时,因为每次遍历tickets都是从头开始遍历的,搜索效率非常低,需要用map替代list。 

看完代码随想录之后的想法 

        随想录答案改造了一下ticket,用Map<String, Map<String, Integer>> map存储tickets每一个起点对应的多个目的地,以及机票用过与否的情况。目前自己写还比较难,先放着以后看。

class Solution {
    private Deque<String> res;
    private Map<String, Map<String, Integer>> map;

    private boolean backTracking(int ticketNum){
        if(res.size() == ticketNum + 1){
            return true;
        }
        String last = res.getLast();
        if(map.containsKey(last)){//防止出现null
            for(Map.Entry<String, Integer> target : map.get(last).entrySet()){
                int count = target.getValue();
                if(count > 0){
                    res.add(target.getKey());
                    target.setValue(count - 1);
                    if(backTracking(ticketNum)) return true;
                    res.removeLast();
                    target.setValue(count);
                }
            }
        }
        return false;
    }

    public List<String> findItinerary(List<List<String>> tickets) {
        map = new HashMap<String, Map<String, Integer>>();
        res = new LinkedList<>();
        for(List<String> t : tickets){
            Map<String, Integer> temp;
            if(map.containsKey(t.get(0))){
                temp = map.get(t.get(0));
                temp.put(t.get(1), temp.getOrDefault(t.get(1), 0) + 1);
            }else{
                temp = new TreeMap<>();//升序Map
                temp.put(t.get(1), 1);
            }
            map.put(t.get(0), temp);

        }
        res.add("JFK");
        backTracking(tickets.size());
        return new ArrayList<>(res);
    }
}

遇到的困难

        想了一个多小时,超时问题实在不知道怎么解,hard不愧是hard。

51. N皇后

题目链接:51. N皇后

代码随想录题解:51. N皇后

视频讲解:​​​​​​​这就是传说中的N皇后? 回溯算法安排!| LeetCode:51.N皇后_哔哩哔哩_bilibili

解题思路:

       两眼一麻黑,直接抄答案。

看完代码随想录之后的想法 

        重点还是得把图画出来,理清每一层的思路,才能真正写出来。

        首先来看一下皇后们的约束条件:

  1. 不能同行
  2. 不能同列
  3. 不能同斜线

确定完约束条件,来看看究竟要怎么去搜索皇后们的位置,这里可以抽象为一棵树。

用一个 3 * 3 的棋盘,将搜索过程抽象为一棵树,如图:

51.N皇后

从图中,可以看出,二维矩阵中矩阵的高就是这棵树的高度,矩阵的宽就是树形结构中每一个节点的宽度。

那么我们用皇后们的约束条件,来回溯搜索这棵树,只要搜索到了树的叶子节点,说明就找到了皇后们的合理位置了

递归函数参数:定义全局变量二维数组result来记录最终结果,参数n是棋盘的大小,然后用row来记录当前遍历到棋盘的第几层了。

终止条件:当递归到棋盘最底层(也就是叶子节点)的时候,就可以收集结果并返回了,此时row=n

单层搜索的逻辑:递归深度就是row控制棋盘的行,每一层里for循环的col控制棋盘的列,一行一列,确定了放置皇后的位置。每次都是要从新的一行的起始位置开始搜,所以都是从0开始。

验证棋盘是否合法:按照如下标准去重:

  1. 不能同行,这里不需要检查,因为每次递归必然是换新的一行
  2. 不能同列
  3. 不能同斜线 (45度和135度角),这里注意超过180度的对角不用考虑,因为按递归的顺序,大于row的行里面不会有棋子。
class Solution {
	List<List<String>> result = new ArrayList<>();
	List<String> path = new ArrayList<>();
    public List<List<String>> solveNQueens(int n) {
		char[][] chessboard = new char[n][n];
		for (int i = 0; i < n; i++) {
			Arrays.fill(chessboard[i], '.');
		}
		solveNQueens(n, 0, chessboard);
		return result;
    }
	void solveNQueens(int n, int row, char[][] chessboard) {
		if (row == n) {
			List<String> temp= new ArrayList<>();
			for (int i = 0; i < n; i++) {
				temp.add(String.copyValueOf(chessboard[i]));
			}
			result.add(temp);
			return;
		}
		for (int i = 0; i < n; i++) {
			if (!isValid(chessboard, i, row, n)) continue;
			chessboard[row][i] = 'Q';
			solveNQueens(n, row+1, chessboard);
			chessboard[row][i] = '.';
		}
	}

	boolean isValid(char[][] chessboard, int col, int row, int n) {
		for (int i = 0; i < n; i++) {
			if (chessboard[i][col] == 'Q')
				return false;
		}
		for (int i = 1; col - i >= 0 && row - i >= 0 ; i++) {
			if (chessboard[row - i][col - i] == 'Q')
				return false;
		}
		for (int i = 1; col + i < n && row - i >= 0 ; i++) {
			if (chessboard[row - i][col + i] == 'Q')
				return false;
		}
		return true;
	}
}

遇到的困难

        一开始连N皇后问题的定义都不是很清楚,就抓瞎了,这种特型的二维数组题以后就有参考标准了。

37. 解数独

题目链接:37. 解数独

代码随想录题解:​​​​​​​​​​​​​​37. 解数独

视频讲解:回溯算法二维递归?解数独不过如此!| LeetCode:37. 解数独_哔哩哔哩_bilibili

解题思路:

       见答案,二刷再看

看完代码随想录之后的想法 

        

遇到的困难

        

今日收获

        今天全是hard题,确实很难,二刷再好好看。

回溯总结:

回溯算法能解决如下问题:

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 棋盘问题:N皇后,解数独等等

在树形结构中子集问题是要收集所有节点的结果,而组合问题是收集叶子节点的结果。

对于组合问题,for循环在寻找起点的时候要有一个范围,如果这个起点到集合终止之间的元素已经不够题目要求的k个元素了,就没有必要搜索了

排列问题与组合的不同:

  • 每层都是从0开始搜索而不是startIndex
  • 需要used数组记录path里都放了哪些元素了

去重:使用set去重的版本相对于used数组的版本效率都要低很多

  • 21
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值