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

491.递增子序列

题目链接:491.递增子序列

文档讲解:代码随想录/递增子序列

视频讲解:视频讲解-递增子序列

状态:已完成(1遍)

解题过程 

看到题目的第一想法

这道题和子集II的区别一个是必须是递增的子集,一个是答案的子数组里至少得有两个元素。

也是需要树层去重的。

手搓代码如下:

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var findSubsequences = function (nums) {
    let ans = [], smallArr = [];
    function subKid(startIndex) {
        //当小数组里至少有2个元素才添加进ans中
        if (smallArr.length > 1) {
            ans.push([...smallArr]);
        }
        for (let i = startIndex; i < nums.length; i++) {
            if (i != startIndex && nums.indexOf(nums[i]) != i) continue;
            if (smallArr.length==0 || nums[i] >= smallArr[smallArr.length - 1]) {
                //刚开始的时候添加进去第一个数字,smallArr有数字的话如果现在的数大于smallArr的最后一个数字才添加
                smallArr.push(nums[i]);
                subKid(i + 1);
                smallArr.pop();
            }
        }
    }
    subKid(0);
    return ans;
};

提交是有问题的,我发现这里判断去重的时候由于不能够提前将数组排序好,再单调的比较nums的前后两个元素是不可取的。所以我想到了用set哈希。

代码如下:

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var findSubsequences = function (nums) {
    let ans = [], smallArr = [];
    function subKid(startIndex) {
        //当小数组里至少有2个元素才添加进ans中
        if (smallArr.length > 1) {
            ans.push([...smallArr]);
        }
        //动用Set哈希
        let ifUsed = new Set();
        for (let i = startIndex; i < nums.length; i++) {
            if (ifUsed.has(nums[i])) continue;
            if (smallArr.length==0 || nums[i] >= smallArr[smallArr.length - 1]) {
                //刚开始的时候添加进去第一个数字,smallArr有数字的话如果现在的数大于smallArr的最后一个数字才添加
                smallArr.push(nums[i]);
                ifUsed.add(nums[i]);
                subKid(i + 1);
                smallArr.pop();
            }
        }
    }
    subKid(0);
    return ans;
};

提交没有问题。

看完代码随想录之后的想法 

大体上是一样的,不过代码随想录用了数组哈希来进行去重判断,在nums中元素只在±100之间确实比较简便。

讲解代码如下:

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var findSubsequences = function(nums) {
    let result = []
    let path = []
    function backtracing(startIndex) {
        if(path.length > 1) {
            result.push(path.slice())
        }
        let uset = []
        for(let i = startIndex; i < nums.length; i++) {
            if((path.length > 0 && nums[i] < path[path.length - 1]) || uset[nums[i] + 100]) {
                continue
            }
            uset[nums[i] + 100] = true
            path.push(nums[i])
            backtracing(i + 1)
            path.pop()
        }
    }
    backtracing(0)
    return result
};

总结

这题和子集II的最大差别就是不能提前对nums进行排序,所以需要动用哈希表来进行树层去重。


 46.全排列

题目链接:46.全排列

文档讲解:代码随想录/全排列

视频讲解:视频讲解-全排列

状态:已完成(1遍)

解题过程  

看到题目的第一想法

这题应该就是树枝去重了,跟之前的树层去重不一样。父节点用过的数字,递归子节点的时候是不能用的,所以我就想着传递一个数组形式的哈希表,来判断元素是否用过。

手搓代码如下:

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var permute = function(nums) {
    let ans = [],smallArr = [];
    function lineQueue(used){
        if(smallArr.length == nums.length){
            ans.push([...smallArr]);
            return;
        }
        for(let i = 0;i<nums.length;i++){
            //树枝去重,之前用过的标记一下,后面层递归的时候不能再用了
            if(used[nums[i]+10])continue;
            smallArr.push(nums[i]);
            used[nums[i]+10] = true;
            lineQueue(used);
            used[nums[i]+10] = false;
            smallArr.pop();
        }
    }
    lineQueue([]);
    return ans;
};

提交没有问题。

 看完代码随想录之后的想法 

基本是一致的,除了是否使用过的数组,我是用数值来记录的,这里使用索引 i 记录的。

讲解代码如下:

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var permute = function(nums) {
    const res = [], path = [];
    backtracking(nums, nums.length, []);
    return res;
    
    function backtracking(n, k, used) {
        if(path.length === k) {
            res.push(Array.from(path));
            return;
        }
        for (let i = 0; i < k; i++ ) {
            if(used[i]) continue;
            path.push(n[i]);
            used[i] = true; // 同支
            backtracking(n, k, used);
            path.pop();
            used[i] = false;
        }
    }
};

总结

排列问题和组合问题以及切割、子集问题最大的不同就是for循环里不用startIndex 了,因为每次都要从头开始搜索。但是需要一个东西来记录上一层递归用过了哪些元素。

还有需要注意的是树层去重定义的used和树枝去重定义的used的方式和位置的不同


47.全排列 II

题目链接:47.全排列 II

文档讲解:代码随想录/全排列 II

视频讲解:视频讲解-全排列 II

状态:已完成(1遍)

解题过程  

看到题目的第一想法

我的想法是有重复数字了之后就不仅要做树层去重还要做树枝去重了。

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var permuteUnique = function(nums) {
    let ans = [],smallArr = [];
    function repeatQueue(queueUsed){
        if(smallArr.length == nums.length){
            ans.push([...smallArr]);
            return;
        }
        let sameUsed = new Set();
        for(let i = 0;i<nums.length;i++){
            //这里queueUsed是记录树枝上有没有用过这个元素,sameUsed是记录每一层递归里兄弟节点有没有用过这个元素
            if(queueUsed[i] || sameUsed.has(nums[i]))continue;
            smallArr.push(nums[i]);
            queueUsed[i] = true;
            sameUsed.add(nums[i]);
            repeatQueue(queueUsed);
            queueUsed[i] = false;
            smallArr.pop();
        }
    }
    repeatQueue([]);
    return ans;
};

提交没有问题。 

 看完代码随想录之后的想法 

有一点没想到的是可以提前对nums进行排序,因为是排列问题,打乱了原本的顺序也没关系,反正最后是都会全举出来的。这里排序了之后就可以用nums[i] == nums[i-1]来进行判断了。

同时他对是否是树层去重有一个写法:used[i-1] == false。很是精妙。但说实话我的写法好像更精简哈哈哈(虽然性能上比不过,因为每层递归都要新开一个Set)。

讲解代码如下:

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var permuteUnique = function (nums) {
    nums.sort((a, b) => {
        return a - b
    })
    let result = []
    let path = []

    function backtracing( used) {
        if (path.length === nums.length) {
            result.push([...path])
            return
        }
        for (let i = 0; i < nums.length; i++) {
            if (i > 0 && nums[i] === nums[i - 1] && !used[i - 1]) {
                continue
            }
            if (!used[i]) {
                used[i] = true
                path.push(nums[i])
                backtracing(used)
                path.pop()
                used[i] = false
            }


        }
    }
    backtracing([])
    return result
};

总结

有重复数字的时候就不仅要在每层递归判断上层递归有没有用过当前元素,还要在同层递归中判断有没有用过此元素了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值