leetcode刷题:回溯算法14(重新安排行程)

46 篇文章 0 订阅
11 篇文章 0 订阅

332.重新安排行程

力扣题目链接

给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。

提示:

  • 如果存在多种有效的行程,请你按字符自然排序返回最小的行程组合。例如,行程 [“JFK”, “LGA”] 与 [“JFK”, “LGB”] 相比就更小,排序更靠前
  • 所有的机场都用三个大写字母表示(机场代码)。
  • 假定所有机票至少存在一种合理的行程。
  • 所有的机票必须都用一次 且 只能用一次。

示例 1:

  • 输入:[[“MUC”, “LHR”], [“JFK”, “MUC”], [“SFO”, “SJC”], [“LHR”, “SFO”]]
  • 输出:[“JFK”, “MUC”, “LHR”, “SFO”, “SJC”]

示例 2:

  • 输入:[[“JFK”,“SFO”],[“JFK”,“ATL”],[“SFO”,“ATL”],[“ATL”,“JFK”],[“ATL”,“SFO”]]
  • 输出:[“JFK”,“ATL”,“JFK”,“SFO”,“ATL”,“SFO”]
  • 解释:另一种有效的行程是 [“JFK”,“SFO”,“ATL”,“JFK”,“ATL”,“SFO”]。但是它自然排序更大更靠后。

有一说一,这题还是蛮难的,我做了得有七八个小时,思路是正确的,但最终超时。
先放一个我的思路:找到所有的路径,然后筛选。

package com.programmercarl.backtracking;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @ClassName FindItinerary
 * @Descriotion TODO
 * @Author nitaotao
 * @Date 2022/7/11 21:14
 * @Version 1.0
 * https://leetcode.cn/problems/reconstruct-itinerary/
 * 332. 重新安排行程
 *
 * 找到所有结果
 * 筛选最小路径
 *
 **/
public class FindItinerary1 {
    //结果集
    List<List<String>> result = new ArrayList<>();
    List<String> path = new ArrayList<>();


    public List<String> findItinerary(List<List<String>> tickets) {
        boolean[] used = new boolean[tickets.size()];
        // false 表示该车票没有用
        // true 表示该车票已经用了
        Arrays.fill(used, false);
        path.add("JFK");
        //获取所有机票
        backtracking(tickets, used, "JFK");
//        System.out.println(result);
        //删除多于的机票。得到最小路径机票
        getMinPath(0);
        System.out.println("最终结果:" + result);
        return result.get(0);
    }

    /**
     * @param tickets 所有车票
     * @param from    上一个出发的地方
     */
    public void backtracking(List<List<String>> tickets, boolean[] used, String from) {
        //如果规划好一个行程,则添加
        if (path.size() == tickets.size() + 1) {
            result.add(new ArrayList(path));
            return;
        }
        for (int i = 0; i < tickets.size(); i++) {
            //如果该机票已经被使用,则跳过
            if (used[i]) {
                continue;
            }
            //如果该机票的出发地不是之前的降落地,则返回
            if (!tickets.get(i).get(0).equals(from)) {
                continue;
            }
            used[i] = true;
            path.add(tickets.get(i).get(1));
            // 降落地点要更新
            backtracking(tickets, used, tickets.get(i).get(1));
            //回溯
            used[i] = false;
            path.remove(path.size() - 1);
        }
    }


    //删除多于的机票
    public void getMinPath(int curIndex) {
        if (result.size() == 1) {
            return;
        }
        char[] chars = new char[result.size()];
        char min = 'Z';
        //获取当前curIndex城市的字典第一位最小值
        for (int i = 0; i < result.size(); i++) {
            chars[i] = result.get(i).get(curIndex).charAt(0);
            if (chars[i] < min) {
                min = chars[i];
            }
        }
        //凡是比这个值不一样的都删除
        List<List<String>> temp = new ArrayList<>();
        for (int i = 0; i < result.size(); i++) {
            if (result.get(i).get(curIndex).charAt(0) == min) {
                temp.add(new ArrayList<>(result.get(i)));
            }
        }
        result = temp;



        min = 'Z';
        //获取当前curIndex城市的字典第二位最小值
        for (int i = 0; i < result.size(); i++) {
            chars[i] = result.get(i).get(curIndex).charAt(1);
            if (chars[i] < min) {
                min = chars[i];
            }
        }


        temp = new ArrayList<>();
        //凡是比这个值不一样的都删除
        for (int i = 0; i < result.size(); i++) {
            if (result.get(i).get(curIndex).charAt(1) == min) {
                temp.add(new ArrayList<>(result.get(i)));
            }
        }
        result = temp;

        min = 'Z';
        //获取当前curIndex城市的字典第三位最小值
        for (int i = 0; i < result.size(); i++) {
            chars[i] = result.get(i).get(curIndex).charAt(2);
            System.out.println(chars[i]);
            if (chars[i] < min) {
                min = chars[i];
            }
        }
        temp = new ArrayList<>();
        //凡是比这个值不一样的都删除

        for (int i = 0; i < result.size(); i++) {
            if (result.get(i).get(curIndex).charAt(2) == min) {
                temp.add(new ArrayList<>(result.get(i)));
            }
        }
        result = temp;

        curIndex++;
        if (curIndex == result.get(0).size()) {
            return;
        }
        getMinPath(curIndex);
    }


}

