随想录训练营Day29 | 回溯 491.递增子序列, 46. 全排列, 47. 全排列2
标签: LeetCode闯关记
491.递增子序列
错解: 直接用的Boolean[] used 来判断,甚至没有sort
if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true
|| path.size() != 0 && nums[i] < path.getLast()){
continue;
}
path.add(nums[i]);
used[i] = true;
此题注意点
- 思路: 去重(数层去重但要求不改变集合排列的顺序, cf:90.子集II) ---- 巧妙利用int[] used 数组+ 判断是否递增
- 题目的重点: 集合中有重复元素, 找子序列即不改变排列顺序
class Solution {
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
find(nums, 0);
return res;
}
private void find(int[] nums, int startIndex){
if(path.size() > 1){//注意res.add和重构used在for循环之前
res.add(new ArrayList<>(path));
}
int[] used = new int[201];//used[nums[i] + 100] 的作用是记录 nums[i] 是否已经被使用过。由于题目中给定了 -100 <= nums[i] <= 100,所以为了防止数组下标为负数,要将 nums[i] 的值加上 100 后再存储到 used 数组中。因此,当 used[nums[i] + 100] == 1 时,表示 nums[i] 已经被使用过,可以跳过当前循环。
for (int i = startIndex; i < nums.length; i++) {
if(i > 0 && used[nums[i] + 100] == 1 || !path.isEmpty() && nums[i] < path.getLast()){//注意i > 0 && used[nums[i] + 100] == 1 中不能将i > 0改为!path.isEmpty();因为当path为空时,即还没开始取第一个数放入path中时,但是i > 0
//时,仍然需要比较同层的值是否被使用过
continue;
}
used[nums[i] + 100] = 1;//注意思考used的作用域, 在每一次for循环记录,所以每次for循环之前会重新初始化,在for循环中不会被删除;
path.add(nums[i]);
find(nums, i + 1);
path.removeLast();
}
}
}
用时: 1h12min
46.全排列
重点: 用used数组来区别是否在同一根树枝上已经使用; for循环 i 的起始位置为0; 区别: 无重复元素---- means 无需数层去重
class Solution {
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> permute(int[] nums) {
boolean[] used = new boolean[nums.length];//key 类比startIndex的作用,但有区别
Arrays.fill(used, false);
findPermute(nums,used);
return res;
}
private void findPermute(int[] nums, boolean[] used){
if(path.size() == nums.length){
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {//key: i = 0 instead of i = startIndex;
if(used[i] == true){
continue;
}
path.add(nums[i]);
used[i] = true;
findPermute(nums,used);
path.removeLast();
used[i] = false;
}
}
}
用时: 20min
47.全排列 II
思路: 在46.全排列 的基础上,加上数层去重(去重思路见491.递增子序列, 用 int[] used_breadth 记录被使用过的数值, 但是做复杂了,因为此题的集合中的排列顺序可以被打乱,既可以用40.组合总和II、90.子集II 的sort排序 套路 ) 个人AC码:
class Solution {
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
boolean[] used_deep =new boolean[nums.length];
Arrays.fill(used_deep,false);
findUnique(nums, used_deep);
return res;
}
private void findUnique(int[] nums, boolean[] used_deep){
int[] used_breadth = new int[21];
if(path.size() == nums.length){
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if(used_deep[i] == true ){
continue;
}
if(i > 0 && used_breadth[nums[i] + 10] == 1){/*犯错误地方: 写成了 if(!path.isEmpty() && used_breadth[nums[i] + 10] == 1){continue; } //注意i > 0 && used[nums[i] + 10] == 1 中不能将i > 0改为!path.isEmpty();因为当path为空时,即还没开始取第一个数放入path中时,但是i > 0
时,仍然需要比较同层的值是否被使用过, 会造成测试用例:[1,1,2]
测试结果:[[1,1,2],[1,2,1],[1,1,2],[1,2,1],[2,1,1],[2,1,1]]
期望结果:[[1,1,2],[1,2,1],[2,1,1]],"1"被重复取*/
continue;
}
path.add(nums[i]);
used_breadth[nums[i] + 10] = 1;
used_deep[i] = true;
findUnique(nums, used_deep);
used_deep[i] = false;
path.removeLast();
}
}
}
待看随想录的优化思路......
用时: 50min