Leetcode 刷题 (21)堆栈的应用:字符串处理技术(分类有序,避免冗余,递归解法,O(n)复杂度)

题目

394. 字符串解码

Leetcode 刷题 (21)堆栈的应用:字符串处理技术(分类有序,避免冗余,递归解法,O(n)复杂度)_第1张图片

难度:中等(这道题花了我好长时间,最后是修修补补才做出来……如果是简单题,我就哭了)

题目分析:这道题,由于每次读取一个字符,大部分时候不能马上确定最后的形式,因此,需要借助缓存结构;而由于中括号“[ ]”有嵌套(如“3[a2[c]]”,得先转换成“cc”, 在跟“a”拼接),后读取的要先处理,因此选择栈来辅助存储。
于是,剩下的问题便是怎么使用栈来存储数据的问题,应该存哪些, 怎么存。解法一是我自己苦思冥想,结合评测的反馈,修修补补做出来,但是太冗长了,部分代码可以统一处理,而我拆分了……说明没有把握好问题的本质;解法二是参考的标准答案,简洁明了,值得学习。

1. 解法一:冗长版

class Solution:
    def decodeString(self, s: str) -> str:
        # 数字肯定要进栈
        # 方括号是标志
        # 方括号中间是一个基元,怎么拼合
        if s == "":
            return s
        nums = "0123456789"
        
        my_stack = []
        i = 0
        while i < len(s):
            if s[i] in nums: 
                num = s[i]
                j = i+1
                while j < len(s) and s[j] in nums:
                    num = num + s[j]
                    j += 1
                my_stack.append(num)
                i = j
                continue
            if s[i] == "[":
                my_stack.append(s[i])
                temps = "" # 准备存储后面
                j = i+1 # 下一个元素
                while j < len(s) and s[j] != "]" and s[j] not in nums:
                    temps += s[j]
                    j+=1
                    
                my_stack.append(temps)
                i = j
                continue # 如果是右括号,下面会处理;如果是数字,上面处理
          

                
            if s[i] == "]":
                # 说明是嵌套
                temp = ""
                while my_stack[-1]!= "[":
                    temp = my_stack.pop() + temp
                # 出来后,说明到头了
                my_stack.pop() # 去掉左括号
                temp = temp*int(my_stack.pop())
                my_stack.append(temp)
                i += 1
                continue
            my_stack.append(s[i])
            i += 1
            
        ans = ""
        while my_stack:
                ans = my_stack.pop() + ans
                
        return ans

1.1 运行结果:

Leetcode 刷题 (21)堆栈的应用:字符串处理技术(分类有序,避免冗余,递归解法,O(n)复杂度)_第2张图片

1.2 分析:

分析留待解法二介绍后,进行比较。

2. 解法二: 简洁版

class Solution:
    def decodeString(self, s: str) -> str:
    	my_stack, multi, res = [], 0, ""

        for c in s:
            if "0" <= c <= "9":
                multi = multi * 10 + int(c) # 处理两位数以上的方法
            elif c == "[":
                my_stack.append((multi, res))
                multi, res = 0, ""
                # res = "" # 清空等下次使用
            elif c == "]":
                cur_multi, last_res = my_stack.pop()
                res = last_res + cur_multi*res
            else:
                res += c # 针对普通字符或是[]中间的字符
        return res

2.1 运行结果:

Leetcode 刷题 (21)堆栈的应用:字符串处理技术(分类有序,避免冗余,递归解法,O(n)复杂度)_第3张图片

Leetcode 刷题 (21)堆栈的应用:字符串处理技术(分类有序,避免冗余,递归解法,O(n)复杂度)_第4张图片

2.2 分析:

很奇怪Leetcode在晚上这么的不稳定,到了后面几次时间才稳定。 最后稳定的运行时间,跟我的算法也差不多……说明我自己的方法,虽然代码看起来复杂,不过速度是过得去的。