在这里插入图片描述
在这里插入图片描述

然后开始求助群好友们。在他们的引导下,改变思路,贪心思想:只要找到一条路就可以了,步步最优。
在这里插入图片描述

package com.programmercarl.backtracking;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @ClassName FindItinerary
 * @Descriotion TODO
 * @Author nitaotao
 * @Date 2022/7/11 21:14
 * @Version 2.0
 * https://leetcode.cn/problems/reconstruct-itinerary/
 * 332. 重新安排行程
 * 贪心算法思想
 *
 * 每步找到最优解
 *
 **/
public class FindItinerary2 {
    //结果集
    List<List<String>> result = new ArrayList<>();
    List<String> path = new ArrayList<>();
    List<String> to = new ArrayList<>();

    /**
     * 使用贪心算法思想,每次寻找最优解
     *
     * @param tickets
     * @return
     */

    public List<String> findItinerary(List<List<String>> tickets) {
        boolean[] used = new boolean[tickets.size()];
        // false 表示该车票没有用
        // true 表示该车票已经用了
        Arrays.fill(used, false);
        path.add("JFK");
        //获取所有机票
        backtracking(tickets, used, "JFK");
//        System.out.println(result);
        return result.get(0);
    }

    /**
     * @param tickets 所有车票
     * @param from    上一个出发的地方
     */
    public void backtracking(List<List<String>> tickets, boolean[] used, String from) {
        //找到最优解
        if (result.size() == 1) {
            return;
        }
        //如果规划好一个行程,则添加
        if (path.size() == tickets.size() + 1) {
            result.add(new ArrayList(path));
            return;
        }
        //找到当前机场的所有下一个机场
        for (int i = 0; i < tickets.size(); i++) {
            //如果该机票已经被使用,则跳过
            if (used[i]) {
                continue;
            }
            //如果该机票的出发地不是之前的降落地,则跳过
            if (!tickets.get(i).get(0).equals(from)) {
                continue;
            }
            //没有使用的,下一个机场
            //下一个机场的集合
            to.add(tickets.get(i).get(1));
        }
        if (to.size() == 0 && path.size() < tickets.size() + 1) {
            //此路不通
            return;
        }
        String minTo = getMinPath();
        to.clear();

        for (int i = 0; i < tickets.size(); i++) {
            if (used[i]) {
                continue;
            }
            //如果 to 不是 最优机场
            if (!tickets.get(i).get(1).equals(minTo)) {
                continue;
            }
            used[i] = true;
            path.add(minTo);
            // 降落地点要更新
            backtracking(tickets, used, minTo);
            //回溯
            used[i] = false;
            path.remove(path.size() - 1);
        }
    }


    //获取最近的机票
    public String getMinPath() {
        if (to.size() == 1) {
            return to.get(0);
        }
        char[] chars = new char[to.size()];
        char min = 'Z';
        //获取当前curIndex城市的字典第一位最小值
        for (int i = 0; i < to.size(); i++) {
            chars[i] = to.get(i).charAt(0);
            if (chars[i] < min) {
                min = chars[i];
            }
        }
        //凡是比这个值不一样的都删除
        List<String> temp = new ArrayList<>();
        for (int i = 0; i < to.size(); i++) {
            if (to.get(i).charAt(0) == min) {
                temp.add(to.get(i));
            }
        }
        to = temp;
        if (to.size() == 1) {
            return to.get(0);
        }
        min = 'Z';
        chars = new char[to.size()];
        //获取当前curIndex城市的字典第二位最小值
        for (int i = 0; i < to.size(); i++) {
            chars[i] = to.get(i).charAt(1);
            if (chars[i] < min) {
                min = chars[i];
            }
        }

        temp = new ArrayList<>();
        //凡是比这个值不一样的都删除
        for (int i = 0; i < to.size(); i++) {
            if (to.get(i).charAt(1) == min) {
                temp.add(to.get(i));
            }
        }
        to = temp;
        if (to.size() == 1) {
            return to.get(0);
        }
        min = 'Z';
        chars = new char[to.size()];
        //获取当前curIndex城市的字典第三位最小值
        for (int i = 0; i < to.size(); i++) {
            chars[i] = to.get(i).charAt(2);
            if (chars[i] < min) {
                min = chars[i];
            }
        }
        //凡是和这个值不一样的都删除
        temp = new ArrayList<>();
        for (int i = 0; i < to.size(); i++) {
            if (to.get(i).charAt(2) == min) {
                temp.add(to.get(i));
            }
        }
        to = temp;
        return to.get(0);
    }

