米哈游面试算法题:有效的括号

简介: 米哈游面试算法题:有效的括号

有效的括号


描述:给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。


有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。
  • 每个右括号都有一个对应的相同类型的左括号。


函数签名:bool isValid(string s)


示例:

输入:s = "()"
输出:true
输入:s = "()[]{}"
输出:true
输入:s = "(]"
输出:false


约束条件:

  • 1 <= s.length <= 104
  • s 仅由括号 ‘()[]{}’ 组成


题目解析


这个问题可以通过使用栈来解决。栈是一种后进先出(LIFO)的数据结构,它非常适合处理这种需要“最近匹配”的情况,比如括号匹配。我们可以遍历给定的字符串,每当遇到一个左括号时,将其压入栈中,当遇到一个右括号时,我们将检查栈顶元素是否是相应的左括号,如果是,则弹出栈顶元素,否则返回 false。最后,如果栈为空,则说明所有的括号都能匹配,返回 true;否则,返回 false。


下面是算法的详细步骤:


  1. 初始化一个空栈。


  1. 遍历字符串的每个字符:
  • 如果当前字符是左括号(‘(’、‘[’、‘{’),则将其压入栈中。
  • 如果当前字符是右括号(‘)’、‘]’、‘}’),则检查栈是否为空:

如果栈为空,则返回 false,因为没有相应的左括号与之匹配。

如果栈不为空,则弹出栈顶元素,检查其是否与当前右括号匹配,若不匹配,则返回 false。


  1. 遍历结束后,检查栈是否为空:
  • 如果栈为空,则说明所有括号都能匹配,返回 true。
  • 如果栈不为空,则说明有左括号没有匹配的右括号,返回 false。


下面是对应的C++代码实现:

#include <stack>
#include <string>

class Solution {
public:
    bool isValid(string s) {
        stack<char> stk;
        for (char c : s) {
            if (c == '(' || c == '[' || c == '{') {
                stk.push(c);
            } else {
                if (stk.empty()) {
                    return false;
                }
                char topChar = stk.top();
                stk.pop();
                if ((c == ')' && topChar != '(') ||
                    (c == ']' && topChar != '[') ||
                    (c == '}' && topChar != '{')) {
                    return false;
                }
            }
        }
        return stk.empty();
    }
};


