2024.8.16 Python,回溯算法,深度优先搜索实现全排列,栈解码字符串

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

示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
这个题其实有最简单的办法就是直接调用内部函数,所以之后工程实际操作的时候可以查这个代码直接使用

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        return list(itertools.permutations(nums, len(nums)))

大概逻辑就是,返回列表,列表里的元素是迭代工具中的全排列,括号里的参数是原列表,加原列表长度。但是这样子并不能学习到回溯算法的精神,其实这个题和上一个题很像,都是使用回溯算法,甚至和岛屿问题那个题也很像,都需要在满足条件的基础上迭代。
remaining[:i] + remaining[i+1:] 是一种用于在 Python 中移除列表中某个元素的常见操作。具体来说,这个表达式的作用是生成一个新列表,这个新列表中去掉了 remaining 列表中索引为 i 的元素。
现在的问题就是,能看懂代码了,但是你如果从新开始做这个题,我凭什么能想出来这样的办法。回溯算法是for循环的嵌套。n个for循环的嵌套,难怪这么难想,两个for循环就已经有点难想了。

回溯算法的思想,回溯三部曲:1.递归函数的参数与返回值,2.确定终止条件,3.单层递归逻辑

一维数组path,二位参数result,回溯参数n,指的是可以选择的个数,组合大小k,指的是目标组合是挑几个数字,startIndex,用来确定第一个子节点的参数
那么终止条件就为,pathsize==k,那就达到目标了,可以输出了
for(i=startIndex,i++,i<n)
path+letter
backtrack
pop
那么就有用列表递归:

class Solution:
	def permute(self,nums:List[int])->List[List[int]]:
		res=[]
		def backtrack(path,remaining):
			if not remaining:
				res.append(path)
				return
			for i in range(len(remaining)):
				backtrack(path+[remaining[i]],remaining[:i]+remaining[i+1:])
		backtrack([],nums)
		return res

讲道理,让我再抄一遍仍然是觉得我自己写不出来这么妙的代码,我现在能做的就是逐行分析这个代码的逻辑,根据上面的关于回溯的说法,回溯需要先确定具体的参数,在这个题中,参数个数就是len(nums)也就是len(remaining)这两个是一样的。那么循环的层数k就是not remaining,列表里没东西的时候就可以弹出了,和电话号码字母组合那道题非常的相似,那么叉树的第一层就是让i在remaining的取值中取012,这样,然后取了之后直接加在path里,然后在remaining里剔除remaining[i]也就是remaing[:i]+remaining[i+1:]操作,整个操作一气呵成。非常的合适。
第二种解法:
使用了深度优先搜索,其实深度优先搜索也是一种迭代方法,所以当时岛屿问题的深度优先搜索再接下来进入回溯问题就非常合适

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        ans, path = [], []
        on_path = [False] * len(nums)

        def dfs():
            if len(path) == len(nums):
                ans.append(path.copy())
                return
            for i, x in enumerate(nums):
                if not on_path[i]:
                    path.append(x)
                    on_path[i] = True
                    dfs()
                    on_path[i] = False
                    path.pop()
        
        dfs()
        return ans

其实这个也非常的贴合回溯算法的核心思想,我来盘一下这个思路,首先初始化ans,path,他这个on_path其实是一个长为nums的布尔变量,用来进行像岛屿那个1一样的标记。然后进入函数,先写弹出,当path长度等于nums的长度的时候弹出return,进入for循环,i取所有可能性,其实这里的x可以不取nums,直接在path的时候append(nums[i])一样的,然后他这里为什么是dfs,就是因为加了一个on_path的判断,也就是说如果是0,那说明没用过,那就要去置1,然后做dfs,如果用过了,那就跳过了。接下来就是归位,dfs做完之后要on_path和path复位。
我最开始有个想法就是说on_path这个变量好像没有和列表强相关,也就是说如果是字典的话,本来就是绑定的,但是这里是一个独立的列表,那这样的改变有什么用,其实连接就是这个i,用i进行强相关。

***PS1:***这个代码中,添加了pop()以及path.copy()这样的代码,从而我理不清这个逻辑了,他出问题的原因在于,函数中没有传递出去path,所以导致每次path都是空的。代码修改后如下:

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        ans = []
        on_path = [False] * len(nums)

        def dfs(path):
            if len(path) == len(nums):
                ans.append(path)
                return
            for i, x in enumerate(nums):
                if not on_path[i]:
                    on_path[i] = True
                    dfs(path + [x])  # 传递 path 的新副本
                    on_path[i] = False

        dfs([])
        return ans