    public static void main(String[] args) {
        List<List<String>> tickets = new ArrayList<List<String>>();
        ArrayList ticket1 = new ArrayList<ArrayList>();
        ArrayList ticket2 = new ArrayList<ArrayList>();
        ArrayList ticket3 = new ArrayList<ArrayList>();
        ArrayList ticket4 = new ArrayList<ArrayList>();
        ArrayList ticket5 = new ArrayList<ArrayList>();
        ticket1.add("JFK");
        ticket1.add("SFO");
        ticket2.add("JFK");
        ticket2.add("ATL");
        ticket3.add("SFO");
        ticket3.add("ATL");
        ticket4.add("ATL");
        ticket4.add("JFK");
        ticket5.add("ATL");
        ticket5.add("SFO");
        tickets.add(ticket1);
        tickets.add(ticket2);
        tickets.add(ticket3);
        tickets.add(ticket4);
        tickets.add(ticket5);
        System.out.println(tickets);
        System.out.println(new FindItinerary2().findItinerary(tickets));
    }
}

在这里插入图片描述
在这里插入图片描述

总之思路是对的。最后看答案,使用map解法。

package com.programmercarl.backtracking;

import java.util.*;

/**
 * @ClassName FindItinerary
 * @Descriotion TODO
 * @Author nitaotao
 * @Date 2022/7/11 21:14
 * @Version 3.0
 * https://leetcode.cn/problems/reconstruct-itinerary/
 * 332. 重新安排行程
 **/
public class FindItinerary3 {
    Stack<String> stack;
    // 出发机场   到达机场  航班次数
    //通过航班次数来判断当前到达机场是否使用过了
    Map<String, Map<String, Integer>> map;

    public List<String> findItinerary(List<List<String>> tickets) {
        // 出发机场   到达机场  航班次数
        map = new HashMap<String, Map<String, Integer>>();
        stack = new Stack<>();
        for (List<String> ticket : tickets) {
            // 目的机场,班次数量
            Map<String, Integer> temp;
            if (map.containsKey(ticket.get(0))) {
                //如果map包含当前出发机场
                temp = map.get(ticket.get(0));
                //则当前出发机场的目的机场的班次+1
                //如果没有这个 ticket.get(1) 的目的机场,就添加一个,默认是0+1
                // 如果有,就获取其值,然后+1,再更新
                temp.put(ticket.get(1), temp.getOrDefault(ticket.get(1), 0) + 1);
            } else {
                //如果没有这个出发机场
                temp = new TreeMap<>();
                //就添加这个map,把目的机场信息加上
                temp.put(ticket.get(1), 1);
            }
            // map 键:出发机场  值 :目标机场集
            //   如  key   JFK     值   TreeMap    值以 treemap存储,自动升序
            //                          ATL      1
            //                          SFO      1
            //更新出发机场的map
            map.put(ticket.get(0), temp);
        }
        //初始机场
        stack.push("JFK");
        backtracking(tickets.size());
        return new ArrayList(stack);
    }


    /**
     * @param ticketNum 机票数量
     * @return
     */
    public boolean backtracking(int ticketNum) {
        // stack有六个元素时,说明用了五个机票
        if (stack.size() == ticketNum + 1) {
            //如果已使用票数和一共需要用的票数相等,则返回
            return true;
        }
        //获取队尾元素
        String last = stack.peek();
        //防止出现null
        if (map.containsKey(last)) {
            for (Map.Entry<String, Integer> target : map.get(last).entrySet()) {
                //获取 出发地为 last 的 所有 目的地 机票及数量
                //机票数量
                int count = target.getValue();
                //如果机票还有
                if (count > 0) {
                    //如果还有航班次数
                    //使用当前机票
                    stack.push(target.getKey());
                    //可使用航班次数 - 1
                    target.setValue(count - 1);
                    //递归
                    //如果找到一条路径,因为 treemap 默认最优解,直接返回
                    boolean isEnd = backtracking(ticketNum);
                    if (isEnd) {
                        return true;
                    }
                    //回溯
                    stack.pop();
                    target.setValue(count);
                }
            }
        }
        return false;
    }
    public static void main(String[] args) {
        List<List<String>> tickets = new ArrayList<List<String>>();
        ArrayList ticket1 = new ArrayList<ArrayList>();
        ArrayList ticket2 = new ArrayList<ArrayList>();
        ArrayList ticket3 = new ArrayList<ArrayList>();
        ArrayList ticket4 = new ArrayList<ArrayList>();
        ArrayList ticket5 = new ArrayList<ArrayList>();
        ticket1.add("JFK");
        ticket1.add("SFO");
        ticket2.add("JFK");
        ticket2.add("ATL");
        ticket3.add("SFO");
        ticket3.add("ATL");
        ticket4.add("ATL");
        ticket4.add("JFK");
        ticket5.add("ATL");
        ticket5.add("SFO");
        tickets.add(ticket1);
        tickets.add(ticket2);
        tickets.add(ticket3);
        tickets.add(ticket4);
        tickets.add(ticket5);
        System.out.println(tickets);
        System.out.println(new FindItinerary3().findItinerary(tickets));
    }
}

