leetcode回溯问题总结 Python

目录

一、理论基础

二、例题

1. 排列问题

(1)无重复元素的排列问题

(2)有重复元素的排列问题

2. 组合问题

(1)无重复元素的组合问题

(2)无重复元素的子集问题

(3)有重复元素的子集问题

(4)元素之和等于固定值

(5)非递减子序列问题

3. 括号生成

4. 电话号码数字组合

5. 分割回文字符


一、理论基础

回溯算法的基本理论还是递归思想,所谓递归指的是函数在定义时调用函数本身,且在函数定义中需要包含终止条件,否则函数将无休止的调用自己直至内存不足。回溯算法是在递归思想的基础上制定的算法框架,用于解决一类问题。这类问题的共性是,问题本身可以被拆解为多个子问题,每个子问题都是类似的选择题。算法框架如下:

result = []
def backtrace(选择列表nums, 路径pre_list):if 满足结束条件:result.add(路径)returnfor 选择 in 选择列表:(有些问题需要对选择列表进行剪枝)做选择backtrace(剩余选择列表, 路径)撤销选择

每做出一个选择都是调用一次 backtrace 函数,终止条件是用于终止该条路径的搜索,选择列表是列出当前选择的所有可选项,有可能此时需要根据情况进行剪枝。切记回溯算法本身其实是一种暴力遍历法,并没有减少计算量,它常用来解决不定数量的 for 循环嵌套问题。需要注意的是在 python 中,result.add() 语句中需要深度拷贝“路径”,原因是这个路径本身是一直在变化的。实现深度拷贝可以使用 copy 包,可以使用 [:] 运算符,列表转字符串时也是深拷贝。

二、例题

1. 排列问题

(1)无重复元素的排列问题

给定一个不含重复数字的数组 nums ,返回其所有可能的全排列 。你可以按任意顺序返回答案。

示例:

输入:nums = [1,2,3]

输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

该问题最直观的想法是通过 n 个 for 循环迭代实现,但是这样会造成同一个元素被重复使用。所以该问题可以看做是在一个长度为 n 的列表里填数据,每填写一个数据都是做一个选择题,每个选择题所面临的选项都是 n 个值,但这个 n 的值需要加入剪枝,以避免同一个元素被重复使用。注意 python 中的赋值、浅拷贝和深拷贝。

import copydef run1_1(nums):def backtracking(nums, result, path):if len(path) == len(nums):result.append(copy.deepcopy(path))returnfor i in range(len(nums)):if nums[i] not in path:path.append(nums[i])backtracking(nums, result, path)path.pop()result = []backtracking(nums, result, [])return result

(2)有重复元素的排列问题

给定一个可包含重复数字的序列 nums ,按任意顺序返回所有不重复的全排列。

示例:

输入:nums = [1,1,2]

输出:[[1,1,2],[1,2,1],[2,1,1]]

该问题相比上一个问题,相当于删除了一部分排列,删除原因是这部分排列重复了,原本 [位置1,位置2] 与 [位置2,位置1] 代表两个不同的排列,现在位置1和位置2是相同元素,所以变成同一个排列了。最直观的想法就是,首先将 nums 排序,然后在 for 循环中将相邻重复元素去除,但是这样是错误的,原因是这样做相当于去除了重复元素,使得排列变短了。我们要做的是,如果把所有的选择过程看成一个决策树,那在同一层上不能选择两个相同的元素,但是在不同层上是可以选择两个相同元素的。这样就保证了在选择的过程中,同一个元素不能被重复使用,但两个值相同的元素可以被重复使用。注意 对于长度为 2 的 nums,nums[2] 会报错,但是nums[2:]则不会报错。

import copydef run1_2(nums):def backtracking(nums, result, path):if not nums:result.append(copy.deepcopy(path))returnfor i in range(len(nums)):if i == 0 or nums[i] != nums[i-1]:                   # 保证同一层不出现重复元素path.append(nums[i])backtracking(nums[:i]+nums[i+1:], result, path)  # 但是不同层会出现重复元素path.pop()result = []nums.sort()backtracking(nums, result, [])return result

2. 组合问题

(1)无重复元素的组合问题

给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。

示例:

输入: n = 4, k = 2

