系列文章目录
目录
491.递增子序列
- 本题类似求子集问题,也是要遍历树形结构找每一个节点,所以可以不加终止条件,
startIndex
每次都会加1
,并不会无限递归。 - 去重逻辑:在【90.子集II 】中是通过排序,再加一个标记数组
uesd
/利用startIndex
/双指针来达到去重的目的。而本题求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了。所以不能使用之前的去重逻辑!。使用哈希表来记录同层出现过的元素,注意,新的一层哈希表都会重新定义(清空),所以要知道哈希表只负责本层!
回溯法
在判断是否递增来决定是否加入path
,不递增直接跳过时,自己写的如下:
if (path.size() == 0) {
path.add(nums[i]);
} else {
if (nums[i] >= path.getLast()) {
path.add(nums[i]);
} else {
continue;
}
}
可以简化为如下代码(过关斩将:找出不符合的条件直接跳过):
//过关斩将法
if (/*path.size() > 0*/!path.isEmpty() && nums[i] < path.get(path.size() - 1)) {
continue;
}
path.add(nums[i]);
使用 HashSet
作为哈希表进行树层去重
- 程序运行的时候对
HashSet
频繁的add
,HashSet
需要做哈希映射(也就是把key
通过hash(key)
映射为唯一的哈希值)相对费时间,而且每次重新定义set
,add
的时候其底层的符号表也要做相应的扩充,也是费事的。 - 可以对空的
HashSet
调用contains
方法而不会引发异常。当尝试在空集合中查找元素时,contains 方法会直接返回false
,因为空集合中不包含任何元素。 - 调用
LinkedList
的getLast
方法时,链表不能为空,否则会报NoSuchElementException
异常。
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
class Solution {
List<Integer> path = new LinkedList<>();// 存放符合条件结果的集合
List<List<Integer>> res = new LinkedList<>();// 用来存放符合条件结果
public List<List<Integer>> findSubsequences(int[] nums) {
backTracking(nums, 0);
return res;
}
public void backTracking(int[] nums, int startIndex) {
if (startIndex == nums.length) {//可以不写
return;
}
HashSet set = new HashSet();
for (int i = startIndex; i < nums.length; i++) {
//树层去重
if (/*!set.isEmpty() &&*/ set.contains(nums[i])) {
continue;
}
//根据是否递增来决定是否加入,不递增直接跳过
/*if (path.size() == 0) {
path.add(nums[i]);
} else {
if (nums[i] >= path.getLast()) {
path.add(nums[i]);
} else {
continue;
}
}*/
//过关斩将法
if (/*path.size() > 0*/!path.isEmpty() && nums[i] < path.get(path.size() - 1)) {
continue;
}
path.add(nums[i]);
set.add(nums[i]);
if (path.size() > 1) {
res.add(new LinkedList<Integer>(path));
}
backTracking(nums, i + 1);
path.removeLast();
}
}
}
使用 数组 作为哈希表进行树层去重(最快)
题目中说数值范围[-100,100]
,所以完全可以用数组来做哈希。注意,做映射时需要+100
,以使负数(如-100
)能够映射到数组的下标(0
)。
import java.util.LinkedList;
class Solution {
List<Integer> path = new LinkedList<>();// 存放符合条件结果的集合
List<List<Integer>> res = new LinkedList<>();// 用来存放符合条件结果
public List<List<Integer>> findSubsequences(int[] nums) {
backTracking(nums, 0);
return res;
}
public void backTracking(int[] nums, int startIndex) {
if (startIndex == nums.length) {//可以不写
return;
}
// 用于记录当前层遍历过的子节点(注意:不能定义为全局变量,因为递归的时候会加入其它层的节点)
int[] used = new int[210];
for (int i = startIndex; i < nums.length; i++) {
//不递增或者元素已经使用过
if (/*path.size() > 0 */!path.isEmpty() && nums[i] < path.getLast() || used[nums[i] + 100] /*!= 0*/ == 1) {
continue;
}
path.add(nums[i]);
//+100是为了将-100(负数)映射到数组坐标0
used[nums[i] + 100] = 1;
if (path.size() > 1) {
res.add(new LinkedList<>(path));
}
backTracking(nums, i + 1);
path.removeLast();
}
}
}
使用 HashMap
作为哈希表进行树层去重
- 在树层去重时:用
HashMap
的get
方法得到指定键的的value
,如果不包含所指定键,则返回null
,无法与0
比较,会报错。用getOrDefault
得到指定键的的value
,如果不包含所指定键,则返回默认值0
,可以与0
比较。
import java.util.HashMap;
import java.util.LinkedList;
class Solution {
List<Integer> path = new LinkedList<>();// 存放符合条件结果的集合
List<List<Integer>> res = new LinkedList<>();// 用来存放符合条件结果
public List<List<Integer>> findSubsequences(int[] nums) {
backTracking(nums, 0);
return res;
}
public void backTracking(int[] nums, int startIndex) {
if (startIndex == nums.length) {//可以不写
return;
}
// 用于记录当前层遍历过的子节点(注意:不能定义为全局变量,因为递归的时候会加入其它层的节点)
HashMap<Integer,Integer> map = new HashMap<>();
for (int i = startIndex; i < nums.length; i++) {
//不递增或者元素已经使用过
if (/*path.size() > 0 */!path.isEmpty() && nums[i] < path.getLast() || (Integer)map.getOrDefault(nums[i],0)>0) {
continue;
}
path.add(nums[i]);
map.put(nums[i],map.getOrDefault(nums[i],0)+1);
if (path.size() > 1) {
res.add(new LinkedList<>(path));
}
backTracking(nums, i + 1);
path.removeLast();
}
}
}
46.全排列
回溯法
一个排列里一个元素只能使用一次。
使用used
数组,标记已经选择的元素
used
数组记录此时path
里都有哪些元素使用了。
import java.util.Arrays;
import java.util.LinkedList;
class Solution {
List<Integer> path = new LinkedList<>();
List<List<Integer>> res = new LinkedList<>();
boolean[] used;
public List<List<Integer>> permute(int[] nums) {
used = new boolean[nums.length];
Arrays.fill(used, false);
backTracking(nums);
return res;
}
public void backTracking(int[] nums) {
// 终止条件(如果路径长度和数组长度一样,证明已经排列完毕,将路径记录到res中)
if (path.size() == nums.length) {
res.add(new LinkedList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) {//如果元素已经使用过,跳过
continue;
}
path.add(nums[i]);
used[i] = true;
backTracking(nums);
used[i] = false;
path.remove(path.size() - 1);
}
}
}
直接通过LinkedList
的contains
方法判断path
中是否有该元素
import java.util.Arrays;
import java.util.LinkedList;
class Solution {
List<Integer> path = new LinkedList<>();
List<List<Integer>> res = new LinkedList<>();
public List<List<Integer>> permute(int[] nums) {
backTracking(nums);
return res;
}
public void backTracking(int[] nums) {
if (path.size() == nums.length) {
res.add(new LinkedList(path));
return;
}
for (int i = 0; i < nums.length; i++) {
// 如果path中已有,则跳过
if (path.contains(nums[i])) {
continue;
}
path.add(nums[i]);
backTracking(nums);
path.remove(path.size() - 1);
}
}
}
47.全排列 II
- 和【46.全排列】的区别在于,数组中的元素是可以重复的。
- 需要树层去重且数枝不重复使用同一元素。
回溯法
import java.util.LinkedList;
import java.util.Arrays;
class Solution {
List<Integer> path = new LinkedList<>();
List<List<Integer>> res = new LinkedList<>();
boolean[] used;
public List<List<Integer>> permuteUnique(int[] nums) {
used = new boolean[nums.length];
Arrays.fill(used,false);
Arrays.sort(nums);//先排序,后面好树层去重
backTracking(nums);
return res;
}
public void backTracking(int[] nums) {
if (path.size() == nums.length) {
res.add(new LinkedList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
//树层去重, 如果同⼀树层nums[i - 1]使⽤过则直接跳过
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
continue;
}
//如果path中已使用当前元素,跳过
if (used[i]) {
continue;
}
//如果同⼀树⽀nums[i]没使⽤过开始处理
used[i] = true;//标记同⼀树⽀nums[i]使⽤过,防止同一树枝重复使用
path.add(nums[i]);
backTracking(nums);
used[i] = false;//回溯
path.removeLast();//回溯
}
}
}