算法:
在90.子集II (opens new window)中我们是通过排序,再去重来达到去重的目的。
而本题求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了。
肯定还是回溯算法。
画树:
树里面其实有两个注意点:
(1)每个子集中,所取元素应该大于等于前一个元素
(2)同一层树下,不能取重复的元素来制作子集
回溯三部曲:
1.确定返回值和参数
返回值:void
参数:
int[] nums(题目给出)
int startIndex:需要startIndex,调整下一层递归的起始位置。
2.确定终止条件
本题其实类似求子集问题,也是要遍历树形结构找每一个节点,所以和回溯算法:求子集问题! (opens new window)一样,可以不加终止条件(求子集时为了便于理解,终止条件是startindex >= nums.length,但其实这个终止条件可以不加),startIndex每次都会加1,并不会无限递归。
但本题收集结果有所不同,题目要求递增子序列大小至少为2。
if (path.length > 1) {result.add(path);// 注意这里不要加return,因为要取树上的所有节点
}
3.单层递归逻辑
其实就是注意点,当出现以下情况时,continue,跳出for循环
(1)每个子集中,所取元素应该大于等于前一个元素
(2)同一层树下,不能取重复的元素来制作子集
具体实现时,要用一个数组来进行去重操作,题目说数值范围[-100, 100]
将用过的值插入数组,表示这个元素在本层用过了,本层后面不能再用了
(为什么用数组?
若用集合,程序运行的时候对集合频繁的insert,集合需要做哈希映射(也就是把key通过hash function映射为唯一的哈希值)相对费时间,而且每次重新定义set,insert的时候其底层的符号表也要做相应的扩充,也是费事的。)
把nums[i]加入path
递归
回溯:把刚刚加入的nums[i]弹出
调试过程
第一次调试:
class Solution {//两个全局变量List<List<Integer>> result = new LinkedList<>();List<Integer> path = new LinkedList<>();public List<List<Integer>> findSubsequences(int[] nums) {backtracking(nums, 0);return result;}void backtracking (int[] nums, int startindex) {//确定终止条件,收集结果if (startindex >= nums.length) {result.add (new LinkedList(path));return;}//单层递归逻辑for (int i=startindex; i <= nums.length; i++){//题目中说:-100 <= nums[i] <= 100,说明nums的最大长度为201//为了确保所有数字都能被记录,去重数组的长度被设置为 201int[] used = new int[201];/*跳出循环的条件:(1)nums[i]<当前path中的最后一个值,前提是path非空注意:这里不是nums[i]<nums[i-1],因为nums本身无序,而path有序(2)nums[i]的值和该层前面的值重复了:每用过一次,就让该值在used中对应索引的位置(used[nums[i]+100]==1)为1,因为-100 <= nums[i] <= 100,为了得到合法的索引(>=0),used中nums对应的索引为used[nums[i]+100]若在循环中发现used中该值为1,说明重复了*/if (!path.emphty() && nums[i]<path.get(path.size()-1) || used[nums[i]+100]==1) continue;used[nums[i]+100]=1;//标记用过的值path.add(nums[i]);//递归backtracking(nums, i+1);//回溯path.removeLast();}}
}
原因:
java中判断(is)非空(Empty)的函数为:isEmpty()
isEmpty()
我写得不对。
另外,used数组是记录每层是否使用重复元素的,应该放在for循环外面
第二次调试:
原因:
问题在终止条件上,索引是从0-nums.length-1,
所以for循环终止条件应该为 i < nums.length
另外,
收集结果时不要return,因为要取树上的所有节点,而不是叶子节点
正确代码:
class Solution {//两个全局变量List<List<Integer>> result = new LinkedList<>();List<Integer> path = new LinkedList<>();public List<List<Integer>> findSubsequences(int[] nums) {backtracking(nums, 0);return result;}void backtracking (int[] nums, int startindex) {//确定终止条件,收集结果if (path.size() > 1) {result.add (new LinkedList(path));}//单层递归逻辑//题目中说:-100 <= nums[i] <= 100,说明nums的最大长度为201//为了确保所有数字都能被记录,去重数组的长度被设置为 201int[] used = new int[201];for (int i=startindex; i < nums.length; i++){/*跳出循环的条件:(1)nums[i]<当前path中的最后一个值,前提是path非空注意:这里不是nums[i]<nums[i-1],因为nums本身无序,而path有序(2)nums[i]的值和该层前面的值重复了:每用过一次,就让该值在used中对应索引的位置(used[nums[i]+100]==1)为1,因为-100 <= nums[i] <= 100,为了得到合法的索引(>=0),used中nums对应的索引为used[nums[i]+100]若在循环中发现used中该值为1,说明重复了*/if (!path.isEmpty() && nums[i] < path.get(path.size()-1) || used[nums[i]+100]==1) continue;used[nums[i]+100]=1;//标记用过的值path.add(nums[i]);//递归backtracking(nums, i+1);//回溯path.removeLast();}}
}