代码随想录算法训练营第二十九天|491.递增子序列、46.全排列、47.全排列 II
491.递增子序列
问题简述:输出数组中的所有非递减子序列,数组中和序列中可能存在重复元素,但结果的序列集不能有重复的序列。
思考:其实回溯的题很多都很相似啊,模版化有点重。本质就是无法确定需要套几层for循环,只能用回溯。这道题区别在于,因为要找原数组的非递减序列,所以不能改变原数组序列顺序,但仍要去重。去重的本质就是同一层一个数字只能访问一次,同一层的结点就是每个for循环,每个分支就是递归,所以只需要在for前标记单层的数字访问情况,即可去重。
算法思路:
- 定义result存储所有组合,定义list存储当前序列。
- 递归函数中在for前先定义一个used数组,用来表示数组中数字
[-100,100]
的使用情况,如果数字被使用或者path不为空的同时,满足前一个数字大于当前数字,则跳过本次循环。没有跳过则将当前节点加入path,同时修改used值,再进行递归,最后回溯path和used数组。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> list = new ArrayList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
//标记使用
backtracking(nums, 0);
return result;
}
public void backtracking(int[] nums, int startIdx){
//标记单层的数字使用情况
int[] used = new int[201];
for (int i = startIdx; i < nums.length; i++) {
//去重
if (used[nums[i] + 100] == 1 || (!list.isEmpty() && nums[i] < list.get(list.size() - 1))) continue;
//加入当前元素
list.add(nums[i]);
used[nums[i] + 100] = 1;
//保留递归树中每个结点的值,即为每个子集
if (list.size() > 1) result.add(new ArrayList<>(list));
//递归i的下一个元素
backtracking(nums, i + 1);
//回溯
list.remove(list.size() - 1);
}
}
}
46.全排列
问题简述:获得无重复数组的所有排列。
思考:之前都是组合,每次递归都是从下一个位置startisx + 1
开始,但这次是排列,虽然也是在叶子结点取到值,但不需要每次传入startidx了。
算法思路:
-
定义path存储每个结果集;lists存储path的集合。定义used数组来标记元素的访问情况。
-
每次递归从先判断path中是否已经包含所有元素,如果是则加入lists。然后每次都遍历所有元素,如果元素被使用过,则跳过该元素;未被使用过则加入该元素,,并递归后进行回溯。
import java.util.ArrayList;
import java.util.List;
class Solution {
//定义返回值
List<List<Integer>> lists = new ArrayList<>();
List<Integer> path = new ArrayList<>();
int[] used;
public List<List<Integer>> permute(int[] nums) {
used = new int[nums.length];
backtracking(nums);
return lists;
}
//回溯函数
public void backtracking(int[] nums){
//path中元素个数和nums相等时保存path
if (path.size() == nums.length){
lists.add(new ArrayList<>(path));
return;
}
//每次都遍历所有结点,如果当前结点已经在path中则跳过
for (int i = 0; i < nums.length; i++) {
//如果当前结点已经被使用,说明其在path中则跳过
if (used[i] == 1) continue;
//将当前元素标记访问
used[i] = 1;
path.add(nums[i]);
//递归
backtracking(nums);
//回溯
path.remove(path.size() - 1);
used[i] = 0;
}
}
}
47.全排列 II
问题简述:求出有重复集合的所有排列。
思考:排列需要去重的问题。
算法思路:
-
定义path存储每个结果集;lists存储path的集合。定义used数组来标记元素的访问情况。
-
先对数组进行排序。
-
每次递归从先判断path中是否已经包含所有元素,如果是则加入lists。然后每次都遍历所有元素,如果元素被使用过,则跳过该元素;未被使用过则进去重,保证i大于0同时上一个元素未被使用(未在当前路径中)且当前元素与上一个元素相等,则跳过;否则加入该元素,并递归后进行回溯。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
//定义返回值
List<List<Integer>> lists = new ArrayList<>();
List<Integer> path = new ArrayList<>();
int[] used;
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
used = new int[nums.length];
backtracking(nums);
return lists;
}
//回溯函数
public void backtracking(int[] nums){
//path中元素个数和nums相等时保存path
if (path.size() == nums.length){
lists.add(new ArrayList<>(path));
return;
}
//每次都遍历所有结点,如果当前结点已经在path中则跳过
for (int i = 0; i < nums.length; i++) {
//去重
if (i > 0 && used[i - 1] == 0 && nums[i] == nums[i - 1]) continue;
//如果当前结点已经被使用,说明其在path中则跳过
if (used[i] == 1) continue;
//将当前元素标记访问
used[i] = 1;
path.add(nums[i]);
//递归
backtracking(nums);
//回溯
path.remove(path.size() - 1);
used[i] = 0;
}
}
}
感想
进行一定再完成一天的。