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

news2024/11/19 5:43:09

这是一道 中等难度 的题
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

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

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

相关文章

DNS隧道穿透

介绍&#xff1a; DNS隧道&#xff0c;是隧道技术中的一种。当我们的HTTP、HTTPS这样的上层协议、正反向端口转发都失败的时候&#xff0c;可以尝试使用DNS隧道。DNS隧道很难防范&#xff0c;因为平时的业务也好&#xff0c;使用也罢&#xff0c;难免会用到DNS协议进行解析&am…

线上问题记录20230610-NGINX代理大文件下载问题

线上问题记录20230610-NGINX代理大文件下载问题 问题描述 我们有一个在线项目&#xff1a;是一个对象文件存储服务&#xff0c;是用来给用户提供文件对象存储的。今天用户在使用的时候发现超过1G的文件无法进行下载。幸好今天是星期六&#xff0c;使用的人并不是特别多&#…

华为OD机试真题 JavaScript 实现【最长的连续子序列】【2022Q4 100分】

一、题目描述 有N个正整数组成的一个序列&#xff0c;给定一个整数sum&#xff0c;求长度最长的的连续子序列使他们的和等于sum&#xff0c;返回该子序列的长度&#xff0c;如果没有满足要求的序列返回-1。 二、输入描述 第1行有N个正整数组成的一个序列。 第2行给定一个整…

【LIN通讯出现问题】

LIN信号发出了&#xff0c;但是没有收到响应的原因 如果你发送了LIN&#xff08;局域网互连&#xff09;信号&#xff0c;但没有收到响应&#xff0c;可能有以下几个原因&#xff1a; 线路故障&#xff1a;检查信号线路是否连接正确&#xff0c;可能存在接触不良、开路或短路等…

【PCB专题】Allegro 单线、差分线自动绕等长功能介绍

在文章【PCB专题】案例:绕等长怎么直接以颜色区分看出是否绕好 中我们讲到Allegro 16.6版本的Timing Vision功能可以直接在PCB上以颜色的区分就能看出单线和差分是否已经绕成等长了,不再需要到规则管理器中去查看。 那么其实Allegro还有单线自动绕等长的功能——Auto interac…

mac电脑储存内存越来越小如何清理释放空间?

如果你是一位Mac系统的用户&#xff0c;可能会发现你的电脑储存空间越来越小。虽然Mac系统设计得非常优秀&#xff0c;但是系统数据和垃圾文件也会占据大量的储存空间。在这篇文章中&#xff0c;我们将探讨mac系统数据怎么这么大&#xff0c;以及mac清理系统数据怎么清理。 一…

# Telegraph-Image:利用Cloudflare Pages和Telegraph无成本创建自己的图床

Telegraph-Image&#xff1a;利用Cloudflare Pages和Telegraph无成本创建自己的图床 Telegraph-Image是一个具有以下特点的图像托管服务&#xff1a; 特点&#xff1a; 无限图片储存数量&#xff1a;你可以上传不限数量的图片&#xff0c;没有存储限制。免费托管&#xff1a…

chatgpt赋能python:Python中如何删除字符串中的标点符号

Python中如何删除字符串中的标点符号 概述 在Python中&#xff0c;字符串是一种基本数据类型&#xff0c;经常被用来存储和处理文本数据。在处理文本数据时&#xff0c;我们常常需要删除其中的标点符号。本文将介绍如何使用Python中的字符串处理方法来删除字符串中的标点符号…

[安卓广播入门][1]Android Studio接收系统广播

