算法通关村第十八关:白银挑战-回溯热门问题

news2024/10/11 20:32:59

白银挑战-回溯热门问题

回溯主要解决一些暴力枚举也搞不定的问题zh,例如组合、分割、子集、排列、棋盘等。

1. 组合总和问题

LeetCode39
https://leetcode.cn/problems/combination-sum/

思路分析

如果不考虑重复,跟题目 LeetCode 113 类似
考虑重复的话,需要重新分析

对于序列{2,3,6,7}, target_sum=7

先选择1个2,剩下target=7-2=5
再选择1个2,剩下target=7-2-2=3
再选择1个2,剩下target=7-2-2-2=1,小于列表中最小的数2,不满足要求了

回退只选2个2时,target=7-2-2=3,序列{2,3,6,7}中有3,满足要求,{2,2,3}
回退只选1个2时,target=7-2=5,这时候不能选择2,从序列{3,6,7}中选择,没有符号要求的

依次类推,后面尝试从3、6、7开始选择,如图所示
图的横向是针对每个元素的暴力枚举,纵向是递归

在这里插入图片描述

代码实现

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        def dfs(path, nums, start_index, target):
            if target < 0:
                return
            if target == 0 and path:
                res.append(path[:])
                return
            for i in range(start_index, len(nums)):
                if nums[i] <= target:
                    path.append(nums[i])
                    dfs(path, nums, i, target - nums[i])
                    path.pop()

        candidates.sort()
        res = []
        path = []
        dfs(path, candidates, 0, target)
        return res
class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        def dfs(path, start):
            if path and sum(path) > target:
                return
            if path and sum(path) == target:
                res.append(path[:])
                return

            for i in range(start, len(candidates)):
                path.append(candidates[i])
                dfs(path, i)
                path.pop()

        candidates.sort()
        res = []
        dfs([], 0)
        return res

题目拓展

如果输入的 candidates 数组中存在负数怎么办,如何实现呢?

2. 分割回文串

分割问题也是回溯要解决的典型题目之一,常见的题目有分割回文串,分割IP地址、以及分割字符串等

LeetCode131 分割回文串
https://leetcode.cn/problems/palindrome-partitioning/

思路分析

本题包含两个点:

  1. 如何判断回文串?=> 双指针
  2. 如何切割?=> 回溯

暴力切割,非常困难,使用回溯就简单清晰的多

  • 试一试,第一次切’a’,第二次切’aa’,第三次切’b’,对应的回溯里的for循环,横向
  • 第一次切了’a’,剩下’ab’。进行递归继续切割’ab’,对应纵向
  • 切割线切割到字符串的结尾位置,说明找到了一个切割方法

在这里插入图片描述

代码实现

class Solution:
    def __init__(self):
        self.res = []
        self.path = []
        
    # 判断回文串
    def is_palindrome(self, str):
        start, end = 0, len(str) - 1
        while start < end:
            if str[start] != str[end]:
                return False
            start += 1
            end -= 1
        return True

    # 回溯
    def backtracking(self, start_index, s):
        if self.path and start_index > len(s) - 1:
            self.res.append(self.path[:])
            return

        for i in range(start_index, len(s)):
            # 判断是否为回文串
            child_str = s[start_index:i + 1]
            if self.is_palindrome(child_str):
                self.path.append(child_str)
                self.backtracking(i + 1, s) # 递归纵向遍历,判断其余是否是回文串
                self.path.pop() # 回溯

    def partition(self, s: str) -> List[List[str]]:
        if not s:
            return []
        self.backtracking(0, s)
        return self.res
        

3. 子集问题

子集问题,回溯的经典使用场景。
回溯可以化成一种树状结构,子集、组合、分割问题都可以抽象为一棵树
子集问题与其他类型相比有个明显的区别,组合问题一般找到满足要求的结果即可,而集合则要找出所有的情况

LeetCode 78 子集
https://leetcode.cn/problems/subsets/

思路分析

递归停止条件
什么时候停下来?起始可以不加终止条件,因为 start_index >= nums.size(),本层for循环本来就结束了。
求取子集问题,不需要任何剪枝!子集就是要遍历整棵树

例子:分析 [1,2,3] 的子集
在这里插入图片描述