输出: [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4]]

该问题为组合问题,同样可以看做是 k 次选择问题,但是每次选择的选项需要基于前面的选择进行调整,具体来说就是每个选择都要避免前面选择已经遍历过的选项。

import copydef run2_1(n, k):def backtracking(n, k, start, result, path):if len(path) == k:result.append(copy.deepcopy(path))returnfor i in range(start, n):path.append(i)backtracking(n, k, i+1, result, path)path.pop()result = []backtracking(n, k, 0, result, [])return result

(2)无重复元素的子集问题

给你一个整数数组 nums ,数组中的元素互不相同。返回该数组所有可能的子集(幂集)。

解集不能包含重复的子集。你可以按任意顺序返回解集。

示例:

输入:nums = [1,2,3]

输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

该问题最直观的想法就是从 0 到 n 遍历所有长度,然后针对每个长度调用上面的函数,但是这样时间复杂度偏高,还有更简单的方法,在观察上面的回溯函数时,发现其实每调用一次回溯函数都会生成一个子集。

import copydef run2_2(nums):def backtracking(nums, start, result, path):result.append(copy.deepcopy(path))for i in range(start, len(nums)):path.append(nums[i])backtracking(nums, i+1, result, path)path.pop()result = []backtracking(nums, 0, result, [])return result

(3)有重复元素的子集问题

给你一个整数数组 nums,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集不能包含重复的子集。返回的解集中,子集可以按任意顺序排列。

示例:

输入:nums = [1,2,2]

输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

该问题同样是需要避免在选择树的同一层选择两个相同的数值(避免两个[1,2]),但是在不同层可以选择两个相同的数(必须包含[1,2,2])。其中,start 参数解决了组合问题,不设置终止条件解决了子集问题,事先排序加 if i == 0 or nums[i] != nums[i-1] 解决了重复元素问题。

import copydef run2_3(nums):def backtracking(nums, start, result, path):result.append(copy.deepcopy(path))for i in range(start, len(nums)):if i == 0 or nums[i] != nums[i-1]:path.append(nums[i])backtracking(nums, i+1, result, path)path.pop()result = []nums.sort()backtracking(nums, 0, result, [])return result

(4)元素之和等于固定值

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。

说明:所有数字(包括 target)都是正整数。解集不能包含重复的组合。

示例:

输入:candidates = [2,3,6,7], target = 7,

所求解集为:[[7],[2,2,3]]

该问题其实就是上面的无重复子集问题的扩展,只是在寻找子集时,还需要允许任意元素的重复使用,此外,在终止条件中,还需要进行剪枝,当某条搜索分支接下来不可能满足条件时,及时终止对该分支的搜索。

import copydef run2_4(nums, n):def sum(path):n = 0for i in path:n += ireturn ndef backtracking(nums, start, result, path):if sum(path) == n:result.append(copy.deepcopy(path))elif sum(path) > n:                      # 终止对不满足条件分支的搜索returnfor i in range(start, len(nums)):path.append(nums[i])backtracking(nums, i, result, path)  # 允许对某一元素的重复使用path.pop()result = []backtracking(nums, 0, result, [])return result

(5)非递减子序列问题

给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中至少有两个元素 。你可以按任意顺序返回答案。数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。

示例 : 

输入:nums = [4,6,7,7]

输出:[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]

该问题看起来与有重复元素的子集问题极为相似,都是遍历的每个路径都需要保存,都是包含重复元素,都是需要去除重复子集,但是实际解法有些不同,原因是该问题不能首先排序,因为排序会打乱原有的顺序,而本问题需要提取非递减序列。该问题是一个变长的回溯问题,所以在回溯函数中,每一步都需要保存,不需要终止条件,本题目要求序列长度大于等于 2,所以需要增加一个 if 判断。该问题同样要保证不包含重复子集,具体做法是保证在选择树的同一层里不能选择两个相同数值的数字,但是在不同层可以选择这两个数字,所以需要在遍历的 for 循环里面增加一个 if 判断,这里是第二种实现方式,第一种正如上面教程中是首先排序然后加入 if 判断。最后,我为了保证所选择子集是非递减序列,在选择数字时还需要增加一个 if 判断。

