本文为Python算法题集之一的代码示例

题22:括号生成

1. 示例说明

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

    示例 1:

    输入:n = 3
    输出:["((()))","(()())","(())()","()(())","()()()"]
    

    示例 2:

    输入:n = 1
    输出:["()"]
    

    提示:

    • 1 <= n <= 8

2. 题目解析

- 题意分解

  1. 本题是计算n对括号可能生成的字符串
  2. 标准解法为通过递归进行回溯求解
  3. 回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法。

- 优化思路

  1. 通常优化:减少循环层次

  2. 通常优化:增加分支,减少计算集

  3. 通常优化:采用内置算法来提升计算速度

  4. 分析题目特点,分析最优解

    1. 可以在回溯算法中使用堆栈方式,也可以使用切片方式

    2. 可以考虑存储过程值列表,逐步扩充到n对括号


- 测量工具

  • 本地化测试说明:LeetCode网站测试运行时数据波动很大【可把页面视为功能测试】,因此需要本地化测试解决数据波动问题
  • CheckFuncPerf(本地化函数用时和内存占用测试模块)已上传到CSDN,地址:Python算法题集_检测函数用时和内存占用的模块
  • 本题本地化超时测试用例自己生成,详见章节【最优算法】,代码文件包含在【相关资源】中

3. 代码展开

1) 标准求解【堆栈回溯】

使用列表作为缓存,使用pop实现回溯

页面功能测试,性能卓越,超越94%Python算法题集_括号生成-LMLPHP

import CheckFuncPerf as cfp

class Solution:
 def generateParenthesis_base(self, n):
     if n == 0:
         return []
     result, path = [], []
     def dfs_backtrack(left, right):
         if right > left or left > n or right > n:
             return
         if left == n and right == n:
             result.append("".join(path))
             return
         path.append("(")
         dfs_backtrack(left + 1, right)
         path.pop()
         path.append(")")
         dfs_backtrack(left, right + 1)
         path.pop()
     dfs_backtrack(0, 0)
     return result

aSolution = Solution()
result = cfp.getTimeMemoryStr(aSolution.generateParenthesis_base, ilen)
print(result['msg'], '执行结果 = {}'.format(len(result['result'])))

# 运行结果
函数 generateParenthesis_base 的运行时间为 8185.84 ms;内存使用量为 234904.00 KB 执行结果 = 2674440

2) 改进版一【切片回溯】

使用列表作为缓存,使用切片实现回溯

页面功能测试,马马虎虎,超过48%Python算法题集_括号生成-LMLPHP

import CheckFuncPerf as cfp

class Solution:
 def generateParenthesis_ext1(self, n):
     if n == 0:
         return []
     self.result, self.track = [], ''
     def dfs_backtrack(left, right):
         if left > right:
             return
         if left < 0 or right < 0:
             return
         if left == 0 and right == 0:
             self.result.append(self.track)
             return
         self.track += '('
         dfs_backtrack(left - 1, right)
         self.track = self.track[:-1]
         self.track += ')'
         dfs_backtrack(left, right - 1)
         self.track = self.track[:-1]
     dfs_backtrack(n, n)
     return self.result

aSolution = Solution()
result = cfp.getTimeMemoryStr(aSolution.generateParenthesis_ext1, ilen)
print(result['msg'], '执行结果 = {}'.format(len(result['result'])))

# 运行结果
函数 generateParenthesis_ext1 的运行时间为 10389.33 ms;内存使用量为 236416.00 KB 执行结果 = 2674440

3) 改进版二【列表缓存+逐层扩充】

使用多维列表结构保存0-n对括号组合的组合列表,逐层扩充组合到n个

页面功能测试,马马虎虎,超过68%Python算法题集_括号生成-LMLPHP

import CheckFuncPerf as cfp

class Solution:
 def generateParenthesis_ext2(self, n):
     if n == 0:
         return []
     map_results = []
     map_results.append([None])
     map_results.append(["()"])
     for iIdx in range(2, n+1):
         tmpResult = []
         for jIdx in range(iIdx):
             now_list1 = map_results[jIdx]
             now_list2 = map_results[iIdx-1-jIdx]
             for item1 in now_list1:
                 for item2 in now_list2:
                     if item1 == None:
                         item1 = ""
                     if item2 == None:
                         item2 = ""
                     tmpItem = "(" + item1 + ")" + item2
                     tmpResult.append(tmpItem)
         map_results.append(tmpResult)
     return map_results[n]

aSolution = Solution()
result = cfp.getTimeMemoryStr(aSolution.generateParenthesis_ext2, ilen)
print(result['msg'], '执行结果 = {}'.format(len(result['result'])))

# 运行结果
函数 generateParenthesis_ext2 的运行时间为 967.22 ms;内存使用量为 233628.00 KB 执行结果 = 2674440

4. 最优算法

根据本地日志分析,最优算法为第3种方式【列表缓存+逐层扩充】generateParenthesis_ext2

本题测试数据,似乎能推出以下结论:

  1. 组合的回溯求解中,列表切片操作性能低于pop操作
  2. 递归为重型结构,如果能用非递归的算法直接实现,性能会好很多
ilen = 10
aSolution = Solution()
result = cfp.getTimeMemoryStr(aSolution.generateParenthesis_base, ilen)
print(result['msg'], '执行结果 = {}'.format(len(result['result'])))
result = cfp.getTimeMemoryStr(aSolution.generateParenthesis_ext1, ilen)
print(result['msg'], '执行结果 = {}'.format(len(result['result'])))
result = cfp.getTimeMemoryStr(aSolution.generateParenthesis_ext2, ilen)
print(result['msg'], '执行结果 = {}'.format(len(result['result'])))

# 算法本地速度实测比较
函数 generateParenthesis_base 的运行时间为 8185.84 ms;内存使用量为 234904.00 KB 执行结果 = 2674440
函数 generateParenthesis_ext1 的运行时间为 10389.33 ms;内存使用量为 236416.00 KB 执行结果 = 2674440
函数 generateParenthesis_ext2 的运行时间为 967.22 ms;内存使用量为 233628.00 KB 执行结果 = 2674440

5. 相关资源

本文代码已上传到CSDN,地址:Python算法题源代码_LeetCode(力扣)_括号生成

一日练,一日功,一日不练十日空

may the odds be ever in your favor ~

03-04 19:53