Python 深搜回溯算法详细解析,包括代码每一步是怎么走的,为什么会得到这样的结果

        在算法方面搜索绝对是出现率非常高的,无论是一些竞赛还是面试的时候,一般都会有几道搜索的题目。就比如蓝桥就可以说是只要暴力写的好,拿个省奖轻轻松松,再会一点搜索,国赛也不是没希望。在面试中搜索题目也是常见题型。那么,搜索算法就很重要了。这篇文章我将给大家讲解一下深度优先搜索。

        可以说大部分用到dfs的题目解法相对都是比较固定的,那么我就用leetcode的一道题目作为示范讲解一下深搜的那些套路。因为我本人就是那种容易发现问题而且不解决问题就特别难受的,在一开始刷这种类型题目的时候我就特别好奇比如循环里回溯那么循环到底运行了多少次,又是怎么运行的,好了,话不多说我们从题目中去解析。

        

题目: 给定一个仅包含数字 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'] 的一个数字。

        这算是深搜题目中很经典的一道题目了,这道题给我的感觉跟leetcode的全排列那道题很像,如果这道题你能弄懂我建议再去做一下全排列,看一下能不能做出来。

        回溯的本质就是在循环中一次次运行自身函数直到源头循环(有人叫第一次循环,但我更倾向于叫它源头循环,源头循环是递归回溯的开始,也是回溯的结束)结束,在dfs函数中会有递归的满足条件,一次次回溯就是一次次把满足条件的值添加到result列表中。这样说是不是更容易理解一些呢。

        那么这一道题就在于怎样才能将各个数字代表的字母加入到这种回溯中去,请大家看一下

下面的图:

        我将结合这面的图一点点剖析,首先函数给到数字digit,要注意这些数字是字符形式。

        我们需要把digit字符串切片,这里的index是循环指针。

t = digit[index]

        这里其实有一个问题,那就是index的范围,稍不注意这里就会出现 list index out of range 报错,这是因为我们的for 循环是建立在每个每个数字代表的字母数量上的,所以这里我们需要一个小技巧,那就是在超出范围之前把函数return。       

    if index == len(digits):
        res.append(tmp)
        return

        切片之后对每个数字进行循环遍历,在这里我的理解是类似于多层循环嵌套,一次回溯就会再一次循环满足上面条件之后就return,本次循环指针+1。在回溯过程中tmp从空串接收循环中的字符i,index指针+1.

    for i in letters:
        # 调用下一层递归,用文字很难描述,请配合图理解
        dfs(digits, res, d, tmp + i, index + 1)

        下面是本题的全部代码

# -*- coding:utf-8 -*-
"""
作者:Zhang Zi yu
日期:2022年03月07日
"""


def letterCombinations(digits):
    """
    :type digits: str
    :rtype: List[str]
    """
    # 注意边界条件
    if not digits:
        return []
    # 一个映射表,第二个位置是"abc“,第三个位置是"def"。。。
    # 这里也可以用map,用数组可以更节省点内存
    d = [" ", "*", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"]
    # 最终输出结果的list
    res = []
    dfs(digits, res, d, "", 0)
    return res
    # 递归函数


def dfs(digits, res, d, tmp, index):
    # 递归的终止条件,注意这里的终止条件看上去跟动态演示图有些不同,主要是做了点优化
    # 动态图中是每次截取字符串的一部分,"234",变成"23",再变成"3",最后变成"",这样性能不佳
    # 而用index记录每次遍历到字符串的位置,这样性能更好
    if index == len(digits):
        res.append(tmp)
        return
    # 获取index位置的字符,假设输入的字符是"234"
    # 第一次递归时index为0所以c=2,第二次index为1所以c=3,第三次c=4
    # subString每次都会生成新的字符串,而index则是取当前的一个字符,所以效率更高一点
    c = digits[index]
    # map_string的下表是从0开始一直到9
    # 比如c=2时候,2-'0',获取下标为2,letter_map[2]就是"abc"
    letters = d[int(c)]

    # 遍历字符串,比如第一次得到的是2,也就是遍历"abc"
    for i in letters:
        # 调用下一层递归,用文字很难描述,请配合动态图理解
        dfs(digits, res, d, tmp + i, index + 1)
        #print(res)

下面附图每次循环的运行过程,用 print 函数进行测试,这是在每次回溯之后的res列表

下面是每次回溯之前的res列表

这样是不是就清晰一点了,要不是测试一下,谁又能想到一次循环加回溯竟然将循环运行了接近20次呢。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

python改变世界

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值