def run2_5(nums):def backtracking(result, path, nums, start):if len(path) >= 2:                 # 保证序列长度大于2result.append(path[:])         # 如果不使用[:] 则需要使用深拷贝函数copyfor i in range(start, len(nums)):if i > start and nums[i] in nums[start:i]:  # 保证当前层不使用相同数字,不同层可以使用相同数字,第二种实现方式,第一种是首先排序然后加入if判断continueif not path or path[-1] <= nums[i]:         # 保证非递减序列path.append(nums[i])backtracking(result, path, nums, i+1)path.pop()result = []backtracking(result, [], nums, 0)return result

3. 括号生成

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

示例 :

输入:n = 3

输出:["((()))","(()())","(())()","()(())","()()()"]

该问题可以看做是需要做 2n 次选择,每次选择都只有左括号和右括号两种选项,但是左括号和右括号并不是同等地位的,只有在还有未闭合的左括号时才可以加入右括号。此外,还需要找到判断某字符串括号列表是否满足条件的判断条件,判断条件是列表长度达到 2n,且所有左括号都已闭合。判断所有左括号是否都已闭合的方法是用一个变量记录未闭合左括号的数量。

import copydef run3(n):def backtracking(n, count, result, path):# 判断括号字符串列表是否满足终止条件if len(path) == 2*n:if count == 0:result.append(copy.deepcopy(path))return# 首先选择左括号path += "("count += 1backtracking(n, count, result, path)path = path[:-1]count -= 1# 在有未闭合左括号的前提下,选择右括号if count > 0:path += ")"count -= 1backtracking(n, count, result, path)path = path[:-1]count += 1result = []backtracking(n, 0, result, "")return result

4. 电话号码数字组合

在 9 宫格电话输入键盘中 2-9 数字分别对应着 3-4个字母,给定一组数字,输出所有可能的字母组合。

示例:

输入:digits = "23"

输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

该问题最直观的想法就是使用 for 循环的嵌套,例如上面示例,使用两个 for 循环的嵌套即可生成最终结果,但是该问题不能这样做的原因是数字字符串的长度不固定,导致 for 循环的嵌套层数不固定,无法实现最终编程。所以,本问题采用回溯算法,回溯函数的形参是当前翻译到哪个数字,所以单次翻译中回溯函数被调用的次数等于数字字符串的长度,除了数字 9 对应 4 个选择外,其它数字都对应 3 个选择。

def run4(digits):if not digits:return list()phoneMap = {"2": "abc","3": "def","4": "ghi","5": "jkl","6": "mno","7": "pqrs","8": "tuv","9": "wxyz",}def backtrack(index: int):if index == len(digits):combinations.append("".join(combination))  # 不需要深拷贝函数else:digit = digits[index]for letter in phoneMap[digit]:combination.append(letter)backtrack(index + 1)combination.pop()combination = list()combinations = list()backtrack(0)return combinations

5. 分割回文字符串

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

示例 :

输入:s = "aab"

输出:[["a","a","b"],["aa","b"]]

本问题看起来需要两步完成,第一步是切割字符串,第二步是判断字符串是否为回文字符串。第一步的切割字符串,这个问题看起来与子集问题很相似,但实际并不是,子集问题不用管字符串整体分割的完整性,本问题需要考虑完整性,即需要将整个字符串完整分割。每次分割都可以看做一个选择,所以本问题依然是不定数量的选择问题,所以本问题用回溯来实现分割,但与之前回溯不一样的是,本问题在调用回溯函数时,需要完成两个数值的选择,之前的应用都是仅仅完成一个数值的选择(其实本质上没有区别)。回溯函数的形参是每个选择或者分割的起始位置,函数体内完成的是本次选择或分割的终止位置的选择。回溯的终止条件肯定是当分割的起始位置超过字符串的总长时终止迭代。以此完成了字符串的分割,下一步是判断每个字符串是否为回文字符串,如果每个字符串都计算一次,很显然存在重复计算,所以这里使用动态规划缩减计算量。具体来说是定义一个二维的矩阵 dp(i, j),行 i 是起始位置,列 j 是终止位置,迭代条件很明显是如果 s[i] == s[j],且 dp[i+1, j-1] 为 true,则 dp[i, j] 为 true,为了解决 i == j 时的递推,初始化时整个矩阵初始化为 true。从递推公式中可以发现 dp[i, j] 依赖于 dp[i+1, j-1],所以递推顺序可以为 i 递减 j 递增。

