代码随想录算法训练营第二十九天|491.递增子序列、46.全排列、47.全排列 II

代码随想录算法训练营第二十九天|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;
        }
    }
}

感想

进行一定再完成一天的。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值