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 !!!