解法一相比解法二的劣势:

  1. 寻找中括号“[ ]” 中间的字符,解法二能在一个统一的框架下处理,即res +=c, 而解法一,是以“[” 为起点, 再次构建了一个while循环来实现,如是,不得不再判断是否有数字或是“]”,这无形中增加了代码的冗余。解法一的方法值得我们学习,判断数字,字母,“[” 和 “]”各由一部分代码负责,互相之间不会嵌套
  2. 解法一对于堆栈的使用太单调了,不加区分把数字,字母和括号都往堆里面塞,从而造成了处理的复杂;比如,解法一,每次发现了“]”后,需要弹出一大堆东西,包括只是起指示意义的“[”。解法二巧妙的使用了元组,存进去的数据互相有联系,于是,弹出一次,就能算出一组中括号圈出的字符串
  3. 由于解法二的栈设置合理,因此,每次处理到“]”时,前面的字符串已经拼接正确,存储在res中,而方法一,由于栈的存储没有充分考虑前后联系,于是最后还要多一个全部元素的拼接操作。比如3[a2[c]]4[f], 使用方法一,在栈中最后存储的是, [accaccacc,ffff], 于是需要最后再拼接一次, 解法二则是一步到位。
  4. 判断数字的小技巧:“0” <= c <= “9”, 这是利用他们的Ansic编码
  5. 使用 multi = multi * 10 + int( c ) 将字符表示的数字转换为其对应的int型数字

3. 解法三:利用递归来求解

能用栈的地方,自然就可以编写递归程序。

class Solution:
    def decodeString(self, s: str) -> str:
		# 尝试编写dfs 主要还是处理 “[” 后面的地方,这部分具有递归的性质
       
        def dfs(s, i):
            res, multi = "", 0
            while i < len(s):
                if '0' <= s[i] <= "9":
                     multi = multi * 10 + int(s[i])
                elif s[i] == "[":
                    i, new_res = dfs(s, i+1) # 此时进入递归,相当于保存了 multi 和 res
                    res = res + multi * new_res
                    multi = 0
                elif s[i] == "]":
                    return i, res
                else:
                    res += s[i]
                i += 1
            return res #最后答案
        return dfs(s,0)

3.1 运行结果:

Leetcode 刷题 (21)堆栈的应用:字符串处理技术(分类有序,避免冗余,递归解法,O(n)复杂度)_第5张图片

Leetcode 刷题 (21)堆栈的应用:字符串处理技术(分类有序,避免冗余,递归解法,O(n)复杂度)_第6张图片
不是很稳定。

3.2 分析:

这道题写递归很考验对于栈的应用的理解。我自己是先写的非递归程序,然后,再改写出递归程序。

方法不是很直观,但是很有启发意义。

1.明确栈需要存储哪些信息,这道题中,我们一旦遇到 “[”, 就需要把之前已经有的字符 res 和 数字倍数储存起来
2. 递归的本质是利用 函数帧 来储存当前程序的各种变量,于是,很自然的,运用递归的地方,就是程序遇到 “[” 的地方,这样 res 和 数字倍数 multi 就被保存起来;等程序返回递归结果,这部分数据就能用上,对应方法二从栈中弹出数据
3. 递归的终点应该是什么呢? 在处理字符串的时候,我们是以右中括号 “]” 为终点,它的出现,标明前面的字符串已经完整,可以运用倍数扩充;于是,这儿返回res, 当然便忘了,是共用一个指标 i, 得返回给调用程序
4. 步骤 3 中的返回到了 步骤 2, 刚好实现了一对方括号中间的字符的解码,同时还获得了继续往下处理的下标。

小结: 这里的递归,其实就是把每对中括号看成是缩小了的原问题, 随着每对中括号的完成,逐步往上完成嵌套的字符串恢复。最后while循环外的 return res, 是最后答案,因为此时输入串已经处理完

3.3 易错点:

  1. 每次进入函数,res 和 multi 两个变量都要初始化,因为进入递归,就要处理新的字符, 之前的字符结果,已经保存在函数帧,不需要我们自己储存
  2. 在递归返回后,字符通过 multi 扩充后,multi应该清0,否则,当两个左中括号 “[” 是平行关系的话,数字处理会出错。

你可能感兴趣的:(刷题,学习札记)