阅读 27

LeetCode每日一题——394. 字符串解码

*题目描述

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

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

示例:

s = "3[a]2[bc]", 返回 "aaabcbc". s = "3[a2[c]]", 返回 "accaccacc". s = "2[abc]3[cd]ef", 返回 "abcabccdcdcdef".

*本菜鸡的解法

*思路

看到题目想了好一会儿,才有点眉目,看到题目的标签中有“栈”,“深度优先搜索”这两个标签,总觉得不需要用递归,但实际上最后还是用了递归。

心路历程如下(包含错误心路历程):

  • 首先发现字符串前后可能出现不重复的字母,如:“ab3[cd]e”,于是思考首先将字符串前后的单独字母剔除,分别存为first和end,然后将中间的部分作为middle去单独处理,即first=“ab”,middle=“3[cd]”,end="e"
  • 对于中间涉及循环的部分,从前到后遍历,对于每遇到的一个“[”,都寻找其最大的一个重复部分,即对于“3[ab4[c]]”而言,第一个且最大的重复部分是“[ab4[c]]”,如果是“3[ab]4[c]”的话,第一个且最大的循环就是“[ab]”
  • 对每个部分重复其前面的重复次数,然后和first,end拼接起来返回
  • 在考虑循环之前应考察字符串中是否存在“[”,没有“[”则说明没有循环,直接返回即可
  • 当出现循环嵌套循环的时候就可以使用递归

问题出现在第二点上,当我把前后的不重复字母都删掉之后,认为内层的循环部分不会再出现有不循环的字母了,因此对于middle层的处理逻辑是遍历全部字符串,找到循环体之后就循环其前面的数字次,这个数字怎么定位呢?一开始因为我们已经去除了first和end,因此第一个循环的数字肯定是从第零位开始的,到“[”前为止,到这里都没问题,但是等到第二个循环体的时候,由于我默认不会再有不循环的字母,因此第二个循环体的循环次数我是将上一个循环体的结尾“]”第二个循环体的“[” 中间的这一部分强转为int类型作为循环次数的,而这显然就出现了问题,比如:

对于“3[z]2[2[y]pq4[2[jk]e1[f]]]ef”,首先我去除“ef”,剩下3[z]2[2[y]pq4[2[jk]e1[f]]],然后我循环3次z,到了第二个大循环体,循环2次[2[y]pq4[2[jk]e1[f]]],对于这个循环体里面,首先循环2次y,然后我的程序准备循环 “pq4” 次[2[jk]e1[f]],这明显就报错了。

后面我发现,我的单个函数的递归入口处已经判断并提取过了子串的first和end,那么我为啥不直接把后面的子串直接带入递归循环呢?于是我就把每次找到的第一个最大循环体和后面余下的子串分别放入递归中,成功AC。

*代码

1    class Solution: 
2        def decodeString(self, s: str) -> str: 
3            if '[' not in s: 
4                return s 
5            else: 
6                a_index=0 
7                b_index=len(s)-1 
8                while s[a_index].isalpha(): 
9                    a_index+=1 
10               first=s[:a_index] 
11               while s[b_index].isalpha(): 
12                   b_index-=1 
13               end=s[b_index+1:] 
14               middle=s[a_index:b_index+1] 
15               mid='' 
16               left_stack=[] 
17               for i in range(len(middle)): 
18                   if middle[i] == '[': 
19                       left_stack.append(i) 
20                       continue 
21                   if middle[i] == ']': 
22                       a = left_stack.pop(-1) 
23                       if len(left_stack) == 0: 
24                           mid += int(middle[0:a]) * self.decodeString(middle[a + 1:i]) 
25                           mid+=self.decodeString(middle[i+1:]) 
26                           break 
27               return first + mid + end
复制代码

1、首先判断串中是否含有“[”,如果不含说明没循环,直接返回,如果有则下一步
2、6-14行通过分别从前向后和从后向前的判断字符,找到了字符串中的first和end和middle部分,其中first和end都是直接的字符串,可以直接拼接,middle部分需要循环
3、借助一个栈找到middle中第一个且最大的循环,然后将这部分和剩下的部分分别送入第一步,进行递归

*举例

以字符串“2[2[y]pq4[jk]]3[z]ef”为例,解释一下程序思路。

1、判断有“[”,开始寻找first,middle,end
2、first='',middle='2[2[y]pq4[jk]]3[z]',end='ef'
3、middle中第一个且最大的一个循环是'2[2[y]pq4[jk]]',剩余部分是'3[z]',分别继续判断
4、对于3中的第一个循环,分解得first='',middle='2[y]pq4[jk]',end=''
5、middle中第一个且最大的一个循环是'2[y]',剩余部分是'pq4[jk]',分别继续判断
6、第一个中first='',middle='2[y]',end='',其中middle的'y'在下一个递归中没有'[',因此直接返回,故这里是返回'yy'
7、剩余部分的first='pq',middle='4[jk]',end='',其中middle部分在下一个递归中没有'[',因此直接返回,故这里是返回'pqjkjkjkjk'
8、结合6、7两步的结果,对于4得到的结果是'yypqjkjkjkjk',返回的结果是'yypqjkjkjkjkyypqjkjkjkjk'
9、对于3中的剩余部分,其first='',middle='3[z]',end='',其中middle部分在下一个递归中没有'[',因此直接返回,故这里是返回'zzz'
10、3得到的结果是'yypqjkjkjkjkyypqjkjkjkjkzzz'
11、结合2中的first和end,得到最后的输出'yypqjkjkjkjkyypqjkjkjkjkzzzef'

*分析

可以发现我的算法中实际上从前到后遍历了不止一遍字符串,在每次判断middle的过程中,先遍历了一部分找到middle中第一个最大的循环体,然后这一部分在下一层递归中又经历了first和end的遍历,所以在时间复杂度上比较高,在最差的情况下应该是会达到O(N!)级别,例如:2[3[4[5[6[7[8[9[a]b]c]d]e]f]g]h]i

而在空间复杂度上由于递归传递的数据太多,导致空间复杂度也很高,所以这个算法实际上很差,基本上只是能完成需求的级别,最后的结果就是

执行结果:
通过

执行用时 :48 ms, 在所有 python3 提交中击败了55.68%的用户

内存消耗 :13.9 MB, 在所有 python3 提交中击败了5.26%的用户
复制代码

唉还是菜

*大佬的解法

自己的算法解完了发现不成,还是得看大佬的解法,这里直接贴大佬的解法链接,并附上自己按照大佬解法写出的代码

**链接

leetcode-cn.com/problems/de…

**代码

1    class Solution: 
2        def decodeString(self, s: str) -> str: 
3            def dfs(s, index, length): 
4                multi = 0 
5                res = '' 
6                while index!=length: 
7                # for i in range(index, length): 
8                    if s[index].isalpha(): 
9                        res += s[index] 
10                   elif s[index].isdigit(): 
11                       multi = multi * 10 + int(s[index]) 
12                   elif s[index] == '[': 
13                       r=dfs(s, index + 1, length) 
14                       res += multi*r[0] 
15                       index=r[1] 
16                       multi=0 
17                   elif s[index] == ']': 
18                       return res,index 
19                   index+=1 
20               return res 
21           return dfs(s,0,len(s))
复制代码

**分析

这样代码从前到后只需要遍历一遍即可,时间复杂度O(N),空间复杂度O(N)

这个代码是看了大佬的解法后自己理解一遍后重新写的,其中感到最trick的地方就是重复次数的读取方式,也就是multi,将其初始化为0,然后如果读到了数字,就将其本身乘10然后和当前数字相加,这刚好能够满足任意位数的数字,而且有多少顺序遍历过去就可以了,不像我之前的想法是先找到是数字的部分,然后整体一个int()强转,这太费时间了,大佬不亏是大佬。用了大佬的思路后,时间真是蹭蹭的减少呀

另外的一个trick的地方应该是python的缘故,可以在方法中返回不同类型的结果,像这里遇到']'的时候,返回了字符串和'['的指针,而在最后全部遍历完之后,返回的却是字符串,这里python不需要像Java一样定义一个专门的返回结构,所以很方便

执行结果:通过

执行用时 :24 ms, 在所有 python3 提交中击败了99.89%的用户

内存消耗 :13.8 MB, 在所有 python3 提交中击败了5.26%的用户
复制代码

*End

关注下面的标签,发现更多相似文章
评论