回溯算法解题思路

回溯算法是一种有效的搜索策略,常用于解决组合优化问题。通过递归尝试所有可能的路径,当发现不符合条件时进行回溯。本文介绍了回溯法的基本原理和模板框架,并列举了多个使用回溯法解决的编程题目,如电话号码的字母组合、括号生成、解数独等。通过对这些题目进行分析,展示了回溯法在实际问题中的应用和解题思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

回溯

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

回溯算法将解空间看作一定的结构,通常为树形结构,一个解对应于树中的一片树叶。算法从树根(即初始状态出发),尝试所有可能到达的结点。当不能前行时就后退一步或若干步,再从另一个结点开始继续搜索,直到尝试完所有的结点。也可以用走迷宫的方式去理解回溯。

模板框架

回溯算法一般是由递归调用,每次递归前做出选择,递归的方向改变,递归完成之后,撤销之前做出的选择。优化的过程就是搜索到目标之后,或者明显不满足目标条件的时候,及时进行剪枝返回,减少多余运行时间。

相关题目

17. 电话号码的字母组合

题目:给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例: 输入:“23”
输出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].

  • 分析:每一个2有[a,b,c]三种情况,所以首先就要建立一个数字对应的字符字典。例如:‘2’:[‘a’, ‘b’, ‘c’]。这样才能够取到每一个值。之后就是回溯算法,每次选个一个数字,用for循环去选择数字所含的字母,然后将其组合起来。
  • 解题模板:
from typing import List
class Solution:
    def letterCombinations(self, digits:int):
        res = []
        dict = {
   
            '2':['a', 'b', 'c'],
            '3':['d', 'e', 'f'],
            '4':['g', 'h', 'i'],
            '5':['j', 'k', 'l'],
            '6':['m', 'n', 'o'],
            '7':['p', 'q', 'r', 's'],
            '8':['t', 'u', 'v'],
            '9':['w', 'x', 'y', 'z']
        }
        next_digit = list(str(digits))
        def rec_char(combination, next_digit):
            #首先是写出每次递归的终止条件:即写一个没有数字时,则表示一次递归结束
            if len(next_digit) == 0:
                res.append(combination)#每次递归结束,表示树达到最深,则将输出加入列表
            else:#本次没有达到树的最深处,则继续往下递归
                for c in dict[next_digit[0]]:#对本次数字内的字符进行循环
                    rec_char(combination + c, next_digit[1:])
        rec_char("", next_digit)
        return res  

22. 括号生成

题目:数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

  • 分析:生成括号分为左括号和右括号,在递归的时候首先就需要设置终止条件。其中这道题的终止条件有两个:
    • 1.字符的个数达到上限。
    • 2.在生成字符的过程中,根据前面的字符串看来明显不能成立,要及时止损,提前终止。中val就是字符串的值,设定左括号为+1,右括号为-1,不难发现。一旦字符串的值为负数,则该字符串一定不成。所以可以使用这个判断进行提前终止生成。还有一种方法就是在生成括号的时候,对左右括号的个数进行限制,因为设定了括号数量上限,如果要使得结果成立,那么左右括号应该各占一半的数量。这样即相当于提前终止,又对字符串的数量进行了限制,一举两得。
  • 解题模板:
class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        ans = []
        def double_tree(S = '',left = 0, right = 0):
            if len(S) == 2*n:
                ans.append(S)
                return 
            if left < n:
                double_tree(S + '(', left + 1, right)
            if right < left:
                double_tree(S + ')', left , right + 1)
        double_tree()
        return ans

37. 解数独

题目:编写一个程序,通过已填充的空格来解决数独问题。
一个数独的解法需遵循如下规则:数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。空白格用 ‘.’ 表示

  • 分析:使用回溯的方法进行填空,每一填入一个数字然后对其合法性进行判断。所以我们还需要一个验证合法性的一个,验证合法性,需要对应行,对应列,所在九宫格三个地方不存在重复。
  • 优化:
    1. 建立一个函数,首先对将要填入的数字进行有效性判断
    2. 回溯函数:首先写出弹出条件,这里是行数为9 的时候弹出,但是注意,由于是回溯,所以会有很多层,我们找到一个答案就想返回时,还会返回到上一层继续计算。这个时候需要逐层一直返回。这个使用的方法就是 return True。
    3. 为什么使用return True? 其实回溯的时候,回溯函数并不是直接写回溯函数,而是使用的 if 回溯函数的形式。这样就可以当得到一个正解的时候一层一层的回溯到最外层。
    4. 弹出条件除了回溯函数的首部,还有一处就是在for 循环内对原始的数字进行跳过的时候,如果这时候后面几个数字早已语设定好了的,那么直接可以递增到 row==9,输出正解,开始逐层回传。
  • 解题模板:
class Solution(object):
    def solveSudoku(self, board):
        """
        :type board: List[List[str]]
        :rtype: None Do not return anything, modify board in-place instead.
        """
        out = []
        def is_valid(row, col, num, board):#验证将填入数字的有效性
            for i in range(9):
                if i != row and  str(num) == board[i][col]:#验证同一列
                    return False
                if i != col and  str(num) == board[row][i]:#验证同一行
                    return False
			#根据索引将数字确定所在哪一块3*3区域
            i_index = row // 3 * 3
            j_index = col // 3 * 3

            for i in range(3):#验证该3*3区域是否有重复
                for j in range(3):
                    if i+i_index != row and j+j_index != col and \
                        str(num) == board[i+i_index][j+j_index]:
                        return False
            return True

        def backtrack(board, row, col):#定义回溯函数
            if row == 9:#由于行数为[0,8],行数为9的时候代表填入完毕
                out.append(board.copy())
                return True
            for k in range(1,10):#对1-9内的数字尝试填入
                while board[row][col] != '.':#从左到右,从上到下扫描,找到待填入的位置
                    if col < 8:
                        col += 1
                    elif col == 8:
                        row += 1
                        col = 0
                        if row == 9:
                            out.append(board)#填入完毕
                            return True
                if is_valid(row,col,k,board):#找到待填入的位置,验证是否可以填入
                    board[row][col] = str(k)#如果可以就填入该数字,进行下一轮回溯
                    if backtrack(board, row if (col<8) else row + 1, (col + 1)%9):
                        return True
                    board[row][col] = '.'#撤销上次的填入
            return False
        backtrack(board, 0 , 0)
        return out[0]  

39. 组合总和

题目:给定一个无重复元素的数组 candidates 和一个目

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值