【算法题解】38. 括号的生成

这是一道 中等难度 的题
https://leetcode.cn/problems/generate-parentheses/

题目

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

示例 1:

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

示例 2:

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

提示:

  • 1 < = n < = 8 1 <= n <= 8 1<=n<=8

递归解法一

分两步操作,先递归生成所有可能的组合,然后判断组合中的每个值是否合法有效。

生成所有的组合
数字 n 代表生成括号的对数,那么最终生成的每一个结果中都会包含 2n 个括号。

12n 这些位置(或者说从 02n - 1 这些下标)上,每个位置的取值都是 “(” 或者 “)”

那么 n 对括号的的所有组合 g e n e r a t e A l l ( 2 n ) generateAll(2n) generateAll(2n) 就应该是在 g e n e r a t e A l l ( 2 n − 1 ) generateAll(2n - 1) generateAll(2n1) 的基础上再加上最后一个位置的取值,即 g e n e r a t e A l l ( 2 n − 1 ) + “ ( ” generateAll(2n - 1) + “(” generateAll(2n1)+( g e n e r a t e A l l ( 2 n − 1 ) + “ ( ” generateAll(2n - 1) + “(” generateAll(2n1)+( 的合集。

i余数 【算法题解】有效的括号

以此类推,直到遇到边界条件 n = 0 时,返回 g e n e r a t e A l l ( 0 ) generateAll(0) generateAll(0) = {“”};

判断是否是有效的括号
判断括号是否有效可以使用栈的思路,遇到 “(” 则入栈,遇到 “)” 就将栈顶元素出栈并判断是否是“(”,如果不是那么肯定不合法直接返回 false

最后如果栈中还有没出栈的元素,那么也不是合法的组合,因为没出栈的 “(” 没有与之相对应的 “)”

相关题解有: 【算法题解】14. 有效的括号

Java 代码实现

class Solution {

    public List<String> generateParenthesis(int n) {
        if(n == 0){
            return Arrays.asList("");
        }
        
    	// 1. 生成所有可能的组合
    	// 2. 排除不合法的组合
        List<String> all = generateAll(2*n);
        
        return all.stream().filter(str -> isValid(str)).collect(Collectors.toList());
    }

    private List<String> generateAll(int size){
        if(size == 0){
            return Arrays.asList("");
        }

        List<String> all = new ArrayList();
        
        List<String> lastAll = generateAll(size - 1);
        for(String last : lastAll){
          
            all.add("(" + last);
            all.add(")" + last);
       
        }

        return all;
    }

    private boolean isValid(String str){
        Deque<Character> stack = new LinkedList<>();
        char[] ch = str.toCharArray();
       
        for(int i = 0; i < ch.length; i++){
            if(ch[i] == '('){
                stack.push(ch[i]);
            }else if(stack.isEmpty() || stack.pop() != '('){
                return false;
            }
        }

        return stack.isEmpty();

    }

   
}

Go 代码实现

func generateParenthesis(n int) []string {
    all := generateAll(2*n)
    ans := []string{}
    for _, str := range all {
        if isValid(st) {
            ans = append(ans, str)
        }
        
    }
    return ans
}

func generateAll(size int) []string {
    all := make([]string, 0)
    if size == 0 {
        all = append(all, "")
        return all
    }

    lastAll := generateAll(size - 1)
    for _, last := range lastAll {
        all = append(all, last + "(")
        all = append(all, last + ")")
    }
    return all
}

func isValid(s string) bool {
    n := len(s)
    stack := []byte{}
    for i := 0; i < n; i++ {
        if s[i] == ')' {
            if len(stack) == 0 || stack[len(stack)-1] != '(' {
                return false
            }
            stack = stack[:len(stack)-1]
        } else {
            stack = append(stack, s[i])
        }
    }
    return len(stack) == 0
}

复杂度分析

时间复杂度 O ( 2 2 n ∗ n ) O(2^{2n} * n) O(22nn), 生成所有组合的时间复杂度为 O ( 2 2 n ) O(2^{2n}) O(22n)n 为生成括号的对数。判断是否合法的时间复杂度为 O ( n ) O(n) O(n) ,总计 O ( 2 2 n ∗ n ) O(2^{2n} * n) O(22nn)

空间复杂度: O ( n ) O(n) O(n)n 为递归调用栈的深度。


递归解法二:分治

所有的合法的组合,都应该是以左括号 “(” 开头的,且后面肯定会有一个右括号 “)” 与之匹配。即所有的组合都满足 (a)b 的格式,其中 ab 可以为空或者其他任意有效的组合。

那么我们只要求的 ab 的结果,然后拼成 (a)b 就得出最终的答案了,求 ab 的结果同样是按照这个方式继续细分,还是递归的思路。

边界条件:n = 0 时,直接返回空,也可以把 n = 1 加上去,直接返回“()”

Java 代码实现

class Solution {
    private Map<Integer, List<String>> cache = new HashMap<>();

    public List<String> generateParenthesis(int n) {
        List<String> ans;
        if(cache.containsKey(n)){
            return cache.get(n);
        }
        if(n == 0){
            ans = Arrays.asList("");
        }else if(n == 1){
            ans = Arrays.asList("()");
        }else{
            ans = new ArrayList<>();
            // a + b = n - 1
            for(int a = 0; a < n; a ++){
                int b = n - 1 - a;
                List<String> aList = generateParenthesis(a);
                List<String> bList = generateParenthesis(b);
                for(String aTemp : aList){
                    for(String bTemp : bList){
                        ans.add("(" + aTemp +")" + bTemp);
                    }
                }
            }

        }

        cache.put(n, ans);
        return ans;
    }
   
}

Go 代码实现


func generateParenthesis(n int) []string {
    ans := []string{}
    if n == 0 {
        ans = append(ans, "")
        return ans;
    }
    if n == 1 {
        ans = append(ans, "()")
        return ans;
    }

    for a := 0; a < n; a++ {
        b := n - 1 - a

        aList := generateParenthesis(a)
        bList := generateParenthesis(b)

        for _, aTemp := range aList {
            for _, bTemp := range bList {
                ans = append(ans, "(" + aTemp + ")" + bTemp)
            }
        }
    }

    return ans
}

深度优先搜索

同解法一的思路一样,先生成所有可能的组合,然后再判断是否合法。不一样的是生成时使用 深度优先搜索 的思路。

递归函数:每一个位置都有 “(” 或者 “)”两个选项。

边界条件:当走到最后一个位置的时候返回。

在这里插入图片描述

关于优化剪枝:

  1. 直接以右括号 “)” 开头的肯定都不合法,直接排除。
  2. 当生成的组合中,无论是 "(" 还是 ")" 的个数已将超过一半(n个),那么肯定是不合法的了,可以提前排除掉。
  3. 当遇到 ")" 的个数 大于 "(" 的个数时,那么多出来的那个右括号已经无法匹配了,可以提前排除掉。

关于判断合法性:
只要能走到最后,且左右括号的个数都是 n,那么肯定就是合法的,无需再判断合法性。

因为不合法的几种情况都通过剪枝剪掉了:

  1. 左右括号的个数不对,已经通过 剪枝条件2 剪掉。
  2. 左右括号个数相等的情况下,只要是不合法的,其前面的某一个时刻,必然是多出来一个右括号“)”是匹配不上的,已经通过 剪枝条件3 剪掉了。如某一时刻为 "a)" ,其中 a 是合法的组合,那么 “a)” 就已经不合法了。

Java 代码实现

class Solution {
 
    private List<String> ans = new ArrayList<>();
    private int size;

    public List<String> generateParenthesis(int n) {
        this.size = n;
        char[] ch = new char[2 * n];
        ch[0] = '(';
        int left = 1, right = 0;
        // 第一个位置肯定是 ‘(’
        dfs(ch, 1, 1, 0);
        return ans;
    }

    private void dfs(char[] ch, int index, int left, int right){
        if(left > size){
            return;
        }
        if(right > size){
            return;
        }

        if(right > left){
            return;
        }
        // 边界条件
        if(index == 2 * size){
            ans.add(new String(ch));
            return;
        }

        // 每个位置都有 2 种可能
        ch[index] = '(';
        dfs(ch, index + 1, left + 1, right);


        ch[index] = ')';
        dfs(ch, index + 1, left, right + 1);

        return;
    }   
}

Go 代码实现

var (
    ans []string
    size int
    )
func generateParenthesis(n int) []string {
    ans = []string{}
    size = n
    // 以左括号开始
    path := "("

    dfs(path, 1, 1, 0)

    return ans
}

func dfs(path string, index int, left int, right int){
    if left > size {
        return
    }
    if right > size {
        return
    }
    if(right > left){
        return
    }

    if index == (2 * size) {
        ans = append(ans, path)
    }

  
    dfs(path + "(", index + 1, left + 1, right)
    
    dfs(path + ")", index + 1, left, right + 1)

}

剪枝后优化效果非常明显。

复杂度分析

时间复杂度 O ( 2 2 n ) O(2^{2n}) O(22n)。总的节点个数为 2 2 n + 1 − 1 2^{2n+1} -1 22n+11 个,除掉右半边,可以按照 2 2 n 2^{2n} 22n个计算。

空间复杂度: O ( n ) O(n) O(n)ch 数组长度为 2n,递归深度也是 2n

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

废柴日记8:从入门到入狱的Python爬虫学习笔记1(入门篇)

前言&#xff1a;我错了&#xff0c;但下次也不一定(●’◡’●) 米娜桑&#xff0c;好久不见&#xff0c;不知道这段时间各位手中的西瓜刀有没有按时擦亮呢&#xff1f; 我也是在摸爬滚打将近一年之后总算是找到了一点人生的方向所以当成救命稻草现在正死死握紧不放手的啊。…

java八股文-并发篇

并发篇 1. 线程状态 要求 掌握 Java 线程六种状态掌握 Java 线程状态转换能理解五种状态与六种状态两种说法的区别 六种状态及转换 分别是 新建 当一个线程对象被创建&#xff0c;但还未调用 start 方法时处于新建状态此时未与操作系统底层线程关联 可运行 调用了 start …

2023全国计算机二级考试时间(全年各阶段考试时间安排)

2023全国计算机二级考试时间(全年各阶段考试时间安排) 2023年全国计算机二级考试时间分别为&#xff1a;3月25日至27日(上半年3月)、9月23日至25日(下半年9月)。 其中3月和9月开考全部级别全部科目&#xff0c;5月和12月考试开考一、二级全部科目&#xff0c;各省级承办机构可根…

SQL锁总结

一、概述 介绍 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资源(CPU、RAM、I/O)的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题&#xff0c;锁…

12 VI——变分推断

文章目录 12 VI——变分推断12.1 背景介绍12.2 Classical VI12.2.1 公式导出12.2.2 坐标上升法 12.3 SGVI——随机梯度变分推断12.3.1 一般化MC方法12.3.2 降方差——Variance Reduction 12 VI——变分推断 12.1 背景介绍 变分推断的作用就是在概率图模型中进行参数估计&…

.mdf.locked加密sql server完美恢复---惜分飞

有可能用友ERP软件的sql server 数据库所在机器被勒索病毒加密,扩展名为.locked和昨天恢复的基本类似(.locked加密勒索数据库级别恢复),通过分析确认sql server被这种病毒加密,也可以完美恢复 通过恢复之后数据库正常挂载成功 测试应用一切正常 对于类似这种被加密的勒索的数…

Hazel游戏引擎(010)预编译头

文中若有代码、术语等错误&#xff0c;欢迎指正 文章目录 前言如何实现 前言 此节目的 由于项目中的头文件或者cpp文件都包含着c的头文件&#xff0c;有些重复&#xff0c;可以将它们包含的c头文件放在一个头文件内&#xff0c;这样不仅使代码简洁&#xff0c;而且预编译头可以…

chatgpt赋能python:Python如何取消空格提升SEO排名

Python如何取消空格提升SEO排名 作为一种高效的编程语言&#xff0c;Python已经成为了许多网站开发人员和SEO优化人员的首选工具。在网站优化中&#xff0c;取消空格是一个重要的优化技术&#xff0c;它可以提升网站速度&#xff0c;提高网站体验&#xff0c;同时也可以提升SE…

前后端交互二、form表单与模板引擎

零、文章目录 前后端交互二、form表单与模板引擎 1、form表单的基本使用 HTML相关知识请参考HTML入门 &#xff08;1&#xff09;表单是什么 表单在网页中主要负责数据采集功能。HTML中的<form>标签&#xff0c;就是用于采集用户输入的信息的&#xff0c;并通过<…

【EasyX】实时时钟

目录 实时时钟1. 绘制静态秒针2. 秒针的转动3. 根据实际时间转动4. 添加时针和分针5. 添加表盘刻度 实时时钟 本博客介绍利用EasyX实现一个实时钟表的小程序&#xff0c;同时学习时间函数的使用。 本文源码可从github获取 1. 绘制静态秒针 第一步定义钟表的中心坐标center&a…

操作系统1-操作系统的基本特征和主要功能

目录 1、操作系统的目标和作用 &#xff08;1&#xff09;操作系统的目标 &#xff08;2&#xff09;操作系统的作用 2、操作系统的发展过程 &#xff08;1&#xff09;未配置操作系统的计算机系统 &#xff08;2&#xff09;单道批处理系统(Simple Batch Processing Sys…

Task Add-in Sample (C#)

下例显示了用 C# 编写Task Add-in 的完整源代码。 使用 C# 类库 &#xff08;.NET Framework&#xff09; 创建 Visual Studio 中的项目。实现 IEdmAddIn5。在“任务属性”对话框中创建自定义页。自定义任务详细信息页面。 注意&#xff1a; 若要填充下面的 GUID 属性&#x…

软考A计划-系统架构师-学习笔记-第三弹

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

Redis 持久化机制

Redis 是个基于内存的数据库。那服务一旦宕机&#xff0c;内存中数据必将全部丢失。所以丢失数据的恢复对于 Redis 是十分重要的&#xff0c;我们首先想到是可以从数据库中恢复&#xff0c;但是在由 Redis 宕机时&#xff08;说明相关工作正在运行&#xff09;且数据量很大情况…

上课补充的知识

题目 char类型的默认值是\u0000 数组的创建方式 数组的遍历 遍历:从头到尾,依次访问数组每一个位置,获取每一个位置的元素.形式如下: 我们通过数组的下标操作数组,所以for循环变量操作的也是数组下标 开始:开始下标0 结束:结束下标length-1 如何变化: 语法&#xff1a; for…

软考A计划-系统架构师-学习笔记-第二弹

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

【java】IO流

IO流 原理 分类 字节流与字符流 节点流与包装流 Java IO详解&#xff08;五)------包装流 - YSOcean - 博客园 (cnblogs.com)JAVA I/O流 字符流和字节流、节点流和处理流(包装流、过滤流)、缓冲流_过滤流和缓冲流,字节流的关系_X-Dragon烟雨任平生的博客-CSDN博客 字符流 i…

Redis常见问题、各个分布式锁优缺点-05

Redis集群为什么至少需要三个master节点&#xff0c;并且推荐节点数为奇数&#xff1f; 因为新master的选举需要大于半数的集群master节点同意才能选举成功&#xff0c;如果只有两个master节点&#xff0c;当其中一个挂了&#xff0c;是达不到选举新master的条件的。 奇数个ma…

【Linux】 -- TCP协议 (一)

TCP协议 Tcp协议可靠性冯诺依曼体系结构 TCP的协议格式序号与确认序号窗口大小六个标志位 确认应答机制 &#xff08;ACK&#xff09;超时重传机制连接管理机制 Tcp协议 TCP全称为 “传输控制协议”&#xff08;Transmission Control Protocol&#xff09; TCP协议被广泛应用…

C语言---形参所导致的段错误

前言 今天刷B站&#xff0c;无意之间看到一个宣称90%人都会错的嵌入式面试题。感兴趣就看了一下。卡了十多分钟才想明白&#xff0c;只是一个小知识点&#xff0c;但还是分享一下。 题目 #include <stdio.h> #include <stdlib.h> #include <string.h>void g…