在这里插入图片描述

经过我的潜心研究,终于懂了,代码删了自己根据思路复现了一遍。

package com.programmercarl.backtracking;

import java.util.*;

/**
 * @ClassName FindItinerary4
 * @Descriotion TODO
 * @Author nitaotao
 * @Date 2022/7/12 15:19
 * @Version 4.0
 * https://leetcode.cn/problems/reconstruct-itinerary/
 * 332. 重新安排行程
 **/
public class FindItinerary4 {
    //当前使用的票
    Stack<String> stack;
    //出发机场  目的机场集合{[目的机场1,机票数量1],[目的机场2,机票数量1]}
    Map<String, Map<String, Integer>> map;

    public List<String> findItinerary(List<List<String>> tickets) {
        stack = new Stack<>();
        map = new HashMap<String, Map<String, Integer>>();
        //遍历每一张机票
        for (List<String> ticket : tickets) {
            Map<String, Integer> curStart;
            //如果当前map中包含该 起始地 为键的 集合
            if (map.containsKey(ticket.get(0))) {
                curStart = map.get(ticket.get(0));
                //更新 当前 起始地 的 目标地 的机票数量,有则+1,无则为1
                curStart.put(ticket.get(1), curStart.getOrDefault(ticket.get(1), 0) + 1);
            } else {
                //保证其中元素按键升序存储
                curStart = new TreeMap<String, Integer>();
                curStart.put(ticket.get(1), 1);
            }
            //更新当前map中 当前 起始地 的 目标地 集合的数据
            map.put(ticket.get(0), curStart);
        }
        //以 JFK 为起始地
        stack.push("JFK");
        //获取机票结果集
        backtracking(tickets.size());
        return new ArrayList(stack);
    }

    /**
     * @param ticketsNum 需要用到的机票的数量
     * @return
     */
    public boolean backtracking(int ticketsNum) {
        // 五个城市 需要 四 个机票连接在一起
        if (stack.size() == ticketsNum + 1) {
            return true;
        }
        //起始地
        String startArea = stack.peek();
        //防止空指针
        if (map.containsKey(startArea)) {
            //获取 当前 出发地 的 所有 目的地 的机票 和 相关 剩余票数
            for (Map.Entry<String, Integer> ticket : map.get(startArea).entrySet()) {
                //当前目的地 的 剩余机票数量
                int remainSum = ticket.getValue();
                if (remainSum > 0) {
                    //当 本目的地 的 机票数量 > 0
                    //机票数量 - 1
                    ticket.setValue(remainSum - 1);
                    stack.push(ticket.getKey());
                    boolean isFind = backtracking(ticketsNum);
                    if (isFind) {
                        //如果找到一条路,就返回,因为treemap自动排序,是最优解
                        return true;
                    }
                    //回溯
                    stack.pop();
                    ticket.setValue(remainSum);
                }
            }
        }
        //此路不通,重新找最优路线
        return false;
    }


    public static void main(String[] args) {
        List<List<String>> tickets = new ArrayList<List<String>>();
        ArrayList ticket1 = new ArrayList<ArrayList>();
        ArrayList ticket2 = new ArrayList<ArrayList>();
        ArrayList ticket3 = new ArrayList<ArrayList>();
        ArrayList ticket4 = new ArrayList<ArrayList>();
        ArrayList ticket5 = new ArrayList<ArrayList>();
        ticket1.add("JFK");
        ticket1.add("SFO");
        ticket2.add("JFK");
        ticket2.add("ATL");
        ticket3.add("SFO");
        ticket3.add("ATL");
        ticket4.add("ATL");
        ticket4.add("JFK");
        ticket5.add("ATL");
        ticket5.add("SFO");
        tickets.add(ticket1);
        tickets.add(ticket2);
        tickets.add(ticket3);
        tickets.add(ticket4);
        tickets.add(ticket5);
        System.out.println(tickets);
        System.out.println(new FindItinerary4().findItinerary(tickets));
    }
}

在这里插入图片描述

Ctrl + C Ctrl + V AC !!! over … next !!!
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值