基础回溯解题思路

回溯一般运用于排列组合或者是“选与不选”的情况。遇到组合或者子集的,无脑回溯基本都能解出来。

排列组合

17. 电话号码的字母组合

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

遇到这种组合的题目,首先可以尝试列举一下它的所有可能性,来看看有什么规律。
Alt
可以发现,这个组合的路径其实是一颗树,每一条路径就是一个答案。而每条路径可以用DFS来得到答案。这一类回溯题其实本质上就是写一个DFS,只是不是运用在标准的树或者图上。

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        if not digits:
            return list()    
        phoneMap = {
            "2": "abc",
            "3": "def",
            "4": "ghi",
            "5": "jkl",
            "6": "mno",
            "7": "pqrs",
            "8": "tuv",
            "9": "wxyz",
        }

        def backtrace(i, cur):
            if i == len(digits) -1:
                for ch in phoneMap[digits[i]]:
                    ans.append(cur + ch)
                return
                #想好怎么结束回溯很重要
            else:
                for ch in phoneMap[digits[i]]:
                    backtrace(i+1, cur + ch)
        ans = []
        backtrace(0, "")
        return ans

巩固

22. 括号生成

拓展

51. N 皇后
这题其实没有看起来那么难,只要洞察了落子后的结果在棋盘上是以何种数学形式表示的,剩下的就是简单的回溯枚举。
140. 单词拆分 II

全排列

46. 全排列

给定一个不含重复数字的数组 nums ,返回其所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1]
输出:[[1]]

全排列,只需要每次选则数组中未选过的数加入当前结果中就可以,可以用回溯,即深度优先搜索。

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        ans = []
        def backtrack(num, res):
            if num == len(nums):
                ans.append(res[:]) 
                #如果是ans.append(res),由于传递的是地址,最终结果会为空
                return
            for i in range(len(nums)):
                if nums[i] not in res:
                    res.append(nums[i])
                    backtrack(num+1, res)
                    res.pop()
        backtrack(0, [])
        return ans
拓展

39. 组合总和

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        ans = []
        def backtrack(idx, num, res):
            if num == target:
                ans.append(res[:])
                return
            elif num > target:
                return
            for i in range(idx, len(candidates)):
                backtrack(i, num+candidates[i], res + [candidates[i]])
            #加入idx是为了防止出现重复的,即搜索时不会往前,因为包含前面的可能结果在之前的搜索中一定已经找到了
        backtrack(0, 0, [])
        return ans

子集问题

这一类的回溯则与DFS的写法有所不同,因为子集意味着有一些元素是取,而另一些元素是不取的。

2044. 统计按位或能得到最大值的子集数目

给你一个整数数组 nums ,请你找出 nums 子集 按位或 可能得到的 最大值 ,并返回按位或能得到最大值的 不同非空子集的数目 。
如果数组 a 可以由数组 b 删除一些元素(或不删除)得到,则认为数组 a 是数组 b 的一个 子集 。如果选中的元素下标位置不一样,则认为两个子集 不同 。
对数组 a 执行 按位或 ,结果等于 a[0] OR a[1] OR … OR a[a.length - 1](下标从 0 开始)。
示例 1:
输入:nums = [3,1]
输出:2
解释:子集按位或能得到的最大值是 3 。有 2 个子集按位或可以得到 3 :
-[3]
-[3,1]
示例 2:
输入:nums = [2,2,2]
输出:7
解释:[2,2,2] 的所有非空子集的按位或都可以得到 2 。总共有 23 - 1 = 7 个子集。
示例 3:
输入:nums = [3,2,1,5]
输出:6
解释:子集按位或可能的最大值是 7 。有 6 个子集按位或可以得到 7 :
-[3,5]
-[3,1,5]
-[3,2,5]
-[3,2,1,5]
-[2,5]
-[2,1,5]

首先,该题显然是一个子集问题,也就是涉及到一个“选与不选”的问题,即当前元素是否要加入到当前的子集中。首先,假设有一个回溯操作 backtrack(i) 表明对原数组中第i个元素进行操作,那么如果不执行该操作,只需要在该函数里直接调用backtrack(i+1),就相当于忽略了第i个元素。而回溯的本质还是枚举每一种情况,只是加上了上述操作后,能够将“不使用某个元素”加入了要枚举的情况中。

此外,由于本题需要求“按位或”的值,而当前的集合“按位或”的值并不会随着元素的增加而变小,因此只需要记录当前值即可。

class Solution:
    def countMaxOrSubsets(self, nums: List[int]) -> int:
        maxOr, cnt = 0, 0
        def backtrace(index, orVal):
            if index == len(nums):
                nonlocal maxOr, cnt
                if orVal > maxOr:
                    maxOr, cnt = orVal, 1
                elif orVal == maxOr:
                    cnt += 1
                return
            backtrace(index + 1, orVal | nums[index]) 
            #这是使用了当前的元素,因此“按位或”的值发生了变化
            backtrace(index + 1, orVal)
            #这是跳过了当前元素
        backtrace(0, 0)
        return cnt

拓展

473. 火柴拼正方形(推荐)
474. 一和零(推荐)
选与不选的题目往往可以用到回溯,但不一定是最优解,因为回溯是偏暴力的一种解法。还有很多类似这样的题中,可以用到动态规划来解。这一题就是两种解法都有涉及。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值