力扣刷题Day24

491.递增子序列

题目链接:力扣

这道题找递增子序列,且存在重复的情况,需要去重。看起来和子集II很像。

但问题是因为是要找递增子序列,因此不能先对这个数组进行排序,然后去重的。1.

这道题的树形结构如下:

 在图中可以看出,同一父节点下的同层上使用过的元素就不能再使用了 == 树层去重(在代码中是每一个for循环时存入的元素必须是不同的)

但是因为不能进行排序,那就是不能使用之前的去重方式。 - 在单层递归逻辑中使用hashset进行判断去重

回溯三部曲:

1.输出和输入参数:输出 - void; 输入-【两个全局变量 - List<List<Integer>> result;List<Integer> list】;backtracking(int[] nums, int startIndex);

2.终止条件:if(startIndex == nums.length) return;

3.单层循环逻辑:用hashset来储存使用过的元素,for(int i = startIndex; i<nums.length; i++) 如果list的size小于1,则先放入nums[i],并将其储存入hashset中; 若list.size大于1,且hashset中不存在nums[i]则将list.get(list.size()-1)与nums[i]比较,如果nums[i]大于等于list.get(list.size()-1)则将其放入list,并list让入result;backtracking(nums, startIndex+1); 回溯 - list.remove(list.size()-1)

具体代码逻辑:

class Solution {
    public List<List<Integer>> findSubsequences(int[] nums) {
        result = new ArrayList<>();
        list = new ArrayList<>();

        backtraking(nums, 0);

        return result;
    }

    List<List<Integer>> result;
    List<Integer> list;

    public void backtraking(int[] nums, int startIndex){
        if(startIndex == nums.length){
            return;
        }

       HashSet<Integer> used = new HashSet<>();

        for(int i=startIndex; i<nums.length;i++){
            //将不符合条件的排除 - 不是单调递增的/去重 - 注意list是否为空
            if(!list.isEmpty() && list.get(list.size()-1) > nums[i] || used.contains(nums[i])){
                continue;
            }

            list.add(nums[i]);
            used.add(nums[i]);

            //如果list的size为2以上,则放入结果集
            if(list.size()>1){
                result.add(new ArrayList<>(list));
            }
            
            backtraking(nums, i+1);
            
            //回溯
            list.remove(list.size()-1);
        }
    }
}

注意题目中说了,数值范围[-100,100],所以完全可以用数组。

用数组来做哈希,效率就高了很多。 ---- 数组,set,map都可以做哈希表,而且数组干的活,map和set都能干,但如果数值范围小的话能用数组尽量用数组

具体优化之后的代码:

class Solution {
    public List<List<Integer>> findSubsequences(int[] nums) {
        result = new ArrayList<>();
        list = new ArrayList<>();

        backtraking(nums, 0);

        return result;
    }

    List<List<Integer>> result;
    List<Integer> list;

    public void backtraking(int[] nums, int startIndex){
        if(startIndex == nums.length){
            return;
        }
        
        //采用数组进行hash去重
        int[] used = new int[201];

        for(int i=startIndex; i<nums.length;i++){
            //排除不符合的情况
            if(!list.isEmpty() && list.get(list.size()-1) > nums[i] ||  used[nums[i]+100] == 1){
                continue;
            }

            list.add(nums[i]);
            used[nums[i]+100] = 1;
            
            //放入结果集
            if(list.size()>1){
                result.add(new ArrayList<>(list));
            }
            
            //递归
            backtraking(nums, i+1);

            //回溯
            list.remove(list.size()-1);
        }
    }
}

参考资料:代码随想录 

46.全排列

 题目链接:力扣

排列问题和组合、分割、子集不同;排列中[1,2]和[2,1]是不同的结果。

以[1,2,3]为例,抽象成树形结构如下:

回溯三部曲:

1.输出以及输入参数:输出 - void;输入 - 【两个全局变量  - List<List<Integer>> result,List<Integer> list】,backtracking(int[] nums, int[] used - 记录当前被使用的元素,防止从头遍历的时候重复获取)

2.终止条件:如果list.size()==nums.length,将list放入result。

3.单层循环逻辑: for(int i=0; i<nums.length; i++)【对于排列来说,不存在index,因此每次for循环都需要从0开始遍历】 如果used[i] == 0, 将nums[i]放入list,将used[i]==1;backtracking(nums, used); 回溯 - used[i] == 0, 将num[i]从list中移除。

具体代码实现:

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        result = new ArrayList<>();
        list = new ArrayList<>();

        //使用used记录放入list中的元素
        int[] used = new int[nums.length];

        backtraking(nums, used);

        return result;
    }

    List<List<Integer>> result;
    List<Integer> list;

    public void backtraking(int[] nums, int[] used){
        //终止条件
        if(list.size() == nums.length){
            result.add(new ArrayList<>(list));
            return;
        }

        //每层for循环都是从index为0的地方开始遍历
        for(int i=0; i<nums.length; i++){
            if(used[i] == 1){
                continue;
            }

            list.add(nums[i]);
            used[i] = 1;

            //递归
            backtraking(nums, used);

            //回溯
            list.remove(list.size()-1);
            used[i] = 0;
        }
    }
}

参考资料:代码随想录 

47.全排列 II 

题目链接:力扣

这道题唯一的区别就是存在重复的元素。

因为所有的顺序都会考虑进去,因此和组合一样,在对数组进行排序之后,并不会影响最后的结果集。

去重的考虑也是一样的,对树层进行去重 - 在同一for循环,如果是有选取过相同的元素则不需要重复选取。

具体的树形结构图如下:
 

回溯三部曲:

1.输出以及输入参数:输出 - void;输入 - 【两个全局变量  - List<List<Integer>> result,List<Integer> list】,backtracking(int[] nums, int[] used - 记录当前被使用的元素,防止从头遍历的时候重复获取 - 【也可以直接当做全局变量】)

2.终止条件:如果list.size()==nums.length,将list放入result。

3.单层循环逻辑: for(int i=0; i<nums.length; i++)【对于排列来说,不存在index,因此每次for循环都需要从0开始遍历】 如果(i>0 && nums[i] == nums[i-1] && used[i-1]==1) 跳过;如果used[i] == 0, 将nums[i]放入list,将used[i]==1;backtracking(nums, used); 回溯 - used[i] == 0, 将num[i]从list中移除。

具体代码如下:

class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        result = new ArrayList<>();
        list = new ArrayList<>();
        used = new int[nums.length];

        //排序
        Arrays.sort(nums);
        
        backtraking(nums);

        return result;
    }

    List<List<Integer>> result;
    List<Integer> list;
    int[] used;//可以将used作为全局变量

    public void backtraking(int[] nums){
        if(list.size() == nums.length){
            result.add(new ArrayList<>(list));
            return;
        }

        //0开始遍历
        for(int i=0; i<nums.length; i++){
            //去重
            if(i>0 && nums[i] == nums[i-1] && used[i-1] == 1){
                continue;
            }

            if(used[i] == 1){
                continue;
            }

            list.add(nums[i]);
            used[i] = 1;

            //递归
            backtraking(nums);

            //回溯
            list.remove(list.size() -1);
            used[i] = 0;
        }
    }
}

参考资料:代码随想录 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值