一、新建项目 二、增加权限 <uses-permission android:name"android.permission.ACCESS_NETWORK_STATE" />三、代码 public class MainActivity extends AppCompatActivity {private IntentFilter intentFilter;//过滤隐式意图private NetworkChangeReceiver…

【状态估计】变分贝叶斯近似的递归噪声自适应卡尔曼滤波(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【Spring框架】第一个Spring(创建和使用)

目录 Spring创建和使用创建创建一个Maven项目添加Spring框架支持(spring-context、spring-beans)添加启动类 存储Bean对象创建Bean将Bean注册到容器读取并使用Bean对象经典面试题&#xff1a;ApplicationContext和BeanFactory的区别是什么&#xff1f; Spring创建和使用 创建 …

SpringBoot-【回顾】

第一个SpringBoot程序 自动装配原理 Springboot的自动装配实际上就是为了从Spring.factories文件中获取到对应的需要进行自动装配的类&#xff0c;并生成相应的Bean对象&#xff0c;然后将它们交给Spring容器来帮我们进行管理 启动器&#xff1a;以starter为标记 EnableAuto…

C语言:给定两个数,求这两个数的最大公约数(新思路:辗转相除法)

题目&#xff1a; 从键盘输入两个数&#xff0c;求这两个数的最大公约数。 思路一&#xff1a;普通方法 总体思路&#xff1a; &#xff08;一&#xff09;. 生成相关变量&#xff1b; 从键盘输入两个数&#xff1b; 再使用 三目操作符&#xff08;条件操作符&#xff09; 找出…

树——“数据结构与算法”

各位CSDN的uu们好久不见呀&#xff0c;好久没有更新我的数据结构与算法专栏啦&#xff0c;现在&#xff0c;我要开始重拾丢下的知识啦&#xff0c;这次&#xff0c;小雅兰要给uu们介绍一个全新的数据结构&#xff0c;下面&#xff0c;就让我们进入树的世界吧&#xff01;&#…

Flutter 环境配置

Flutter 环境配置 电脑上面安装配置JDK 1.下载安装JDK 下载地址&#xff1a;https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) 2.配置JDK 2.1、系统变量 里面新增JAVA_HOME&#xff0c;设置值为java sdk 根目录&#xff1a; 2.2 …

OpenGl之纹理坐标及纹理映射

文章目录 纹理坐标纹理映射代码 纹理坐标 为了实现纹理贴图我们需要做三件事&#xff1a;将一张贴图加载到OpenGL中&#xff0c;提供纹理坐标和顶点&#xff08;将纹理对应匹配到顶点上&#xff09;&#xff0c;并使用纹理坐标从纹理中进行取样操作取得像素颜色。由于三角形会被…

Vue源码解析--更新中

【尚硅谷】Vue源码解析之虚拟DOM和diff算法 【Vue源码】图解 diff算法 与 虚拟DOM-snabbdom-最小量更新原理解析-手写源码-updateChildren] 文章目录 2. snabbdom 简介 及 准备工作2.1 简介2.2 搭建初始环境1. 安装snabbdom2. 安装webpack5并配置3. 复制官方demo Example 3. …

如何把歌曲里的伴奏音乐提取出来,分享几个方法给大家!

对于一首歌&#xff0c;我们都知道&#xff0c;它有两部分组成&#xff1a;背景音乐人声。这两者合在一起&#xff0c;便是我们经常听的歌。部分用户想要直接获取歌曲伴奏&#xff0c;那么可以在UU伴奏网上下载。 操作方法比较简单&#xff0c;直接搜索想要的歌曲名称就可以了…

【分布式系统与一致性协议】

分布式系统与一致性协议 CAP原理APCPCA总结BASE理论 一致性拜占庭将军问题 分布式系统是一个硬件或软件组件分布在不同的网络计算机上&#xff0c;彼此之间仅仅通过消息传递进行通信和协调的系统。 分布式系统的设计目标一般包含如下&#xff1a; 可用性&#xff1a;可用性是分…

C++14中binary literals的使用

一个形如42的值被称作字面值常量(literal),这样的值一望而知。每个字面值常量都对应一种数据类型&#xff0c;字面值常量的形式和值决定了它的数据类型。 我们可以将整型字面值写作十进制(基数为10)、八进制(基数为8)或十六进制(基数为16)数的形式。以0开头的整数代表八进制数&…