leetcode 刷题_day32_回溯06_哈希表优化横向遍历

332. 重新安排行程

思路

题目本质是要在机票列表中找到符合条件的组合/排列,因此可以使用回溯来解题

image.png

注意:结果是有排序要求的,要求返回从小到大最小的结果

解题方法

在回溯的横向遍历中,有两种解法

  1. 直接 for 循环暴力遍历所有的机票,在机票较多时容易超时
  2. 使用哈希表重构机票列表,构建 出发机场 -> (到达机场 & 航班次数)哈希表,提高遍历效率

无论哪种解法,在遍历之前都需要将待遍历对向进行排序才能保证最终的 path正确

复杂度

  • 时间复杂度:

添加时间复杂度, 示例: O ( n ) O(n) O(n)

  • 空间复杂度:

添加空间复杂度, 示例: O ( n ) O(n) O(n)

Code

暴力检索

class Solution {

    private LinkedList<String> path = new LinkedList<>();

    // 指示到过的目的地,避免进入死循环
    private boolean[] used;
     
    public List<String> findItinerary(List<List<String>> tickets) {
        used = new boolean[tickets.size()];
        // 对 tickets 的**终点**进行升序排序,保证最先找到的路径是字典排序最小的
        Collections.sort(tickets, (a, b) -> a.get(1).compareTo(b.get(1)));
        // 起始位置为 "JFK"
        path.add("JFK");
        
        backTracking(tickets);

        // 只需要一个路径结果
        return path;
    }

    private boolean backTracking(List<List<String>> tickets) {
        // 终止条件
        // 路径已经规划完成,整个流程结束,也即回溯到一条符合的路径后,整个回溯递归需要终止
        if (path.size() == tickets.size() + 1) {
            return true;
        }

        // 采用暴力的横向遍历,每一层都要对所有的机票进行遍历判断,最终会在第 80 个测试用例中超时
        for (int i = 0; i < tickets.size(); i++) {

            // 当前机票没用过且起始位置是上一层级的终点
            if (!used[i] && tickets.get(i).get(0).equals(path.getLast())) {
                // 加入路径
                path.add(tickets.get(i).get(1));
                used[i] = true;
            } else {
                continue;
            }

            boolean hasRes = backTracking(tickets);
            if (hasRes) {
                return true;
            }

            used[i] = false;
            path.removeLast();
        }

        return false;
    }
}

哈希表优化


  class Solution {

    private LinkedList<String> path = new LinkedList<>();
    // Map<出发机场, Map<到达机场, 航班次数>>
    // 内嵌 Map 使用 TreeMap,维持大顶堆,这样顶上的就是最小的几个
    private Map<String, Map<String, Integer>> target = new HashMap<String, Map<String, Integer>>();
    private int ticketNum;

    public List<String> findItinerary(List<List<String>> tickets) {
        // 把 tickets 的所有航班信息转换为:Map<出发机场, Map<到达机场, 航班次数>> 的 Map 集合
        for (List<String> t: tickets) {
            // 每个出发机场放入 target,并构建 TreeMap 放所有从这个机场出发的机票
            if (!target.containsKey(t.get(0))) {
                Map<String, Integer> temp = new TreeMap<>();
                temp.put(t.get(1), 1);
                target.put(t.get(0), temp);
            }
            // 追加
            else {
                Map<String, Integer> temp = target.get(t.get(0));
                temp.put(t.get(1), temp.getOrDefault(t.get(1), 0) + 1);
            }
        }

        // 起始机场
        path.add("JFK");
        ticketNum = tickets.size();

        backTracking();

        return path;
    }

    private boolean backTracking() {
        // 终止条件,路径包含完整行程即可退出整个流程
        if (path.size() == ticketNum + 1) {
            return true;
        }

        // 横向遍历
        // 使用哈希表替代暴力检索,避免判断不可能到达的目的地
        if (target.containsKey(path.getLast())) {
            // 所有可能目的地,因为使用的TreeMap 大顶堆,所以遍历时是从小到大遍历
            for (Map.Entry<String, Integer> t: target.get(path.getLast()).entrySet()) {
                // 当前目的地所余票数
                Integer cnt = t.getValue();
                if (cnt > 0) {
                    t.setValue(--cnt);
                    path.add(t.getKey());

                    if (backTracking()) {
                        return true;
                    }

                    t.setValue(++cnt);
                    path.removeLast();
                }
            }
        }

        return false;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值