LeetCode 22. 括号生成【字符串,回溯;动态规划】中等

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。

由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。

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

示例 1:

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

示例 2:

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

提示:

  • 1 <= n <= 8

解法 暴力递归(生成 2 2 n 2^{2n} 22n 个序列)

每个位置可以使用 ( 或者 ) ,因此总共有 2 2 n 2^{2n} 22n 个序列。我们全部生成出来,然后检查每个是否有效即可。

为了生成所有序列,我们可以使用递归。长度为 n n n 的序列就是在长度为 n − 1 n - 1 n1 的序列前加一个 ()

为了检查序列是否有效,我们遍历这个序列,并使用一个变量 balance \textit{balance} balance 表示左括号的数量减去右括号的数量。如果在遍历过程中 balance \textit{balance} balance 的值小于零,或者结束时 balance \textit{balance} balance 的值不为零,那么该序列就是无效的,否则它是有效的。

class Solution {
private:vector<string> ans;bool valid(const string& s) {int balance = 0;for (char c : s) {if (c == '(') ++balance;else --balance;if (balance < 0) return false;}return balance == 0;}void dfs(string& s, int n) {if (n == s.size()) {if (valid(s)) ans.push_back(s); // 检查是否有效序列return;}s += '(';dfs(s, n);s.pop_back();s += ')';dfs(s, n);s.pop_back();}
public:vector<string> generateParenthesis(int n) {string current;dfs(current, n * 2);return ans;}
};

复杂度分析:

  • 时间复杂度: O ( 2 2 n n ) O(2^{2n}n) O(22nn) ,对于 2 2 n 2^{2n} 22n 个序列中的每一个,我们用于建立和验证该序列的复杂度为 O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n) ,除了答案数组之外,我们所需要的空间取决于递归栈的深度,每一层递归函数需要 O ( 1 ) O(1) O(1) 的空间,最多递归 2 n 2n 2n 层,因此空间复杂度为 O ( n ) O(n) O(n)

解法2 回溯剪枝

方法一还有改进的余地:我们可以只在序列仍然保持有效时才添加 (, ) ,而不是像方法一那样每次添加。我们可以通过跟踪到目前为止放置的左括号和右括号的数目来做到这一点:

  • 如果已用左括号数量不大于 n n n ,我们可以放一个左括号。
  • 如果已用左括号的数量大于已用右括号数量,我们可以放一个右括号。
class Solution {
private:vector<string> ans;void dfs(const string& s, int l, int r) {if (l < 0 || l > r) return;if (l == 0 && r == 0) {ans.push_back(s);return;}dfs(s + '(', l - 1, r);dfs(s + ')', l, r - 1);}
public:vector<string> generateParenthesis(int n) {dfs("", n, n);return ans;}
};

复杂度分析:这依赖于理解 g e n e r a t e P a r e n t h e s i s ( n ) generateParenthesis(n) generateParenthesis(n) 中有多少个元素。这个分析超出了本文的范畴,但事实证明这是第 nnn 个卡特兰数 1 n + 1 ( 2 n n ) \dfrac{1}{n+1}\dbinom{2n}{n} n+11(n2n) ,这是由 4 n n n \dfrac{4^n}{n\sqrt{n}} nn 4n 渐近界定的。

