今日内容
- Leetcode. 491 递增子序列
- Leetcode. 46 全排列
- Leetcode. 47 全排列Ⅱ
Leetcode. 491 递增子序列
文章链接:代码随想录 (programmercarl.com)
题目链接:491. 非递减子序列 - 力扣(LeetCode)
本题也是一个子集问题,根据题意也知道要去重,提到去重就会不自觉地想到 90. 子集 II - 力扣(LeetCode)中的去重。但在本题中不能使用 Leetcode. 90 这种去重思想,因为这种去重需要对数组进行排序,而本题如果进行排序,会对得到的结果产生影响。
所以本题的去重应该用一个 Set 来记录本层已经使用过的元素。
根据上述内容,可写出代码:
class Solution {
List<Integer> elem = new LinkedList<>();
List<List<Integer>> result = new LinkedList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
backtracking(nums, 0);
return result;
}
public void backtracking(int[] nums, int startIndex){
if (elem.size() >= 2){
result.add(new LinkedList<>(elem));
}
if (startIndex == nums.length){
return;
}
Set<Integer> used = new HashSet<>(); // 记录已使用元素的Set
for (int i = startIndex; i < nums.length; i++){
if (!elem.isEmpty() && nums[i] < elem.get(elem.size() - 1) || used.contains(nums[i])){continue;}
used.add(nums[i]);
elem.add(nums[i]);
backtracking(nums, i + 1);
elem.remove(elem.size() - 1);
}
}
}
- 时间复杂度:O(n * 2 ^ n)
- 空间复杂度:O(n)
Leetcode. 46 全排列
文章链接:代码随想录 (programmercarl.com)
题目链接:46. 全排列 - 力扣(LeetCode)
本题是回溯问题中的排列问题, 排列问题和组合问题不同,排列问题返回得结果是有序的,比如{1, 2} {2, 1} 在排列问题中是两个不同的结果。
也正因为结果有序,所以和组合问题、分割问题以及子集问题不同,排列问题每次搜索都要从头开始,所以不需要startIndex指示下一次搜索开始的位置。
虽然排列问题不需要startIndex了,但是它返回的结果是有序的,元素的排列顺序依然需要其他变量进行标识。因此需要使用一个数组来记录每个元素的使用情况。
本题中,我们使用一个布尔数组 used 来标识每个元素的使用情况,当本层遍历使用到该元素后,该元素在used数组中的值被赋为 true。在遍历元素时,就可以根据 used 数组中对应索引的值来判断是否被使用,被使用就跳过。
排列问题经过抽象得到的树形结构图如下所示:
根据上述内容,写出如下代码:
class Solution {
List<Integer> elem = new LinkedList<>();
List<List<Integer>> result = new LinkedList<>();
public List<List<Integer>> permute(int[] nums) {
boolean[] used = new boolean[nums.length]; // 记录元素的使用情况
backtracking(nums, used);
return result;
}
public void backtracking(int[] nums, boolean[] used){
if (elem.size() == nums.length){
result.add(new LinkedList<>(elem));
return;
}
for (int i = 0; i < nums.length; i++){
if (used[i]){continue;} // 若元素已使用过,则跳过该元素
elem.add(nums[i]);
used[i] = true; // 元素被使用,赋值为true
backtracking(nums, used);
used[i] = false; // 回溯
elem.remove(elem.size() - 1);
}
}
}
- 时间复杂度:O(n!)
- 空间复杂度:O(n)
Leetcode. 47 全排列Ⅱ
文章链接:代码随想录 (programmercarl.com)
题目链接:47. 全排列 II - 力扣(LeetCode)
本题可以看作是 Leetcode. 46 + Leetcode. 90 的结合,也就是在全排列问题中加入去重。
代码如下:
class Solution {
List<Integer> elem = new LinkedList<>();
List<List<Integer>> result = new LinkedList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
boolean[] used = new boolean[nums.length];
Arrays.sort(nums); // 注意一定要排序
backtracking(nums, used);
return result;
}
public void backtracking(int[] nums, boolean[] used){
if (elem.size() == nums.length){
result.add(new LinkedList<>(elem));
return;
}
for (int i = 0; i < nums.length; i++){
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true){ // 判断元素是否被使用以及去重
continue;
}
if (used[i]){continue;}
elem.add(nums[i]);
used[i] = true;
backtracking(nums, used);
used[i] = false;
elem.remove(elem.size() - 1);
}
}
}
- 时间复杂度:O(n! * n)
- 空间复杂度:O(n)
总结
第一题是去重的不同,去重时注意所给数据是否有序,有序的话就可以使用 Leetcode. 90 的去重思想;无序的话就需要其他容器记录已使用元素。
排列问题要求结果有序,所以每次遍历都需要从头开始,并且需要一个容器记录哪些元素被使用过,注意这里的标识不是为了去重,而是为了调整结果顺序。
去重和判断元素使用情况的区别,从Leetcode. 47 就可以看出来了。