前端重拾算法数据结构一个月(11)

122 阅读2分钟

第十一天

学习开始前,先来写一道题目吧,一样是力扣的第78题。

第二十二题

LeetCode78.子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

输入: nums = [1,2,3]
输出: [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

思路: 这一题其实是组合问题的一个简化版(自我感觉)。但有一点要注意的是,它将满足条件的path推进result的场合并不是在终止条件里面,因为这个题目中没有k的限制,没有规定path中的元素个数,这代表着终止条件应该是path中的最后一个元素=nums中的最后一个元素(即每个都要进来)。

function subsets(nums: number[]): number[][] {
    const path:number[] = []
    const result:number[][] = []
    const backtracking = (startIndex:number)=>{
        result.push(path.map(i=>i))
        if (path[path.length - 1] === nums[nums.length - 1]) return
        for (let i = startIndex; i < nums.length; i++) {
            path.push(nums[i])
            backtracking(i + 1)
            path.pop()
        }
    }
    backtracking(0)
    return result
};

一次写出!没有失误非常开心哈哈。那么今天来学习的回溯算法的内容是:组合总和。

组合总和

组合总和和组合有什么区别呢,这里用力扣的第39题作为模板来学习这个内容:

第二十三题

LeetCode39.组合总和

给你一个无重复元素整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以无限制重复被选取。如果至少一个数字的被选数量不同,则两种组合是不同的。 

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

思路: 还是根据我们的三部曲来做,第一步确认递归函数的参数以及返回值。对我来说让我先做第二步确认终止条件更好确认一些,那么这道题到什么程度需要返回呢,自然是在path中的元素相加等于target的时候了,于是终止条件就出来了

eval(path.join("+"))===target //eval()能计算 JavaScript 字符串,并把它作为脚本代码来执行。

但是!这是我之前犯的错误,不要使用eval()!!eval() 是一个危险的函数, 它使用与调用者相同的权限执行代码。eval() 通常比其他替代方法更慢,因为它必须调用 JS 解释器,而许多其他结构则可被现代 JS 引擎进行优化。

后面我选择的方法是:

path.length > 0 && path.reduce((a, b) => a + b) === target
//这里要判断长度大于0,否侧reduce不生效

然后在题目中是这样,分别从2、3、6、7为startIndex跟之后的集合组成元素组合。所以我确认了一个参数为startIndex。

function combinationSum(candidates: number[ ], target: number): number[ ][ ] {
    const path: number[ ] = [ ]
    const result: number[ ][ ] = [ ]
    const backtracking = (startIndex: number) => {
        if (path.length > 0 && path.reduce((a, b) => a + b) === target) {
            result.push(path.map((i) => i))
            return
        }
        for (let i = startIndex; i < candidates.length; i++) {
            path.push(candidates[i])
            if (path.reduce((a, b) => a + b) > target) {
            //这里剪枝,条件不满足就直接不用进入下一个递归了
                path.pop()
                continue
            }
            backtracking(i)
            path.pop()
        }
    }
    backtracking(0)
    return result
};

在看一个导师的讲解中,他使用了一个sum作为path中元素的和,如果这样我就不用每次都使用reduce了。但我不太确定会节省很多的时间。反正空间肯定是会多用一丢丢,但是没差我试了一下,大概差了几ms,应该只是网络问题吧。

function combinationSum(candidates: number[], target: number): number[][] {
    const path: number[] = []
    const result: number[][] = []
    const backtracking = (startIndex: number,sum:number) => {
        if (sum === target) {
            result.push(path.map((i) => i))
            return
        }
        for (let i = startIndex; i < candidates.length; i++) {
            path.push(candidates[i])
            sum += candidates[i]
            if (sum > target) {
                path.pop()
                sum -= candidates[i]
                continue
            }
            backtracking(i,sum)
            path.pop()
            sum -= candidates[i]
        }
    }
    backtracking(0,0)
    return result
};

ok!今天到这里。明天周六还得上班QAQ