目录
46.全排序
47.全排列||
332.重新安排行程
46.全排序
46. 全排列
中等
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1] 输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1] 输出:[[1]]
提示:
1 <= nums.length <= 6
-10 <= nums[i] <= 10
nums
中的所有整数 互不相同
通过全局变量used数组进行树枝去重
class Solution {
// 存储所有可能的排列结果的列表
List<List<Integer>> result = new ArrayList<>();
// 存储当前正在构建的排列的列表
List<Integer> path = new ArrayList<>();
// 标记数组,用于记录数字是否已经被使用
int[] used;
// 外部接口方法,用于获取数字数组的所有排列
public List<List<Integer>> permute(int[] nums) {
// 初始化used数组,大小为21,因为题目中暗示了nums中的元素范围为[0, 10],所以偏移10后使用
used = new int[nums.length];
// 开始回溯过程
backtracking(nums);
// 返回所有可能的排列结果
return result;
}
// 回溯方法,用于生成排列
public void backtracking(int[] nums){
// 如果当前路径的长度等于nums的长度,说明一个排列已经生成完毕
if(path.size() == nums.length){
// 将当前路径添加到结果列表中
result.add(new ArrayList(path));
// 结束当前递归分支
return;
}
// 遍历nums数组中的每个元素
for(int i = 0; i < nums.length; i++){
// 如果当前元素已经被使用,则跳过
if(used[i] == 1){
continue;
}
// 将当前元素添加到路径中
path.add(nums[i]);
// 标记当前元素为已使用
used[i] = 1;
// 继续递归生成下一个位置的元素
backtracking(nums);
// 回溯,撤销选择,标记当前元素为未使用
used[i] = 0;
// 回溯,撤销选择,从路径中移除当前元素
path.removeLast();
}
}
}
47.全排列||
47. 全排列 II
中等
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2] 输出: [[1,1,2], [1,2,1], [2,1,1]]
示例 2:
输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
提示:
1 <= nums.length <= 8
-10 <= nums[i] <= 10
这里要用到树枝去重和树层去重,树枝去重使用used数组,和之前的方式一样,树层去重去
class Solution {
// 存储所有可能的排列结果的列表
List<List<Integer>> result = new ArrayList<>();
// 存储当前正在构建的排列的列表
List<Integer> path = new ArrayList<>();
// 标记数组,用于记录数字是否已经被使用
int[] used;
// 外部接口方法,用于获取数字数组的所有不重复排列
public List<List<Integer>> permuteUnique(int[] nums) {
// 初始化used数组,大小为nums的长度
used = new int[nums.length];
// 对nums数组进行排序,以便在回溯过程中跳过重复元素
Arrays.sort(nums);
// 开始回溯过程
backtracking(nums);
// 返回所有可能的排列结果
return result;
}
// 回溯方法,用于生成不重复排列
public void backtracking(int[] nums){
// 如果当前路径的长度等于nums的长度,说明一个排列已经生成完毕
if(path.size() == nums.length){
// 将当前路径添加到结果列表中
result.add(new ArrayList<>(path));
// 结束当前递归分支
return;
}
// 遍历nums数组中的每个元素
for(int i = 0; i < nums.length; i++){
// 如果当前元素和前一个元素相同,并且前一个元素未被使用,则跳过当前元素
// 这样可以避免生成包含重复元素的排列
if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == 0){
continue;
}
// 如果当前元素已经被使用,则跳过
if(used[i] == 1){
continue;
}
// 将当前元素添加到路径中
path.add(nums[i]);
// 标记当前元素为已使用
used[i] = 1;
// 继续递归生成下一个位置的元素
backtracking(nums);
// 回溯,撤销选择,标记当前元素为未使用
used[i] = 0;
// 回溯,撤销选择,从路径中移除当前元素
path.removeLast();
}
}
}
332.重新安排行程
332. 重新安排行程
困难
给你一份航线列表 tickets
,其中 tickets[i] = [fromi, toi]
表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。
所有这些机票都属于一个从 JFK
(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK
开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
- 例如,行程
["JFK", "LGA"]
与["JFK", "LGB"]
相比就更小,排序更靠前。
假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
示例 1:
输入:tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]] 输出:["JFK","MUC","LHR","SFO","SJC"]
示例 2:
输入:tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]] 输出:["JFK","ATL","JFK","SFO","ATL","SFO"] 解释:另一种有效的行程是 ["JFK","SFO","ATL","JFK","ATL","SFO"] ,但是它字典排序更大更靠后。
提示:
1 <= tickets.length <= 300
tickets[i].length == 2
fromi.length == 3
toi.length == 3
fromi
和toi
由大写英文字母组成fromi != toi
这里先对传入的集合排序是为了先访问到字典排序更小的元素,backtracking返回boolean是为了只找一个有效行程,先找到的那个有效行程一定是字典排序更小的 ,用used数组来进行树枝去重
class Solution {
// 结果列表,存储最终的旅行行程
private ArrayList<String> res;
// 当前构建的行程路径
private ArrayList<String> path = new ArrayList<>();
//去重
int[] used;
// 主方法,用于找到旅行行程
public List<String> findItinerary(List<List<String>> tickets) {
// 将tickets按照目的地(即第二个元素)进行排序
// 这样能够确保回溯时优先选择目的地字母顺序靠前的航班
Collections.sort(tickets, (a, b) -> a.get(1).compareTo(b.get(1)));
// 起点是"JFK"
path.add("JFK");
// 标记数组,用于记录机票是否已被使用
used = new int[tickets.size()];
// 开始回溯
backTracking(tickets);
// 返回最终构建的旅行行程
return res;
}
// 回溯方法,用于生成旅行行程
public boolean backTracking(List<List<String>> tickets) {
// 如果路径中的机场数量等于机票数量加1(因为起点"JFK"也算一个点)
// 则说明已经构建了一个完整的旅行行程
if (path.size() == tickets.size() + 1) {
// 将当前路径赋值给结果列表
res = new ArrayList<>(path);
// 找到了一个完整的行程,返回true
return true;
}
// 遍历所有机票
for (int i = 0; i < tickets.size(); i++) {
// 如果机票未被使用,且当前机票的起点与当前路径的最后一个机场相同
if (used[i] == 0 && tickets.get(i).get(0).equals(path.getLast())) {
// 将当前机票的终点添加到路径中
path.add(tickets.get(i).get(1));
// 标记当前机票为已使用
used[i] = 1;
// 继续回溯,寻找下一个机票
if (backTracking(tickets)) {
// 如果找到了一个完整的行程,则直接返回true
return true;
}
// 回溯,撤销选择
// 标记当前机票为未使用
used[i] = 0;
// 从路径中移除当前机票的终点
path.removeLast();
}
}
// 如果无法构建完整的行程,则返回false
return false;
}
}