【算法专题】Leetcode刷题之回溯算法(深度优先搜索)

目录

 

【回溯算法】

简单概述

详细描述

回溯法应用

回溯法的实现方法

1. 递归

2. 递推

子集树和排列树

1. 子集树

2. 排列树

经典问题

【刷过的题整理】

分割回文串

【题目】

【思路】

【代码】

电话号码的字母组合

【题目】

【我的方法】


【回溯算法】

简单概述

回溯法思路的简单描述是:把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。

基本思想类同于:

  • 图的深度优先搜索
  • 二叉树的后序遍历

详细描述

回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。

剪枝函数包括两类:1. 使用约束函数,剪去不满足约束条件的路径;2.使用限界函数,剪去不能得到最优解的路径。

回溯法应用

当问题是要求满足某种性质(约束条件)的所有解或最优解时,往往使用回溯法。

回溯法的实现方法

回溯法的实现方法有两种:递归和递推(也称迭代)。一般来说,一个问题两种方法都可以实现,只是在算法效率和设计复杂度上有区别。
【类比于图深度遍历的递归实现和非递归(递推)实现】

1. 递归

思路简单,设计容易,但效率低,其设计范式如下:

//针对N叉树的递归回溯方法  
void backtrack (int t)  
{  
    if (t>n) output(x); //叶子节点,输出结果,x是可行解  
    else  
       for i = 1 to k//当前节点的所有子节点  
        {  
            x[t]=value(i); //每个子节点的值赋值给x  
            //满足约束条件和限界条件  
          if (constraint(t)&&bound(t))   
                backtrack(t+1);  //递归下一层  
        }  
}  

2. 递推

算法设计相对复杂,但效率高。

//针对N叉树的迭代回溯方法  
void iterativeBacktrack ()  
{  
    int t=1;  
    while (t>0) {  
        if(ExistSubNode(t)) //当前节点的存在子节点  
        {  
            for i = 1 to k  //遍历当前节点的所有子节点  
            {  
                x[t]=value(i);//每个子节点的值赋值给x  
                if (constraint(t)&&bound(t))//满足约束条件和限界条件   
                {  
                    //solution表示在节点t处得到了一个解  
                    if (solution(t)) output(x);//得到问题的一个可行解,输出  
                    else t++;//没有得到解,继续向下搜索  
                }  
            }  
        }  
        else //不存在子节点,返回上一层  
        {  
            t--;  
        }  
    }  
}  

子集树和排列树

1. 子集树

所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间成为子集树。
如0-1背包问题,从所给重量、价值不同的物品中挑选几个物品放入背包,使得在满足背包不超重的情况下,背包内物品价值最大。它的解空间就是一个典型的子集树。

回溯法搜索子集树的算法范式如下:

void backtrack (int t)  
{  
  if (t>n) output(x);  
    else  
      for (int i=0;i<=1;i++) {  
        x[t]=i;  
        if (constraint(t)&&bound(t)) backtrack(t+1);  
      }  
}  

2. 排列树

所给的问题是确定n个元素满足某种性质的排列时,相应的解空间就是排列树。
如旅行售货员问题,一个售货员把几个城市旅行一遍,要求走的路程最小。它的解就是几个城市的排列,解空间就是排列树。
回溯法搜索排列树的算法范式如下:

void backtrack (int t)  
{  
  if (t>n) output(x);  
    else  
      for (int i=t;i<=n;i++) {  
        swap(x[t], x[i]);  
        if (constraint(t)&&bound(t)) backtrack(t+1);  
        swap(x[t], x[i]);  
      }  
}   

经典问题

(1)装载问题
(2)0-1背包问题
(3)旅行售货员问题
(4)八皇后问题
(5)迷宫问题
(6)图的m着色问题

(参考自https://blog.csdn.net/weiyuefei/article/details/79316653


【刷过的题整理】

分割回文串

【题目】

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

回文串 是正着读和反着读都一样的字符串。

示例 1:

输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例 2:

输入:s = "a"
输出:[["a"]]
提示:

1 <= s.length <= 16
s 仅由小写英文字母组成

【思路】

1、看到题目要求「所有可能的结果」,而不是「结果的个数」,一般情况下,我们就知道需要暴力搜索所有的可行解了,可以用「回溯法」。

2、「回溯法」实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就「回溯」返回,尝试别的路径。

回溯法是一种算法思想,而递归是一种编程方法,回溯法可以用递归来实现。

回溯法的整体思路是:搜索每一条路,每次回溯是对具体的一条路径而言的。对当前搜索路径下的的未探索区域进行搜索,则可能有两种情况:

  • 当前未搜索区域满足结束条件,则保存当前路径并退出当前搜索;
  • 当前未搜索区域需要继续搜索,则遍历当前所有可能的选择:如果该选择符合要求,则把当前选择加入当前的搜索路径中,并继续搜索新的未探索区域。

上面说的未搜索区域是指搜索某条路径时的未搜索区域,并不是全局的未搜索区域。

作者:fuxuemingzhu
链接:https://leetcode-cn.com/problems/palindrome-partitioning/solution/hui-su-fa-si-lu-yu-mo-ban-by-fuxuemingzh-azhz/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

【代码】

class Solution:
    def partition(self, s: str) -> List[List[str]]:
        def ifhuiwen(substr):
            return substr == substr[::-1]

        def backtrack(leftSubS, path):  # leftSubs代表未探索的区域
            if not leftSubS:  # 如果所有区域均探索完,则保存路径
                res.append(path[:])
                return
            for i in range(1,len(leftSubS)+1):
                if ifhuiwen(leftSubS[:i]):  # 探索剩下的区域
                    backtrack(leftSubS[i:], path+[''.join(leftSubS[:i])])  # 将当前结果加入path

        res = []
        backtrack(s, [])

        return res

执行结果:

执行用时:192 ms, 在所有 Python3 提交中击败了20.80%的用户

内存消耗:31.8 MB, 在所有 Python3 提交中击败了34.18%的用户


电话号码的字母组合

【题目】

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:

输入:digits = ""
输出:[]
示例 3:

输入:digits = "2"
输出:["a","b","c"]

提示:

0 <= digits.length <= 4
digits[i] 是范围 ['2', '9'] 的一个数字。

【我的方法】

回溯~

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        if digits=='':
            return []
        digits = list(digits)
        dig2Alph = ['abc','def','ghi','jkl','mno','pqrs','tuv','wxyz']
        res = []
        def backtrack(remain,output):
            if not remain:
                res.append(output)
                return
            num = int(remain[0])
            options = list(dig2Alph[num-2])
            for i in options:
                backtrack(remain[1:], output+i)
        backtrack(digits, '')
        return res

执行结果:

执行用时:40 ms, 在所有 Python3 提交中击败了60.17%的用户

内存消耗:15.1 MB, 在所有 Python3 提交中击败了5.40%的用户

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值