【算法练习】有趣的括号匹配问题(思路+ 图解 +优化)基于java实现

简介: 1.题目描述小洛看着一堆只包含’(‘和’)‘的括号序列犯愁了,小洛想知道这串序列里最长正确匹配的序列长度是多少,你能帮帮小洛吗?

1.题目描述

小洛看着一堆只包含’(‘和’)‘的括号序列犯愁了,小洛想知道这串序列里最长正确匹配的序列长度是多少,你能帮帮小洛吗?

输入

输入一个只包含’(‘与’)'的字符串,字符串长度n满足0<n≤50000

输出

输出一个数表示子串长度

数据范围

对于100%的数据,0<n≤50000

输入样例

((((())(

输出样例

4


2.问题分析

说到括号匹配问题,我们很容易想到利用栈来解决,但是直接使用栈来进行处理的话,你会发现,无法记录在入栈出栈过程中匹配的长度。因此,我们对栈进行优化,对于栈中存储的每个元素我们都把它当作一个结点,包含data(左括号或者右括号),以及标号值,用index表示。



对于一个序列"( ( ( ) ) ) ) )",首先我们将括号序列依次进行标号,分别为12345678我们让左括号直接入栈,遇到右括号则观察栈中栈顶元素是否为左括号,若是,则出栈;反之,则入栈。

反复进行入栈和出栈操作后,栈中只剩下标号为7、8的两个右括号,则最大匹配长度为7 - 0 - 1 = 6;




我们再来看一个序列"()()",标号分别为1234,进行入栈出栈操作后,最终栈中元素为0,此时最大匹配长度则为字符串的长度,即为4 - 0 = 4。


3.算法分析

对于执行后的栈,我们大致可以分为如下的三种情况:


栈为空:此时最大匹配长度为字符串的长度;

栈底为" ) ":此时隐藏一种匹配长度为栈底标号 - 0 - 1;

栈底为" ( ": 只需要比较任意相邻两个栈中元素标号的差,匹配长度为end - start - 1。

以题目的测试样例为例,输入字符串((((())(,最终剩余在栈中的为标号为1238的左括号,此时最大匹配长度为8 - 3 -1 = 4。


4.实现代码

这里简单对以上讨论的三种情况进行实现,详细可见注释内容。

import java.util.Scanner;
import java.util.Stack;
// 结点类,用于入栈元素的标号
class Node{
    char data;// 存储括号值
    int index;// 用于标号
    public Node(char data, int index){
        this.data = data;
        this.index = index;
    }
}
public class test03 {
    public static void main(String[] args) {
        Stack<Node> stack = new Stack<>(); // 入栈的每个元素都是带有标号的左括号,而匹配的长度则为两个相邻的左括号标号的差值-1
        Scanner in = new Scanner(System.in);
        String s = in.next();
        char[] strings = s.toCharArray();
        boolean flag = false;
        int temp = 0; // 临时储存
        // 遍历括号字符串
        int i; // 这里把i单独声明是为了扩大作用域便于end使用
        for ( i = 0 ; i < strings.length; i++) {
            // 遇到左括号入栈,栈中的标号以1为起点
            if(strings[i] == '('){
                stack.push(new Node(strings[i], i+1));
            }
            // 遇到右括号看栈内是否匹配
            if(strings[i] == ')'){
                if(stack.empty()){
                    // 处理)))这种情况
                    flag = true; // 说明栈是以右括号为栈底
                    temp = i+1;
                    stack.push(new Node(strings[i], i+1)); // 防止栈下溢
                }else{
                    // 匹配则出栈,不匹配则入栈
                    if(stack.peek().data == '('){
                        stack.pop();
                    }else{
                        stack.push(new Node(strings[i], i+1));
                    }
                }
            }
        }
        // 计算匹配长度
        int start = 0, length = 0, end = i+1;
        // 括号完全匹配,即栈中元素为0,则长度为字符串长度
        if(stack.empty())
            length = end - start - 1;
        while (!stack.empty()){
            // 栈中以右括号为栈底元素,需要记录一次栈底元素标号与0的差值
            if(flag){
                length = Math.max(temp - start -1, length);
            }
            // 栈中以左括号为栈底元素,只需要比较标号的差值
            start = stack.pop().index;
            //System.out.println("start = " + start);
            length = Math.max(end - start - 1, length);
            // 以start为新的end
            end = start;
        }
        // 输出结果
        System.out.println(length);
        in.close();
    }
}

5.实现结果

输入((((())(测试1



输入()()测试2


输入)))))测试3


6.代码优化

从上面的实现中发现,分为三种情况即单独对栈空的情况进行计算、使用了flag变量来判断栈底是否为右括号、单独对栈底为左括号的情况进行运算。虽然逻辑上没有问题,但是这样不仅多声明了变量,还使代码显得异常复杂。

那么有没有办法可以简化计算呢?

我们发现,只要先将一个标号为0的无关量入栈,这样无论何种情况,在进行最后一步“计算最大匹配长度”时,栈都不会为空,这样就不需要对栈空的情形进行单独讨论;不仅如此,我们存储的0无关量也可以解决右括号为栈底的情况,可以把无关量想成左括号,进行一般情况下的栈元素的标号差操作,即可求出匹配长度。


6.1 具体代码优化实现

import java.util.Scanner;
import java.util.Stack;
// 结点类,用于入栈元素的标号
class Node{
    char data;// 存储括号值
    int index;// 用于标号
    public Node(char data, int index){
        this.data = data;
        this.index = index;
    }
}
public class test03 {
    public static void main(String[] args) {
        Stack<Node> stack = new Stack<>(); // 入栈的每个元素都是带有标号的左括号,而匹配的长度则为两个相邻的左括号标号的差值-1
        Scanner in = new Scanner(System.in);
        String s = in.next();
        char[] strings = s.toCharArray();
        stack.push(new Node('0', 0));// 标号0位置存储一个无关量,便于计算
        // 遍历括号字符串
        int i; // 这里把i单独声明是为了扩大作用域便于end使用
        for ( i = 0 ; i < strings.length; i++) {
            // 遇到左括号入栈,栈中的标号以1为起点
            if(strings[i] == '('){
                stack.push(new Node(strings[i], i+1));
            }
            // 遇到右括号看栈内是否匹配
            if(strings[i] == ')'){
                if(stack.empty()){
                    stack.push(new Node(strings[i], i+1)); // 防止栈下溢
                }else{
                    // 匹配则出栈,不匹配则入栈
                    if(stack.peek().data == '('){
                        stack.pop();
                    }else{
                        stack.push(new Node(strings[i], i+1));
                    }
                }
            }
        }
        // 计算匹配长度
        int start = 0, length = 0, end = i+1;
        while (!stack.empty()){
            start = stack.pop().index;
            //System.out.println("start = " + start);
            length = Math.max(end - start - 1, length);
            // 以start为新的end
            end = start;
        }
        // 输出结果
        System.out.println(length);
        in.close();
    }
}


相关文章
|
12天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
33 6
|
13天前
|
算法 调度
基于遗传模拟退火混合优化算法的车间作业最优调度matlab仿真,输出甘特图
车间作业调度问题(JSSP)通过遗传算法(GA)和模拟退火算法(SA)优化多个作业在并行工作中心上的加工顺序和时间,以最小化总完成时间和机器闲置时间。MATLAB2022a版本运行测试,展示了有效性和可行性。核心程序采用作业列表表示法,结合遗传操作和模拟退火过程,提高算法性能。
|
12天前
|
存储 算法 Java
leetcode算法题-有效的括号(简单)
【11月更文挑战第5天】本文介绍了 LeetCode 上“有效的括号”这道题的解法。题目要求判断一个只包含括号字符的字符串是否有效。有效字符串需满足左括号必须用相同类型的右括号闭合,并且左括号必须以正确的顺序闭合。解题思路是使用栈数据结构,遍历字符串时将左括号压入栈中,遇到右括号时检查栈顶元素是否匹配。最后根据栈是否为空来判断字符串中的括号是否有效。示例代码包括 Python 和 Java 版本。
|
14天前
|
人工智能 算法 大数据
Linux内核中的调度算法演变:从O(1)到CFS的优化之旅###
本文深入探讨了Linux操作系统内核中进程调度算法的发展历程,聚焦于O(1)调度器向完全公平调度器(CFS)的转变。不同于传统摘要对研究背景、方法、结果和结论的概述,本文创新性地采用“技术演进时间线”的形式,简明扼要地勾勒出这一转变背后的关键技术里程碑,旨在为读者提供一个清晰的历史脉络,引领其深入了解Linux调度机制的革新之路。 ###
|
22天前
|
Java 数据库连接 数据库
优化之路:Java连接池技术助力数据库性能飞跃
在Java应用开发中,数据库操作常成为性能瓶颈。频繁的数据库连接建立和断开增加了系统开销,导致性能下降。本文通过问题解答形式,深入探讨Java连接池技术如何通过复用数据库连接,显著减少连接开销,提升系统性能。文章详细介绍了连接池的优势、选择标准、使用方法及优化策略,帮助开发者实现数据库性能的飞跃。
26 4
|
19天前
|
存储 Java 开发者
成功优化!Java 基础 Docker 镜像从 674MB 缩减到 58MB 的经验分享
本文分享了如何通过 jlink 和 jdeps 工具将 Java 基础 Docker 镜像从 674MB 优化至 58MB 的经验。首先介绍了选择合适的基础镜像的重要性,然后详细讲解了使用 jlink 构建自定义 JRE 镜像的方法,并通过 jdeps 自动化模块依赖分析,最终实现了镜像的大幅缩减。此外,文章还提供了实用的 .dockerignore 文件技巧和选择安全、兼容的基础镜像的建议,帮助开发者提升镜像优化的效果。
|
24天前
|
人工智能 算法 数据安全/隐私保护
基于遗传优化的SVD水印嵌入提取算法matlab仿真
该算法基于遗传优化的SVD水印嵌入与提取技术,通过遗传算法优化水印嵌入参数,提高水印的鲁棒性和隐蔽性。在MATLAB2022a环境下测试,展示了优化前后的性能对比及不同干扰下的水印提取效果。核心程序实现了SVD分解、遗传算法流程及其参数优化,有效提升了水印技术的应用价值。
|
24天前
|
缓存 前端开发 JavaScript
9大高性能优化经验总结,Java高级岗必备技能,强烈建议收藏
关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。本文介绍了9种性能优化方法,涵盖代码优化、数据库优化、连接池调优、架构层面优化、分布式缓存、异步化、Web前端优化、服务化、硬件升级、搜索引擎和产品逻辑优化。欢迎留言交流。
|
23天前
|
存储 缓存 算法
优化轮询算法以提高资源分配的效率
【10月更文挑战第13天】通过以上这些优化措施,可以在一定程度上提高轮询算法的资源分配效率,使其更好地适应不同的应用场景和需求。但需要注意的是,优化策略的选择和实施需要根据具体情况进行详细的分析和评估,以确保优化效果的最大化。
|
24天前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。