def run5(s):# 动态规划部分n = len(s)f = [[True] * n for _ in range(n)]for i in range(n - 1, -1, -1):for j in range(i + 1, n):f[i][j] = (s[i] == s[j]) and f[i + 1][j - 1]# 回溯部分ret = list()ans = list()def dfs(i: int):if i == n:ret.append(ans[:]) # 不需要深拷贝函数returnfor j in range(i, n):if f[i][j]:ans.append(s[i:j+1])dfs(j + 1)ans.pop()dfs(0)return ret

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

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

相关文章

SpikingJelly笔记之泊松编码

文章目录 前言一、泊松编码的原理二、生成符合泊松分布的脉冲序列三、SpikingJelly中的泊松编码四、Lena图像的泊松编码与还原1.原始图像2.图像编码3.图像还原 总结 前言 记录SpikingJelly中泊松编码的使用方法&#xff0c;对图像数据进行编码与还原 一、泊松编码的原理 基于…

AI实景无人直播项目:开启自动直播新时代,一部手机即可实现增长

在当今社会&#xff0c;直播已经成为了人们日常生活中不可或缺的一部分。无论是商家推广产品、明星互动粉丝还是普通人分享生活&#xff0c;直播已经渗透到了各行各业。然而&#xff0c;传统直播方式存在着一些不足之处&#xff0c;如需现场主持人操作、高昂的费用等。近年来&a…

62、python - 全手写搭建 resnet50 神经网络

如果将上篇文章中涉及到的算法都自己手写完一遍后,我们开始尝试利用自己手写的算法,搭建一个完整的 resnet50 神经网络。 网络结构就参考这个链接中的网络结构,是在之前下载模型的章节中,下载的模型。 为了搭建一个完整的神经网络,定义一个 Resnet 的类,这个类就包含 r…

【大数据进阶第三阶段之Datax学习笔记】使用阿里云开源离线同步工具DataX 实现数据同步

【大数据进阶第三阶段之Datax学习笔记】阿里云开源离线同步工具Datax概述 【大数据进阶第三阶段之Datax学习笔记】阿里云开源离线同步工具Datax快速入门 【大数据进阶第三阶段之Datax学习笔记】阿里云开源离线同步工具Datax类图 【大数据进阶第三阶段之Datax学习笔记】使用…

MYSQL篇--sql优化高频面试题

sql优化 1 如何定位及优化SQL语句的性能问题&#xff1f;创建的索引有没有被使用到?或者说怎么才可以知道这条语句运行很慢的原因&#xff1f; 其实对于性能比较低的sql语句定位&#xff0c;最重要的也是最有效的方法其实还是看sql的执行计划&#xff0c;而对于mysql来说 它…

C/C++ 联合体

目录 联合体概述 联合体的内存分配 联合体大小计算 联合体概述 联合与结构非常的相似&#xff0c;主要区别就在于联合这两个字。 联合的特征&#xff1a;联合体所包含的成员变量使用的是同一块空间。 联合体定义 //联合类型的声明 union Un {char c;int i; }; //联合变量…

php将文本内容写入一个文件(面向过程写法)

一、封装2个函数&#xff0c;读写文件 /*** desc 读取文件内容* param string $filename* return array*/ private function readContent(string $filename): array {$text file_get_contents($filename);if (!$text) {return [];}$result json_decode($text,true);return…

x-cmd pkg | usql - SQL 数据库的通用交互界面

目录 简介首次用户功能特点竞品和相关作品进一步阅读 简介 “usql” 是一个基于命令行的数据库客户端工具&#xff0c;它允许用户连接和管理多种类型的数据库。usql可以在多个操作系统上运行&#xff0c;包括 Linux、macOS 和 Windows。它还具有插件系统&#xff0c;可以根据需…

Devtools热部署

1.添加Devtools jra <groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional> </dependency>2.添加plugin插件 <build><…

MySQL BufferPool精讲

