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

该博客主要介绍了如何解决括号序列中最长正确匹配的子串长度问题。通过利用栈的数据结构,并对栈进行优化,存储带有标号的括号节点,实现了在遇到右括号时检查栈顶元素是否匹配并进行出栈或入栈操作。在遍历完整个括号序列后,通过计算栈中元素的标号差值,得到最长匹配的子串长度。博客提供了两种代码实现方式,分别是原始的栈处理和优化后的栈处理,优化后的代码更加简洁。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.题目描述

小洛看着一堆只包含’(‘和’)‘的括号序列犯愁了,小洛想知道这串序列里最长正确匹配的序列长度是多少,你能帮帮小洛吗?
输入
输入一个只包含’(‘与’)'的字符串,字符串长度n满足0<n≤50000
输出
输出一个数表示子串长度
数据范围
对于100%的数据,0<n≤50000
输入样例
((((())(
输出样例
4

2.问题分析

说到括号匹配问题,我们很容易想到利用栈来解决,但是直接使用栈来进行处理的话,你会发现,无法记录在入栈出栈过程中匹配的长度。因此,我们对栈进行优化,对于栈中存储的每个元素我们都把它当作一个结点,包含data(左括号或者右括号),以及标号值,用index表示。
Node示意图
对于一个序列"( ( ( ) ) ) ) )",首先我们将括号序列依次进行标号,分别为12345678我们让左括号直接入栈,遇到右括号则观察栈中栈顶元素是否为左括号,若是,则出栈;反之,则入栈。
反复进行入栈和出栈操作后,栈中只剩下标号为7、8的两个右括号,则最大匹配长度为7 - 0 - 1 = 6;
序列1
我们再来看一个序列"()()",标号分别为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. 输入((((())(测试1

  2. 输入()()测试2

  3. 输入)))))测试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();
    }
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兴趣使然黄小黄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值