  • 时间复杂度: O ( 4 n n ) O(\dfrac{4^n}{\sqrt{n}}) O(n 4n) ,在回溯过程中,每个答案需要 O ( n ) O(n) O(n) 的时间复制到答案数组中。
  • 空间复杂度: O ( n ) O(n) O(n) ,除了答案数组之外,我们所需要的空间取决于递归栈的深度,每一层递归函数需要 O ( 1 ) O(1) O(1) 的空间,最多递归 2 n 2n 2n 层,因此空间复杂度为 O ( n ) O(n) O(n)

解法3 按括号序列的长度递归(记忆化搜索)或动态规划

任何一个括号序列都一定是由 ( 开头,并且第一个 ( 一定有一个唯一与之对应的 ) 。这样一来,每一个括号序列可以用 ( a ) b (a)b (a)b 来表示,其中 a a a b b b 分别是一个合法的括号序列(可以为空)。

这样就把生成 n n n 对括号的所有序列这一问题,分解为「生成 a a a 对括号的所有序列」+「生成 b b b 对括号的所有序列」两个规模更小但是本质相同的子问题 a + b = n − 1 a+b=n-1 a+b=n1 ,接着在 a a a 对括号的所有序列外面加上一个括号,这样就得到了 n n n 对括号的所有序列,并且完成了问题的分解。

总结一下,就是找到了一个最优子结构,将原问题转换为较小子问题求解。这道题的动态规划解难就是因为这个最优子结构不好想到。

一个示例如下:

  • i = 0 i = 0 i=0 结果是空;
  • i = 1 i = 1 i=1 结果有一种: ( ) () ()
  • i = 2 i = 2 i=2 结果有两种: ( ) ( ) , ( ( ) ) ()(),\ (()) ()(), (())
  • i = 3 i = 3 i=3 的结果,使用公式 ( + a + ) + b ,有如下三种情况共5种结果(以花括号来表示新添加的括号):
    • a = 2 , b = 0 a = 2, b = 0 a=2,b=0 { ( ) ( ) } , { ( ( ) ) } \{()()\},\ \{(())\} {()()}, {(())}
    • a = 1 , b = 1 a = 1, b = 1 a=1,b=1 { ( ) } ( ) \{()\}() {()}()
    • a = 0 , b = 2 a= 0, b = 2 a=0,b=2 { } ( ) ( ) , { } ( ( ) ) \{\}()(), \{\}(()) {}()(),{}(())

为了生成所有长度为 2 n 2n 2n(具有 n n n 个括号)的括号序列,我们定义一个函数 g e n e r a t e ( n ) generate(n) generate(n) 来返回所有可能的括号序列。那么在函数 generate ( n ) \textit{generate}(n) generate(n) 的过程中:

  • 我们需要枚举 a a a ,从 0 0 0 开始一直到 n − 1 n - 1 n1 ,并递归调用 g e n e r a t e ( a ) generate(a) generate(a) 计算 a a a 的所有可能性;
  • 相应的, b b b n − 1 n - 1 n1 一直到 0 0 0 ,递归调用 g e n e r a t e ( b ) generate(b) generate(b) 即可计算 b b b 的所有可能性;
  • 遍历 a a a b b b 的所有可能性并拼接,即可得到所有长度为 2 n 2n 2n 的括号序列。

为了节省计算时间,我们在每次 g e n e r a t e ( i ) generate(i) generate(i) 函数返回之前,把返回值存储起来,下次再调用 g e n e r a t e ( i ) generate(i) generate(i) 时可以直接返回,不需要再递归计算(即记忆化搜索)。

class Solution {
private:shared_ptr<vector<string>> cache[100] = {nullptr}; // string[][]shared_ptr<vector<string>> generate(int n) {if (cache[n] != nullptr) return cache[n];if (n == 0) cache[0] = shared_ptr<vector<string>>(new vector<string>{""});else {auto result = shared_ptr<vector<string>>(new vector<string>);for (int i = 0; i != n; ++i) {auto lefts = generate(i);auto rights = generate(n - i - 1);for (const string& left : *lefts)for (const string& right : *rights)result -> push_back("(" + left + ")" + right);}cache[n] = result;}return cache[n];}
public:vector<string> generateParenthesis(int n) {return *generate(n);}
};

动态规划解法如下:

class Solution {
public:vector<string> generateParenthesis(int n) {if (n == 0) return {};if (n == 1) return { "()" };vector<vector<string>> dp(n + 1);dp[0] = { "" };dp[1] = { "()" };for (int parenthesisNum = 2; parenthesisNum <= n; ++parenthesisNum) {for (int i = 0; i < parenthesisNum; ++i) {int j = parenthesisNum - i - 1;for (string &a : dp[i])for (string &b : dp[j]) dp[parenthesisNum].push_back("(" + a + ")" + b);}}return dp[n];}
};

复杂度分析:

  • 时间复杂度: O ( 4 n n ) O(\dfrac{4^n}{\sqrt{n}}) O(n 4n) ,该分析与 方法二 类似
  • 空间复杂度: O ( 4 n n ) O(\dfrac{4^n}{\sqrt{n}}) O(n 4n) ,此方法除答案数组外,中间过程中会存储与答案数组同样数量级的临时数组,是我们所需要的空间复杂度。

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

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

相关文章

Java架构师内功嵌入式技术

目录 1 导学2 嵌入式微处理体系结构3 微处理器分类4 多核处理器5 嵌入式软件6 嵌入式系统6.1 系统组成7 嵌入式实时操作系统8 嵌入式软件设计9 软件开发工具想学习架构师构建流程请跳转:Java架构师系统架构设计 1 导学 嵌入式技术对软件架构的作用主要体现在以下几个方面: …

YOLOv5论文作图教程(1)— 软件介绍及下载安装(包括软件包+下载安装详细步骤)

前言:Hello大家好,我是小哥谈。在学习YOLOv5算法的过程中,很多同学都有发表论文的需求。作为文章内容的支撑,图表是最直接的整合数据的工具,能够更清晰地反映出研究对象的结果、流程或趋势。在发表论文的时候,审稿人除了关注论文的内容和排版外,也会审核图表是否清晰美观…

网络编程进化史:Netty Channel 的崭新篇章

上篇文章&#xff08;Netty 入门 — ByteBuf&#xff0c;Netty 数据传输的载体&#xff09;&#xff0c;我们了解了 Netty 的数据是以 ByteBuf 为单位进行传输的&#xff0c;但是有了数据&#xff0c;你没有通道&#xff0c;数据是无法传输的&#xff0c;所以今天我们来熟悉 Ne…

【Gan教程 】 什么是变分自动编码器VAE?

名词解释&#xff1a;Variational Autoencoder&#xff08;VAE&#xff09; 一、说明 为什么深度学习研究人员和概率机器学习人员在讨论变分自动编码器时会感到困惑&#xff1f;什么是变分自动编码器&#xff1f;为什么围绕这个术语存在不合理的混淆&#xff1f;本文从两个角度…

高等数学啃书汇总重难点(七)微分方程

同济高数上册的最后一章&#xff0c;总的来说&#xff0c;这篇章内容依旧是偏记忆为主&#xff0c;说难不难说简单不简单&#xff1a; 简单的是题型比较死&#xff0c;基本上就是记公式&#xff0c;不会出现不定积分一般花样繁多的情况&#xff1b;然而也就是背公式并不是想的…

基于Java的在线教育网站管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

python输出与数据类型

目标 1、使用print输出内容 2、熟悉字符串类型 3、熟悉数字类型 4、熟悉数字与字符串操作 输出 print可控制输出内容也可配合、-、*、/进行运算&#xff0c;和整数型配合可进行运算和字符型配合有不同效果&#xff0c;如为拼接&#xff0c;*为多次输出注&#xff1a;整数型如&…

flutter开发实战-hero实现图片预览功能extend_image

flutter开发实战-hero实现图片预览功能extend_image 在开发中&#xff0c;经常遇到需要图片预览&#xff0c;当feed中点击一个图片&#xff0c;开启预览&#xff0c;多个图片可以左右切换swiper&#xff0c;双击图片及手势进行缩放功能。 这个主要实现使用extend_image插件。在…

【软件测试】自动化测试selenium

目录 一、什么是自动化测试 二、Selenium介绍 1、Selenium是什么 2、Selenium的原理 三、了解Selenium的常用API 1、webDriver API 1.1、元素定位 1.1.1、CSS选择器 1.1.2、Xpath元素定位 1.1.3、面试题 1.2、操作测试对象 1.3、添加等待 1.4、打印信息 1.5、浏…

kvm webvirtcloud 如何添加直通物理机的 USB 启动U盘

第一步&#xff1a;查看USB设备ID 在物理机上输入 lsusb 命令 rootubuntu:/media/usb1# lsusb Bus 002 Device 002: ID 0781:5581 SanDisk Corp. Ultra Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 004: ID 0424:2514 Microchip Technolo…

UE4/5 批量进行贴图Texture压缩、修改饱和度

该插件下载地址&#xff1a; &#x1f35e;正在为您运送作品详情https://mbd.pub/o/bread/ZZWYmpxw 适用于 UE4 4.25/4.26/4.27 UE5 以上版本 在Edit - Plugins中分别开启 插件 Python Editor Script Plugin 插件 Editor Scripting Utilites 如果会python代码&#xff0c;…

分享从零开始学习网络设备配置--任务4.2 使用IPv6静态及默认路由实现网络连通

任务描述 某公司利用IPv6技术搭建网络&#xff0c;公司3个部门所有PC机连接在同一交换机上&#xff0c;PC1代表行政部划分到VLAN10中&#xff0c;PC2代表财务部划分到VLAN20中&#xff0c;PC3代表销售部划分到VLAN30中&#xff0c;R1代表公司出口路由器&#xff0c;R2模拟Inter…

统计学习方法 支持向量机(下)

文章目录 统计学习方法 支持向量机&#xff08;下&#xff09;非线性支持向量机与和核函数核技巧正定核常用核函数非线性 SVM 序列最小最优化算法两个变量二次规划的求解方法变量的选择方法SMO 算法 统计学习方法 支持向量机&#xff08;下&#xff09; 学习李航的《统计学习方…

操作系统:计算机系统概述

一战成硕 1.1 手工操作阶段1.2 批处理阶段1.3 分时操作系统1.4 实时操作系统1.5 中断和异常的概念1.6 系统调用 1.1 手工操作阶段 1.2 批处理阶段 单道批处理系统 自动性 顺序性 单道性多道批处理系统 多道 宏观上并行 微观上串行 优点&#xff1a;资源利用率高&#xff0c;多…

Postman —— 配置环境变量

PostMan是一套比较方便的接口测试工具&#xff0c;但我们在使用过程中&#xff0c;可能会出现创建了API请求&#xff0c;但API的URL会随着服务器IP地址的变化而改变。 这样的情况下&#xff0c;如果每一个API都重新修改URL的话那将是非常的麻烦&#xff0c;所以PostMan中也提供…

hive窗口函数记录

记录工作中和学习中的窗口函数&#xff0c;方便以后使用&#xff0c;本记持续更新和完善&#xff0c;版本&#xff1a;231019 文章目录 1.什么是窗口函数2.窗口函数的表达式3.窗口函数的类型1&#xff09; 排名函数2&#xff09; 聚合函数3&#xff09; 跨行取值函数 4.[frame…

如何实现前端实时通信(WebSocket、Socket.io等)?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

回归预测 | MATLAB实现IWOA-LSTM改进鲸鱼算法算法优化长短期记忆神经网络的数据回归预测(多指标,多图)

回归预测 | MATLAB实现IWOA-LSTM改进鲸鱼算法算法优化长短期记忆神经网络的数据回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现IWOA-LSTM改进鲸鱼算法算法优化长短期记忆神经网络的数据回归预测&#xff08;多指标&#xff0c;多图&#…

【图解数据结构】手把手教你如何实现顺序表(超详细)

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;数据结构、算法模板、汇编语言 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 一. ⛳️线性表1.1 &#x1f514;线性表的定义1.2 &#x1f514;线性表的存储结构 二. ⛳️…

Premiere Pro(Pr)2023软件下载及安装教程

目录 一.简介 二.安装步骤 软件&#xff1a;Pr版本&#xff1a;2023语言&#xff1a;简体中文大小&#xff1a;8.30G安装环境&#xff1a;Win11/Win10&#xff08;1809版本以上&#xff09;硬件要求&#xff1a;CPU2.6GHz 内存8G(或更高&#xff0c;不支持7代以下CPU&#xf…