算法和数据结构:回溯法

137 阅读2分钟

依靠递归进行树的建立,根据问题本身的一些特点进行 return (即剪枝)。 总结了几道 leetcode 上常见的回溯的题目。

22. 括号生成

var generateParenthesis = function(n) {
//插入数量不超过n
//可以插入 ) 的前提是 ( 的数量大于 )  ——括号成对偶的硬性要求,因为每一个 ) 都需要一个( 进行匹配
//每次放 ( 或者 ) 都要考虑,
    let result = [] 
    function help(left, right, string){
        if(left === n && right === n){
            result.push(string)
            return
        }
        if(left < n){
            help(left + 1, right, string + '(')
        }
        if(right < left){
            help(left, right + 1, string + ')')
        }
    }
    help(00'')
    return result
};

20. 有效的括号——非回溯

本题不是用回溯法,只是根据上一题的生成括号过程,我们可以反过来判断括号是否有效。

var isValid = function(s) {
    //左边的全部压进去,右边的来匹配,匹配到左边的就,左边的就出栈,所以一定是都能匹配的
    let len = s.length
    if(len % 2 !== 0return false 
    let stack = [] //匹配到就出栈,否则压栈
    for(let i = 0; i < len; i++){
        if(s[i] === '('){
            stack.push(s[i])
        }else if(s[i] === ')'){
            let current = stack.pop()
            if(current !== "("return false
        }else if(s[i] === '['){
            stack.push(s[i])
        }else if(s[i] === ']'){
            let current = stack.pop()
            if(current !== "["return false
        }else if(s[i] === '{'){
            stack.push(s[i])
        }else if(s[i] === '}'){
            let current = stack.pop()
            if(current !== "{"return false
        }else{
            return false
        }
    }
    return stack.length === 0
};

93. 复原IP地址

var restoreIpAddresses = function(s) {
    let result = []
    function help(pre, n, arr){ //上一次插入的位置(pre之前),已经插入过的次数,当前的结果
        if(n === 3){
            let str = s.substring(pre)
            let num = parseInt(str)
            if(str.substring(0,1) === '0' && str.length > 1) return
            if(str.length < 4 && num <= 255){
                arr.push(str)
                result.push(arr.join('.'))
            }
            return
        }
        for(let i = pre + 1; i < s.length && i - pre <= 3; i++){
            let str = s.substring(pre, i)
            let num = parseInt(str)
            if(str.substring(0,1) === '0' && str.length > 1) continue //除非是0,否则不会由零开始
            if(str.length < 4 && num <= 255){
                help(i, n + 1, [...arr, str])
            }
        }
    }

    help(0, 0, [])
    return result
};

78. 子集

每个元素都可以选择或者不选择 每一次递归,下标都要增加。

var subsets = function(nums) {
    //回溯算法,父节点的情况
    let len = nums.length
    let result = []
    function help(index, arr){ //index 为数组的下标
        if(index === len - 1){
            result.push(arr)
            return
        }
        help(index + 1, [...arr])
        help(index + 1, [...arr, nums[index + 1]])
    }
    help(-1, [])
    return result
};

46. 全排列

**注意:数组拷贝修改,需要深拷贝,每一次递归使用的数组都不一样!

[...temp]

每一次将可挑选的范围缩小即可。

var permute = function(nums) {
    let result = []
    function help(remainNums, res){
        let len = remainNums.length
        if(len === 0) {
            result.push(res)
        }
        for(let i = 0; i < len; i++){
            let temp = [...remainNums]  //数组拷贝修改,需要深拷贝!!!
            let item = temp.splice(i, 1)[0//切了返回被切掉的
            help(temp, [...res, item])
            // //不拷贝数组的做法
            // let item = remainNums.splice(i, 1)[0]
            // help(remainNums, [...res, item])
            // remainNums.splice(i, 0, item)
        }
    }
    help(nums, [])
    return result
};

77. 组合

Cnk n中选k,每个都可以选或者不选,选完k个结束,到n了还没选完也结束。与子集的不同之处是,这里规定了选取的长度,而子集的长度任意。

var combine = function(n, k) {
    // Cnk
    let result = []
    function help(currentNum, remain, arr){
        if(currentNum > n) return
        if(remain === 0){
            result.push(arr)
            return
        }
        help(currentNum + 1, remain, arr)
        help(currentNum + 1, remain - 1, [...arr, currentNum + 1])
    }
    help(0, k, [])
    return result
};

39. 数组总和

找到数组子集元素等于目标值的所有组合(下标单调增,达到去重的效果)

var combinationSum = function(candidates, target) {
    //简单的回溯法会造成重复,
    //当前的元素可以重复,但是后面的递归不能再用当前元素前面的元素!
    //回溯法
    let len = candidates.length
    let result = []
    function help(arr, cur, start){  //当前的结果,当前的和,当前可以使用元素的起始位置
        if(cur > target){
            return 
        }else if(cur === target){
            result.push(arr)
        }else{
            for(let i = start; i < len; i++){
                help([...arr, candidates[i]], cur + candidates[i], i)
            }
        }
    }
    help([], 00)
    return result
};

岛屿问题

这类问题使用 dfs 来解决。

网格的遍历容易重复遇到之前遍历过的点,所以要做标记。

695. 岛屿的最大面积

2层循环遍历每个点,遇到一个岛屿就能遍历这个岛的所有内容,并且全部变成2,避免重复。 注意边界返回值即可

var maxAreaOfIsland = function(grid) {
    //遇到一个为1的点,就开始找到它周围为1的点,全部置为2,也就是让这个岛在后续的遍历中,不再重复遍历。
    let row = grid.length
    let col = grid[0].length
    let result = 0
    for(let i = 0; i < row; i++){
        for(let j = 0; j < col; j++){
            if(grid[i][j] === 1){
                let a = area(i, j)
                result = Math.max(result, a)
            }
        }
    }
    function area(i, j){
        if(i < 0 || i > row - 1 || j  < 0 || j > col -1){
            return 0
        }
        if(grid[i][j] !== 1return 0
        grid[i][j] = 2
        return 1 
                + area(i - 1, j)
                + area(i, j - 1)
                + area(i + 1, j)
                + area(i, j + 1)
    }
    return result
};

200. 岛屿的数量

var numIslands = function(grid) {
    //遇到一个为1的点,就开始找到它周围为1的点,全部置为2,也就是让这个岛在后续的遍历中,不再重复遍历。
    let row = grid.length
    if(row <= 0return 0
    let col = grid[0].length
    let result = 0
    for(let i = 0; i < row; i++){
        for(let j = 0; j < col; j++){
            if(grid[i][j] === '1'){ //遇到一个1,能够这个岛屿全部变成 2,即沉没 
                //这里也可以直接 result++,然后在area中对岛屿进行沉没
                result += area(i ,j)
            }
        }
    }
    //每遇到一个岛就让它全部为2
    function area(i, j){
        if(i < 0 || i > row - 1 || j  < 0 || j > col -1){
            return 0
        }
        if(grid[i][j] !== '1'return 0
        grid[i][j] = '2'
        area(i - 1,j)
        area(i ,j - 1)
        area(i + 1,j)
        area(i ,j + 1)
        return 1
    }
    return result
};

463. 岛屿的周长

var islandPerimeter = function(grid) {
    let row = grid.length
    if (row === 0return 0
    let col = grid[0].length
    let result = 0
    for (let i = 0; i < row; i++) {
        for (let j = 0; j < col; j++) {
            if (grid[i][j] === 1) {
                result = Math.max(help(i, j), result)
            }
        }
    }
    function help(i, j) {
        //周长即遍历的过程中遇到边界或者水是次数
        //遇到边界
        if (i < 0 || j < 0 || i > row - 1 || j > col - 1) {
            return 1
        }
        //遇到水
        if (grid[i][j] === 0) {
            return 1
        }
        if (grid[i][j] === 1) {
            grid[i][j] = 2
            let a = help(i - 1, j) +
                help(i, j - 1) +
                help(i + 1, j) +
                help(i, j + 1)
            return a
        }
        //2的情况
        return 0
    }
    return result
};