这样就避免使用path.copy()这样的逻辑了,我不想去探究这个逻辑是什么样子了
第三种解法:
使用最朴素的交换思想加回溯思想

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        def backtrack(start = 0):
            if start == len(nums):
                result.append(nums[:])
                return
            for i in range(start , len(nums)):
                nums[start],nums[i] = nums[i], nums[start]
                backtrack(start + 1)
                nums[start],nums[i] = nums[i],nums[start]
        result = []
        backtrack()
        return result

这个题我完全理解了,这个题的关键在于,在for i in range(start,len(nums))这里,这里的逻辑是,第一层的时候,通过交换,把需要锁定的数字换到第一位,其余位置不动,然后再下一层的时候,把更换的数字变为第二个,同时固定第一个和第二个,以此类推,到最后一个的时候,做的交换就是一个3位交换了,也就是固定1,交换23,固定2,交换13,固定3,交换12。有点像九连环,然后注意不要忘记return

2.给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

示例 1:

输入:s = “3[a]2[bc]”
输出:“aaabcbc”
示例 2:

输入:s = “3[a2[c]]”
输出:“accaccacc”
示例 3:

输入:s = “2[abc]3[cd]ef”
输出:“abcabccdcdcdef”
示例 4:

输入:s = “abc3[cd]xyz”
输出:“abccdcdcdxyz”

解法一:递归法:
朴素思想,就是遇见谁就针对处理,同时带一点回溯迭代,因为括号解码可能嵌套,用回溯来去控制这份不确定

class Solution:
    def decodeString(self,s:str)->str:
        def func(index):
            mul=0
            result=[]
            while index<len(s):#指针
                if s[index].isdigit():
                    mul=10*mul+int(s[index])
                elif s[index]=='[':
                    index,decode=func(index+1)
                    result.append(decode*mul)
                    mul=0
                elif s[index]==']':
                    return index,''.join(result)
                else :
                    result.append(s[index])
                index+=1
            return ''.join(result)
        return func(0)

其中,num=num*10+int(s[index])是一个很好的处理,对于字符串来说,给他计算一个完整的数字,第二个elif里index,decoded_string这个输出是和第三个elif关联的,不和后面关联,所以可以有不同的return
num置0很关键,不然之后的num没法用了。再重复调用的时候,函数中的num是独立的,是形式参数,不理他。
关于这个’'.join(result) 是这样用的:

result = ['Hello', ' ', 'World']
final_string = ''.join(result)
print(final_string)  # 输出: 'Hello World'

首先result为一个字符串列表,join其实目的是让三个字符串列表变成一个字符串,但是result仍然是列表,这也就解答了我的疑问,每一次append其实都是在给列表增加新的字符串,而join的出现是为了满足这个题。

***PS1:***这里的join是’‘.join(result),join是方法,作用对象是result,’'指的是用什么连起来,这里是空的,所以就什么都不连了。
PS2这里的嵌套函数可以访问其外部函数的变量,这是通过作用域链实现的。在你的代码中,func 能够访问 decodeString 中的 s,因此可以直接使用 s 进行字符串解析,而无需显式传递 s 作为参数。

解法二:辅助栈法

class Solution:
    def decodeString(self, s: str) -> str:
        stack, res, multi = [], "", 0
        for c in s:
            if c == '[':
                stack.append([multi, res])
                res, multi = "", 0
            elif c == ']':
                cur_multi, last_res = stack.pop()
                res = last_res + cur_multi * res
            elif '0' <= c <= '9':
                multi = multi * 10 + int(c)            
            else:
                res += c
        return res

栈这个东西也太牛逼了,他就像放盘子一样,他可以像科学计算器一样,智能的横向计算括号问题,也就按照人类的计算方式,去计算科学计算器的时候,肯定是先浏览式子,然后再找最里面的括号,然后再一层一层往外走,高效,但是如果嵌套太多的话,人类会迷失在里面,而有了栈这个东西,再多的括号都无所畏惧,他不需要将式子迭代嵌套,而是用栈存起来,等到遇见了后括号,栈就发现,哦,这下该计算了。
逻辑如下:
先计算系数,这个是一样的。遇见左括号就存一组res和multi,然后置0,遇见别的就直接记到res里,因为左括号操作的时候,已经记录了之前的res,遇见右括号的时候,就开始操作,用pop函数取最近的一组栈,用来操作现有的res,操作完成以后res就已经更新了,最后输出即可,天才。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值