回溯算法——DFS

401 阅读3分钟

回溯算法,在leetcode中的算法题有很多种,结合几道leecode的题,加深一下对此理解,方便以后愉快的刷题。

回溯算法

概念

回溯算法,即DFS(深度优先算法),看到深度优先,想到了啥?是不是树的遍历,一条道走到黑,然后又回退到上一步,是一种试探性的算法。解题的套路,我就直说吧,就是用递归。

例题

以一道题来说明:

leetcode 复原iP地址

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式,IP地址有以下几点特点:

  • 4个整数(每个整数位于 0 到 255 之间)
  • 不能以0开始,但是可以只有一个0
  • 整数之间用 '.' 分隔

简单画下它的树形图,一般这种用回溯算法的题目,我都是直接画个图先。

可以看到:回溯算法实际上一个类似枚举的深度优先搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回(也就是递归返回),尝试别的路径。

解题

前面提到,既然回溯算法要用到递归,我用递归解下这道题:

var restoreIpAddresses = function(s) {
    let res = [];
    dfs(s, 1, -1, []);
    /**
     * 回溯算法
     * @param {*} str 传入的字符串
     * @param {*} level ip的第几个整数,从0开始
     * @param {*} index  要选取的字符串的位置
     * @param {*} ipAddress ip地址,以数组形式传入,,满足条件后以.连接起来。比如[192, 168, 80, 1] 可连接为192.168.80.1
     */
    function dfs(str, level, index, ipAddress) {
        //1.截止条件
        if(level === 5 || index === str.length -1 ){
            if(level === 5 && index === str.length -1){
                res.push(ipAddress.join('.'));
            }           
            return;
        }
        
        // 2.候选节点
         //每个整数位于 0 到 255 之间组成,最多截取前三位
         for(let i =1; i <4; i++){
           let ip = str.substr(index+1, i);

            //判断是否满足条件  0~255之间,不能以0开始
           if(parseInt(ip)<256 && ( ip === '0' || !ip.startsWith('0'))){
                ipAddress.push(ip);
                dfs(str,level+1,index+i,ipAddress);
                ipAddress.pop();
            }
        }
    }
    return res;
}

解题模板

  • 判断递归终止条件
  • 找到候选节点push进栈
  • 完成递归后pop出数据,即回溯

这个只是我自己认为的解决这类问题的模板。

回溯题目解答

主要刷几道leecode题加深自己理解,都是套上面的模板

电话号码的字母组合

电话号码字母组合

/**
 * @param {string} digits
 * @return {string[]}
 */
var letterCombinations = function(digits) {
    // 处理边界条件
    if(digits.length<1){
        return []
    }
    let res=[];
   

    //创建一个map数据结构保存数字与字母对应关系
    let map = new Map()
        .set('2', 'abc')
        .set('3', 'def')
        .set('4', 'ghi')
        .set('5', 'jkl')
        .set('6', 'mno')
        .set('7', 'pqrs')
        .set('8', 'tuv')
        .set('9', 'wxyz');

     dfs(digits, 0 , ''); 
   
   /**
     * digits: 传入的数字字符串
     * index:  遍历到第几个数字了
     * str:    字母组合
     */
    function dfs(digits, index, str){
        //1. 截止条件
        if(index === digits.length){
            res.push(str);
            return;
        }
        // 2. 候选节点
        for(let s of map.get(digits[index])){
            str+= s;
            dfs(digits, index+1,str);
            // 3. 回溯,从栈中弹出
            str= str.slice(0, str.length-1);
        }
    }
    return res;
};

组合总数

组合总数

/**
 * @param {number[]} candidates
 * @param {number} target
 * @return {number[][]}
 */
var combinationSum = function(candidates, target) {
    let res = [];
    let strArr = [];
    dfs(candidates, target, []);

    function dfs(candidates, remain, arr){
        if(remain <= 0){
            if(remain === 0 ){
                let str = [...arr].sort(function(a,b) {
                    return a-b;
                });
                // 这里主要是去重
                if(strArr.indexOf([...str].toString()) === -1){
                    strArr.push([...str].toString());
                    res.push([...str]);
                }               
            }        
            return;
        }
        for(let can of candidates){
            remain -= can;
            arr.push(can);
            dfs(candidates, remain, arr);
            remain+=can;
            arr.pop();
        }
    }
    return res;
};