让我们通过一些示例来验证算法的正确性:


  • 对于输入字符串 “()”,栈会依次压入 ‘(’ 和 ‘)’,最后栈为空,返回 true。
  • 对于输入字符串 “()[]{}”,栈会依次压入 ‘(’, ‘[’, ‘{’,然后弹出 ‘{’, ‘[’, ‘(’,最后栈为空,返回 true。
  • 对于输入字符串 “(]”,栈会依次压入 ‘(’, ‘[’,然后弹出 ‘[’, ‘(’,此时栈为空,但是’]'没有对应的左括号,返回 false。


题目延伸


理解你的需求,你希望对给定的算法题目进行延伸和实际应用的讨论,并且希望我能够将内容分成五次发送给你,每次约1000字左右。我会尽力满足你的要求。让我们开始第一部分。


延伸与实际应用:有效的括号


  1. 算法复杂度分析:


  • 时间复杂度:遍历字符串一次,时间复杂度为 O(n),其中 n 是字符串的长度。
  • 空间复杂度:最坏情况下,当字符串中的字符都是左括号时,栈的大小会达到 n/2,因此空间复杂度为 O(n)。


  1. 括号匹配问题的变体:


括号匹配是一类常见的问题,可以延伸到更复杂的情况,如包含其他符号的括号匹配、带有优先级的表达式求值等。


  1. 错误信息提示:


在实际应用中,括号匹配可以用于编译器、解释器等程序中,以检查代码中括号的正确性。如果代码中存在括号不匹配的情况,编译器或解释器可以给出错误信息提示,帮助开发者快速定位问题所在。


实际应用


  1. 编程语言解析器:

在编程语言的解析器中,括号匹配是一个重要的步骤。解析器需要检查代码中的括号是否匹配,以确保代码的语法正确性。如果代码中存在不匹配的括号,解析器会报告错误并指出问题所在,帮助开发者及时修复代码。


代码演示:

#include <iostream>
#include <stack>
#include <unordered_map>
#include <string>

using namespace std;

class LanguageParser {
public:
    bool isValidCode(string code) {
        stack<char> brackets;
        unordered_map<char, char> bracketPairs = {{')', '('}, {']', '['}, {'}', '{'}};

        for (char c : code) {
            if (bracketPairs.find(c) != bracketPairs.end()) { // 右括号
                if (brackets.empty() || brackets.top() != bracketPairs[c]) {
                    return false; // 括号不匹配
                }
                brackets.pop();
            } else if (bracketPairs.values().find(c) != bracketPairs.values().end()) { // 左括号
                brackets.push(c);
            }
        }

        return brackets.empty(); // 所有左括号都匹配了
    }
};

int main() {
    LanguageParser parser;
    string code1 = "()";
    string code2 = "()[]{}";
    string code3 = "(]";

    cout << boolalpha; // 输出true/false而不是1/0
    cout << parser.isValidCode(code1) << endl; // 输出true
    cout << parser.isValidCode(code2) << endl; // 输出true
    cout << parser.isValidCode(code3) << endl; // 输出false

    return 0;
}


  1. 表达式求值:

在数学表达式的求值过程中,括号的匹配也起着重要的作用。括号可以改变表达式中运算的优先级,因此在求值之前,需要确保括号的正确匹配,以保证表达式求值的准确性。


以下是一个简单的表达式求值的算法示例,它可以处理加法、减法、乘法和括号操作。

#include <iostream>
#include <stack>
#include <string>

using namespace std;

class ExpressionEvaluator {
public:
    int evaluateExpression(string expression) {
        stack<int> nums;
        stack<char> ops;

        for (int i = 0; i < expression.length(); i++) {
            char c = expression[i];
            if (c == ' ') continue; // 忽略空格

            if (isdigit(c)) {
                int num = 0;
                while (i < expression.length() && isdigit(expression[i])) {
                    num = num * 10 + (expression[i] - '0');
                    i++;
                }
                i--; // 回退一步,因为在循环结束时i多增加了一次
                nums.push(num);
            } else if (c == '(') {
                ops.push(c);
            } else if (c == ')') {
                while (ops.top() != '(') {
                    processOperation(nums, ops);
                }
                ops.pop(); // 弹出左括号
            } else if (c == '+' || c == '-' || c == '*' || c == '/') {
                while (!ops.empty() && hasPrecedence(c, ops.top())) {
                    processOperation(nums, ops);
                }
                ops.push(c);
            }
        }

        while (!ops.empty()) {
            processOperation(nums, ops);
        }

        return nums.top();
    }

private:
    bool hasPrecedence(char op1, char op2) {
        if (op2 == '(' || op2 == ')') return false;
        if ((op1 == '*' || op1 == '/') && (op2 == '+' || op2 == '-')) return false;
        return true;
    }

    void processOperation(stack<int>& nums, stack<char>& ops) {
        int num2 = nums.top();
        nums.pop();
        int num1 = nums.top();
        nums.pop();
        char op = ops.top();
        ops.pop();

        int result = 0;
        switch (op) {
            case '+':
                result = num1 + num2;
                break;
            case '-':
                result = num1 - num2;
                break;
            case '*':
                result = num1 * num2;
                break;
            case '/':
                result = num1 / num2;
                break;
        }

        nums.push(result);
    }
};

int main() {
    ExpressionEvaluator evaluator;
    string expression = "2 * (3 + 4) - 9 / 3";

    cout << "Expression: " << expression << endl;
    cout << "Result: " << evaluator.evaluateExpression(expression) << endl;

    return 0;
}

这个算法使用了栈来处理操作数和操作符。它首先遍历表达式中的每个字符,然后根据情况进行处理。处理过程中,操作符的优先级和括号的处理是关键点。


  1. 正则表达式解析:

正则表达式是一种强大的模式匹配工具,在解析正则表达式时,也需要考虑括号的匹配。括号可以用来分组和捕获匹配的内容,因此在解析正则表达式时,需要确保括号的正确闭合,以保证模式匹配的准确性。


下面是一个简单的正则表达式解析算法示例,它可以解析包含括号、字符集合和通配符的正则表达式。

#include <iostream>
#include <stack>
#include <string>

using namespace std;

class RegularExpressionParser {
public:
    bool isMatch(string s, string p) {
        return isMatchHelper(s, p, 0, 0);
    }

private:
    bool isMatchHelper(string& s, string& p, int sIndex, int pIndex) {
        if (pIndex == p.length()) {
            return sIndex == s.length();
        }

        if (pIndex + 1 < p.length() && p[pIndex + 1] == '*') {
            if (isMatchHelper(s, p, sIndex, pIndex + 2)) {
                return true; // 匹配0次
            }
            while (sIndex < s.length() && (s[sIndex] == p[pIndex] || p[pIndex] == '.')) {
                if (isMatchHelper(s, p, sIndex + 1, pIndex + 2)) {
                    return true; // 匹配1次或多次
                }
                sIndex++;
            }
        } else if (sIndex < s.length() && (s[sIndex] == p[pIndex] || p[pIndex] == '.')) {
            return isMatchHelper(s, p, sIndex + 1, pIndex + 1); // 精确匹配
        }

        return false;
    }
};

int main() {
    RegularExpressionParser parser;
    string s = "mississippi";
    string p = "mis*is*p*.";

    cout << "String: " << s << endl;
    cout << "Pattern: " << p << endl;
    cout << "Is Match: " << boolalpha << parser.isMatch(s, p) << endl;

    return 0;
}

这个算法采用递归的方式处理正则表达式的匹配过程。它可以处理通配符 . 和 *,其中 . 可以匹配任意字符,* 表示前一个字符可以出现零次或多次。


  1. 括号匹配的优化:

在实际开发中,我们可以对括号匹配算法进行优化。例如,可以通过使用哈希表来存储左右括号的对应关系,这样可以提高查找速度。此外,还可以通过使用位运算或其他技巧来减少空间复杂度。

下面是一个括号匹配优化的算法示例,它使用了哈希表来存储左右括号的对应关系,以提高查找速度。

#include <iostream>
#include <stack>
#include <unordered_map>
#include <string>

using namespace std;

class BracketMatcher {
public:
    bool isBracketValid(string s) {
        stack<char> brackets;
        unordered_map<char, char> bracketPairs = {{')', '('}, {']', '['}, {'}', '{'}};

        for (char c : s) {
            if (bracketPairs.find(c) != bracketPairs.end()) { // 右括号
                if (brackets.empty() || brackets.top() != bracketPairs[c]) {
                    return false; // 括号不匹配
                }
                brackets.pop();
            } else if (bracketPairs.find(c) == bracketPairs.end()) { // 左括号
                brackets.push(c);
            }
        }

        return brackets.empty(); // 所有左括号都匹配了
    }
};

int main() {
    BracketMatcher matcher;
    string s1 = "()[]{}";
    string s2 = "([)]";

    cout << boolalpha; // 输出true/false而不是1/0
    cout << matcher.isBracketValid(s1) << endl; // 输出true
    cout << matcher.isBracketValid(s2) << endl; // 输出false

    return 0;
}

这个算法与之前的括号匹配算法类似,但是使用了哈希表存储左右括号的对应关系。这样,可以在 O(1) 的时间复杂度内找到对应的右括号,从而提高了算法的效率。

相关文章
|
1天前
|
开发框架 算法 搜索推荐
C# .NET面试系列九:常见的算法
#### 1. 求质数 ```c# // 判断一个数是否为质数的方法 public static bool IsPrime(int number) { if (number < 2) { return false; } for (int i = 2; i <= Math.Sqrt(number); i++) { if (number % i == 0) { return false; } } return true; } class Progr
64 1
|
1天前
|
负载均衡 算法 应用服务中间件
面试题:Nginx有哪些负载均衡算法?Nginx位于七层网络结构中的哪一层?
字节跳动面试题:Nginx有哪些负载均衡算法?Nginx位于七层网络结构中的哪一层?
45 0
|
14小时前
|
移动开发 算法 搜索推荐
2024最新Android算法相关面试大全,请查收
2024最新Android算法相关面试大全,请查收
|
1天前
|
存储 缓存 算法
面试遇到算法题:实现LRU缓存
V哥的这个实现的关键在于维护一个双向链表,它可以帮助我们快速地访问、更新和删除最近最少使用的节点,同时使用哈希表来提供快速的查找能力。这样,我们就可以在 O(1) 的时间复杂度内完成所有的缓存操作。哈哈干净利索,回答完毕。
|
1天前
|
算法 搜索推荐 Python
数据结构与算法在Python面试中的应用实例
【4月更文挑战第13天】本文聚焦Python面试中的数据结构与算法问题,包括排序算法、链表操作和树图遍历。重点讨论了快速排序、链表反转和二叉树前序遍历的实现,并指出理解算法原理、处理边界条件及递归操作是避免错误的关键。通过实例代码和技巧分享,帮助面试者提升面试表现。
13 0
|
1天前
|
设计模式 算法 Java
如何在面试中应对编程与算法面试?
面试中,编程能力至关重要,主要分为三个层次:初级关注基本功,如语法、原理和常见问题解决;高级涉及数据结构与算法,基础算法如排序对中小厂重要,大厂则需深入数据结构;资深专家层次需精通设计模式,以保证代码的扩展性和维护性。提升编程技能可采用PDCA循环学习法,从计划到执行、检查、行动不断迭代。通过实践项目如开发后端系统、测试框架来检验学习成果,并逐步学习算法和设计模式。坚持不懈的努力和重构将助你成为技术专家。记住,超越大多数人的关键在于持续学习和专注深耕。
8 0
如何在面试中应对编程与算法面试?
|
1天前
|
存储 移动开发 算法
面试时写不出排序算法?看这篇就够了
面试时写不出排序算法?看这篇就够了
93 0
|
1天前
|
算法
覃超老师 算法面试通关40讲
无论是阿里巴巴、腾讯、百度这些国内一线互联网企业,还是 Google、Facebook、Airbnb 等硅谷知名互联网公司,在招聘工程师的过程中,对算法和数据结构能力的考察都是重中之重。本课程以帮助求职者在短时间内掌握面试中最常见的算法与数据结构相关知识点,学会面试中高频算法题目的分析思路,同时给大家从面试官的角度来分析算法题的解答技巧,从而更有效地提升求职者的面试通过率。
16 3
覃超老师 算法面试通关40讲
|
1天前
|
存储 Java
面试官:素有Java锁王称号的‘StampedLock’你知道吗?我:这什么鬼?
面试官:素有Java锁王称号的‘StampedLock’你知道吗?我:这什么鬼?
43 23
|
1天前
|
消息中间件 安全 前端开发
字节面试:说说Java中的锁机制?
Java 中的锁(Locking)机制主要是为了解决多线程环境下,对共享资源并发访问时的同步和互斥控制,以确保共享资源的安全访问。 锁的作用主要体现在以下几个方面: 1. **互斥访问**:确保在任何时刻,只有一个线程能够访问特定的资源或执行特定的代码段。这防止了多个线程同时修改同一资源导致的数据不一致问题。 2. **内存可见性**:通过锁的获取和释放,可以确保在锁保护的代码块中对共享变量的修改对其他线程可见。这是因为 Java 内存模型(JMM)规定,对锁的释放会把修改过的共享变量从线程的工作内存刷新到主内存中,而获取锁时会从主内存中读取最新的共享变量值。 3. **保证原子性**:锁
16 1