代码实现

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        def dfs(start_index, path):
            res.append(path[:])
            # 终止条件加不加都行
            if start_index >= len(nums):
                return
            for i in range(start_index, len(nums)):
                path.append(nums[i])
                dfs(i + 1, path)
                path.pop()

        res = []
        path = []
        dfs(0, path)
        return res

4. 排列问题

LeetCode 46
https://leetcode.cn/problems/permutations/

思路分析

这个问题与前面组合等问题的一个区别是使用过的后面还要在用,如[1,2]和[2,1],从集合的角度看是一个,从排列的角度看是两个

元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次,所以就不能用start_index,为此可以使用一个used数组来标记已经选择的元素

终止条件的判断:收集元素的数组path的大小和nums数组一样大的时候,说明找到了一个全排列,表示到达了叶子结点

在这里插入图片描述

代码实现

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        def dfs(path):
            if len(path) == len(nums):
                res.append(path[:])

            for i in nums:
                # 如果i已经被path收录,跳过
                if i not in path:
                    path.append(i)
                    dfs(path)
                    path.pop()

        res = []
        path = []
        dfs(path)
        return res
class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        def dfs(path, used):
            if len(path) == len(nums):
                res.append(path[:])
            for i in range(len(nums)):
                if used[i]:
                    continue
                used[i] = True
                path.append(nums[i])
                dfs(path, used)
                path.pop()
                used[i] = False

        res = []
        path = []
        used = [False] * len(nums)
        dfs(path, used)
        return res

5. 字母大小写全排列

LeetCode 784
https://leetcode.cn/problems/letter-case-permutation/

思路分析

这里的数字是干扰项,我们需要做的是过滤掉数字,只处理字母。另外还要添加个大小写转换的方法

由于每个字符的大小写形式刚好差了32,u因此在大小写转换时可以用加减32来进行转换和恢复

有点类似于上面的子集问题,多了一个判断是字母的处理

代码实现

class Solution:
    def letterCasePermutation(self, s: str) -> List[str]:
        def dfs(s_list, index):
            ans.append(''.join(s_list))
            # 此处递归终止条件可有可无
            if index >= len(s_list):
                return
            for i in range(index, len(s_list)):
                if s_list[i].isalpha():
                    s_list[i] = s_list[i].swapcase()
                    dfs(s_list, i + 1)
                    s_list[i] = s_list[i].swapcase()

        ans = []
        dfs(list(s), 0)
        return ans

另一种回溯的写法

class Solution:
    def letterCasePermutation(self, s: str) -> List[str]:
        def dfs(s_list, pos):
            while pos < len(s_list) and s_list[pos].isdigit():
                pos+=1
            if pos == len(s_list):
                res.append("".join(s_list))
                return
            
            s_list[pos] = s_list[pos].swapcase()
            dfs(s_list, pos+1)
            s_list[pos] = s_list[pos].swapcase()
            dfs(s_list, pos+1)
            
        res = []
        dfs(list(s), 0)
        return res

6. 单词搜索

LeetCode 79
https://leetcode.cn/problems/word-search/

思路分析

从上到下,从做到右遍历网络,每个坐标递归调用 check(i,j,k) 函数,其中i,j表示网格坐标,k表示word中的第k个字符。
如果能搜索到第k个字符,返回true,否则返回false。

check(i,j,k)执行情况分析

  • 坐标为 i,j 的字符和word中的第k个字符不相等,这条路径搜索失败,返回false
  • 如果搜索到了字符串的结尾,则找到了网格中的一条路径,这条路径正好可以组成字符串s

以上两种情况都不满足,把当前网络节点加入 visited 数组,表示节点已经访问过了
然后顺着当前网络坐标的四个方向继续尝试

注:python二维数组的初始化方法

used = [[False for _ in range(len(board[0]))] for _ in range(len(board))]

代码实现

class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        def dfs(i, j, k, used):
            if not 0 <= i < len(board) or not 0 <= j < len(board[0]) or board[i][j] != word[k] or used[i][j]:
                return False
            if k == len(word) - 1:
                return True
            used[i][j] = True
            ans = dfs(i + 1, j, k + 1, used) or \
                  dfs(i - 1, j, k + 1, used) or \
                  dfs(i, j + 1, k + 1, used) or \
                  dfs(i, j - 1, k + 1, used)
            used[i][j] = False
            return ans

        used = [[False for _ in range(len(board[0]))] for _ in range(len(board))]
        for i in range(len(board)):
            for j in range(len(board[0])):
                if dfs(i, j, 0, used):
                    return True
        return False
