学习回溯法
follow:代码随想录
46 全排列
题目描述:
给定一个不含重复数字的数组 nums ,返回其所有可能的全排列 。你可以按任意顺序返回答案。
示例:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
解析:
排列是有序的,[1,2]和[2,1]是两个集合。
这道题的重点就是:如何记录已经排列的元素。
- 使用used额外数组,记录目前那个元素已经排列
- 将所有以排列好的元素,移动到数组的前面,下一次递归时,跳过已排列好的元素
方法二:
List<List<Integer>> res = new ArrayList();
List<Integer> element = new ArrayList();
public List<List<Integer>> permute(int[] nums) {
backTracking(nums, 0);
return res;
}
public void backTracking(int[] nums, int now_index){
if(now_index == nums.length){
res.add(new ArrayList<>(element));
return;
}
for(int i = now_index; i < nums.length; i++){
int temp = 0;
element.add(nums[i]);
if(i != now_index){
temp = nums[i];
nums[i] = nums[now_index];
nums[now_index] = temp;
}
backTracking(nums, now_index + 1);
if(i != now_index){
temp = nums[i];
nums[i] = nums[now_index];
nums[now_index] = temp;
}
element.remove(element.size() - 1);
}
}
方法一:
List<List<Integer>> res = new ArrayList();
List<Integer> element = new ArrayList();
boolean[] used;
public List<List<Integer>> permute(int[] nums) {
used = new boolean[nums.length];
backTracking(nums, 0);
return res;
}
public void backTracking(int[] nums, int now_index){
if(now_index == nums.length){
res.add(new ArrayList<>(element));
return;
}
for(int i = 0; i < nums.length; i++){
if(used[i]) continue;
element.add(nums[i]);
used[i] = true;
backTracking(nums, now_index + 1);
element.remove(element.size() - 1);
used[i] = false;
}
}
47. 全排列2⃣️
题目描述:
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
解析:
该题的关键就是去重,这个去重需要两点,分别是:同一树枝去重和同一层去重
解答:
List<List<Integer>> res = new ArrayList();
List<Integer> element = new ArrayList();
//used标志用来记录当前已遍历的数组中的元素:同一个树枝使用过
boolean[] used;
public List<List<Integer>> permuteUnique(int[] nums) {
used = new boolean[nums.length];
backTracking(nums, 0);
return res;
}
public void backTracking(int[] nums, int now_index){
if(now_index == nums.length){
res.add(new ArrayList<>(element));
return;
}
//flag标志用来记录重复的元素:同一层使用过
boolean[] flag = new boolean[21];
for(int i = 0; i < nums.length; i++){
if(used[i] || flag[nums[i] + 10]) continue;
element.add(nums[i]);
used[i] = true;
flag[nums[i] + 10] = true;
backTracking(nums, now_index + 1);
element.remove(element.size() - 1);
used[i] = false;
}
}
332 重新安排行程
给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。
所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
例如,行程 [“JFK”, “LGA”] 与 [“JFK”, “LGB”] 相比就更小,排序更靠前。
假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
解答:
//保存结果集
List<String> res = new ArrayList<>();
//首先使用一个数据结构可以存储tickets信息
//为了提前剪枝,需要记录的信息有:该出发机场的目的机场有哪些、这些目的机场按照字典顺序进行排序、从该出发机场到目的机场是否已经遍历到(若有多张票,即为该出发机场到目的机场还剩多少张票)
Map<String,Map<String, Integer>> record = new HashMap<>();
public List<String> findItinerary(List<List<String>> tickets) {
//TreeMap是一个能比较元素大小的Map集合,会对传入的key进行了大小排序
for(List<String> ticket : tickets){
Map<String, Integer> temp = null;
//record中有出发机场
if(record.containsKey(ticket.get(0))){
temp = record.get(ticket.get(0));
temp.put(ticket.get(1), temp.getOrDefault(ticket.get(1), 0) + 1);
}else{
//record中没有出发机场
//使用TreeMap,针对该出发机场的目的机场按字典顺序进行排序
temp = new TreeMap<>();
temp.put(ticket.get(1), 1);
}
record.put(ticket.get(0), temp);
}
//进行回溯
res.add("JFK");
backTracking(tickets.size(), "JFK");
return res;
}
public boolean backTracking(int nums, String fromStation){
//找到一个叶节点就是结果,可以直接返回结果
if(res.size() == nums + 1) return true;
//可能该map中不包括fromStation,则直接返回
if(record.containsKey(fromStation)){
for(Map.Entry<String, Integer> entry : record.get(fromStation).entrySet()){
//目的地点已经按照字典排好序,只需要找到叶子节点,就可以得到结果
//首先看该目的地还有没有票
int count = entry.getValue();
if(count > 0){
entry.setValue(count - 1);
res.add(entry.getKey());
if(backTracking(nums, entry.getKey())) return true;
entry.setValue(count);
res.remove(res.size() - 1);
}
}
}
return false;
}