系列文章目录
目录
- 系列文章目录
- 491.递增子序列
- 回溯法
- 使用 `HashSet` 作为哈希表进行树层去重
- 使用 数组 作为哈希表进行树层去重(最快)
- 使用 `HashMap` 作为哈希表进行树层去重
- 46.全排列
- 回溯法
- 使用`used`数组,标记已经选择的元素
- 直接通过`LinkedList`的`contains`方法判断`path`中是否有该元素
- 47.全排列 II
- 回溯法
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(负数)映射到数组坐标0used[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();//回溯}}
}