class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        def dfs(i, j, k):
            if not 0 <= i < len(board) or not 0 <= j < len(board[0]) or board[i][j] != word[k]:
                return False
            if k == len(word) - 1:
                return True
            board[i][j] = ""
            ans = dfs(i + 1, j, k + 1) or \
                  dfs(i - 1, j, k + 1) or \
                  dfs(i, j + 1, k + 1) or \
                  dfs(i, j - 1, k + 1)
            board[i][j] = word[k]
            return ans

        for i in range(len(board)):
            for j in range(len(board[0])):
                if dfs(i, j, 0):
                    return True
        return False

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/993171.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Autowired members must be defined in valid Spring bean (@Component|@Service|…)

报错如下&#xff1a; 自动注入对象必须定义在有效的spring bean内&#xff0c;也就是说只有本身作为bean的类才能注入其他对象。 修正方法&#xff1a;在BookTest上加上Component注解

基于STM32F103 实现按键状态机

文章目录 开发板开发环境前言按键消抖按键硬件原理图软件延时实现思路 实验目的代码按键状态按键信息按键相关定义按键底层配置及状态获取 总结 开发板 正点原子STM32F103ZET6战舰 开发环境 stm32cubeMX Clion 前言 在单片机使用按键时&#xff0c;为了消除按键的抖动&…

【随想】每日两题Day.3(实则一题)

题目&#xff1a;59.螺旋矩阵|| 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]]示例 2&#xff1a; …

Python进阶方法-Decorator装饰器

前言 在Python中&#xff0c;decorator&#xff08;装饰器&#xff09;是一种特殊的函数&#xff0c;主要用于修改或增强其他函数的功能。它可以在不修改原函数代码的情况下&#xff0c;通过在原函数的定义之前使用语法糖来对其进行修饰。 Decorator装饰器的作用 Decorator的…

端口信息收集

一、服务端口介绍 在渗透测试中对服务端口的收集非常重要&#xff0c;通过扫描服务开放的端口可判断对应开启的服务&#xff0c;通过所提供的这些服务的已知漏洞就可进行攻击。知名端口&#xff08;0-1023&#xff09;固定分配给某些服务的&#xff0c;动态端口&#xff08;10…

(高频面试1)Redis缓存穿透、缓存击穿、缓存雪崩

目录 一&#xff1a;缓存数据 1.1 应用场景 1.2&#xff1a;缓存数据出现的问题 1.2.1 缓存穿透 1.2.2 解决办法 1.2.3 缓存击穿 1.2.4 解决办法 1.2.5 缓存雪崩 1.2.6 解决办法 一&#xff1a;缓存数据 1.1 应用场景 数据库查询结果缓存是一种常见的缓存应用场景&a…

ansible 使用roles简单部署LAMP平台

目录 一、了解roles目录 二、基于构建LAMP平台创建roles目录 1、在192.168.115.148创建目录 2、书写php的测试页面 3、编写httpd角色的main.yml文件 4、编写mysql角色的main.yml文件 6、编写lamp的playbook 7、启动剧本 8、访问 一、了解roles目录 在Ansible中&#…

MultipartFile是什么

Multipart是一种file的类型 在我们进行文件上传时所发出的请求&#xff0c;我们页面对请求格式有明确的要求: 1.post提交表单方式 2.编码格式enctype必须是muitipart/form-data&#xff0c;这种格式适合传输数据量大的二进制数据文件 3.类型必须是file类 流程举例&#xf…

开源药店商城系统源码比较:哪个适合你的药品电商业务

在构建药品电商业务时&#xff0c;选择适合的药店商城系统源码是至关重要的决策之一。开源药店商城系统源码提供了快速入门的机会&#xff0c;但在选择之前&#xff0c;您需要仔细考虑您的需求、技术要求和可扩展性。本文将比较几个流行的开源药店商城系统源码&#xff0c;以帮…