缓存的重要性 我们知道&#xff0c;对于使用InnoDB作为存储引擎的表来说&#xff0c;不管是用于存储用户数据的索引&#xff08;包括聚簇索引和二级索引&#xff09;&#xff0c;还是各种系统数据&#xff0c;都是以页的形式存放在表空间中的&#xff0c;而所谓的表空间只不过…

vue绑定背景颜色或背景图片 和 nuxtjs动态设置background-image:

v-bind绑定样式表&#xff1a; ---------------------------------------------------------------------------------------------------- HTML写法: <div class"myItem" style"text-align:center; background-image:url(); background-size:auto 100% ;ba…

酷雷曼精彩亮相CMC 2023中国元宇宙大会,助力云上VR直播

12月23日&#xff0c;2023中关村论坛系列活动——CMC 2023中国元宇宙大会在石景山首钢园冰壶馆成功举办。酷雷曼VR作为元宇宙领域代表企业之一受邀出席会议&#xff0c;分享元宇宙技术研发成果及应用方案&#xff0c;并为大会提供VR直播技术支持。 大咖云集&#xff0c;共商元宇…

基于 HTTPS 协议配置 Git 连接 GitHub

文章目录 0.安装 Git1.注册 GitHub 账号2.配置 Git 的用户名和邮箱3.远程连接 GitHub 有两种传输协议4.基于 SSH 协议配置 Git 连接 GitHub5.基于 HTTPS 协议配置 Git 连接 GitHub5.1 创建 GitHub 个人访问令牌5.2 有两种方法将本地仓库和远程仓库关联起来5.2.1 第一种方法&…

Java-网络爬虫(一)

文章目录 前言一、网络爬虫1. 介绍2. 爬虫协议3. 法律法规 二、相关知识1. HttpClient2. Jsoup 三、综合案例1. 案例一2. 案例二 四、总结 前言 下篇&#xff1a;Java-网络爬虫(二) 在大数据时代&#xff0c;信息采集是一项重要的工作&#xff0c;而互联网中的数据是海量的&am…

Maven之私服

1 介绍 团队开发现状分析私服是一台独立的服务器&#xff0c;用于解决团队内部的资源共享与资源同步问题Nexus Sonatype公司的一款maven私服产品 下载地址&#xff1a;https://help.sonatype.com/repomanager3/download win版安装包&#xff1a;https://pan.baidu.com/s/1wk…

【我的Rust库】get_local_info 0.1.6发布

get_local_info是一个获取linux本地信息的Rust三方库&#xff0c;其目标是降低获取本地linux系统信息的难度。支持银河麒麟10、UOS、鸿蒙等国产系统。 项目维护&#xff1a;长期 当前版本&#xff1a;0.1.6 当前功能&#xff1a; 1.网络功能 1.1获取活动网卡信息&#xff…

集团企业OA办公协同平台建设方案

一、企业对协同应用的需求分析 实现OA最核心、最基础的应用 业务流转&#xff1a;收/发文、汇报、合同等各种审批事项的业务协作与办理 信息共享&#xff1a;规章制度、业务资料、共享信息资源集中存储、统一管理 沟通管理&#xff1a;电子邮件、手机短信、通讯录、会议协作等…

6.云原生之jenkins集成SonarQube

文章目录 搭建 SonarQube配置SonarQube创建sonar-token生成令牌查看jenkins暴露的NodePort端口创建Webhook 服务器将 SonarQube 配置添加到 ks-installer Jenkins集成SonarQube将 SonarQube 服务器添加至 Jenkinsjenkins配置SonarQubejenkins中配置SonarQube创建Jenkins凭证将 …

Django 10 表单

表单的使用流程 1. 定义 1. terminal 输入 django-admin startapp the_14回车 2. tutorial子文件夹 settings.py INSTALLED_APPS 中括号添加 "the_14", INSTALLED_APPS [django.contrib.admin,django.contrib.auth,django.contrib.contenttypes,django.contrib…

Eslint+Prettier

1.Eslint js验证的规则标准,Vue也有自己的独特的验证规则,vue-eslint-plugin属于vue自己的验证规则。 如果不想报错,可以在package.json/rules里面进行关闭,默认是开启的,默认缩进是两个空格。 2.Prettier - Code formatter 使写代码更加的美观 可选的配置项: 例如: module…