追溯网络安全本源,原生安全范式框架v1.0外滩大会正式发布

9月8日&#xff0c;2023外滩大会网络安全分论坛在上海举行。论坛由蚂蚁集团和《信息安全研究》杂志社联合主办&#xff0c;以“开启原生安全范式&#xff0c;护航网络空间安全”为主题。会上蚂蚁集团与浙江大学网络空间安全学院重磅首发了一项引领性网络安全成果 “原生安全范式…

什么?你还不会打包给运维?!那快来看看

目录 一、首先是JAVA打包 我们只需要用maven打包即可&#xff0c;生成文件为xxxx.jar 二、Vue打包 而打包是运行 npm run build 只要把 dist 给运维就行了 PS&#xff1a;如果是线上运行&#xff0c;那你要注意 env这个文件&#xff01;&#xff01;&#xff01; ​编辑…

进程间通信(IPC)的方法:命名管道

使用管道时&#xff0c;一个进程的输出可成为另外一个进程的输入。 命名管道(Named pipe或FIFO)是一种类似于管道的特殊文件&#xff0c;但在文件系统上有一个名称&#xff0c;它允许以先进先出(FIFO, first in, first out)的方式存储有限数量的数据。它的使用类似于消息…

(STM32H5系列)STM32H573RIT6、STM32H573RIV6、STM32H573ZIT6嵌入式微控制器基于Cortex®-M33内核

一、应用 工业&#xff08;PLC、工业电机控制、泵和压缩机&#xff09; 智能家居&#xff08;空调、冰箱、冰柜、中央警报系统、洗衣机&#xff09; 个人电子产品&#xff08;键盘、智能手机、物联网标签、跟踪设备&#xff09; 智能城市&#xff08;工业通信、照明控制、数字…

2023秋冬系列丨追求本真的自然纯粹之美

2023年08月&#xff0c;上海&#xff0c;ZESH泽尚&#xff0c;中国轻奢皮具品牌宣布推出2023全新秋冬系列包袋&#xff0c;以“自然之道&#xff0c;纯粹之美”为主题重新定义东方美学。品牌建立之初就坚持贯彻东方美学设计与精湛制作工艺融合的理念。此次秋冬系列从中式禅宗学…

EagleSDR USB HAT FT600

给EagleSDR做了个USB 3.0的子卡&#xff0c;采用FT600方案&#xff0c;实物如下&#xff1a; 用FT600DataStreamerDemoApp测试&#xff0c;速度如下&#xff1a; 由于FT600是16bit的接口&#xff0c;如果用FT601的32bit接口&#xff0c;性能应该还会有大幅提升。 测试代码很简…

通过Idea或命令将本地项目上传至git

通过Idea或命令将本地项目上传至git 一、Git创建仓库 1、登录Gitee账号&#xff0c;点击新建 2、填写如下相关信息&#xff0c;点击创建 3、在此处可以复制项目链接 二、Idea配置和解绑git&#xff0c;提交项目 1、idea打开项目&#xff0c;操作如下 2、在弹框里选择…

【监控系统】Promethus整合Alertmanager监控告警邮件通知

【监控系统】Promethus整合Alertmanager监控告警邮件通知 Alertmanager是一种开源软件&#xff0c;用于管理和报警监视警报。它与Prometheus紧密集成&#xff0c;后者是一种流行的开源监视和警报系统。Alertmanager从多个源接收警报和通知&#xff0c;并根据一组配置规则来决定…

Linux中的用户和用户组

su和exit命令 su命令就是用于账户切换的系统命令&#xff0c;其来源英文单词:Switch User 语法: su [-] [用户名] - 符号是可选的&#xff0c;表示是否在切换用户后加载环境变量 &#xff08;建议带上&#xff09;参数:用户名&#xff0c;表示要切换的用户&#xff0c;用户名也…

怎么提高自己当众讲话的能力?

当众讲话是一项重要的沟通技能&#xff0c;它可以帮助你在各种场合中表达自己的观点、影响他人&#xff0c;并建立自信。虽然对很多人来说&#xff0c;当众讲话可能是一项挑战&#xff0c;但通过一些实践和技巧&#xff0c;你可以提高自己的当众讲话能力。下面是一些方法&#…