探索Java中求解最长有效括号子串的奥秘
在编程的世界里,字符串处理问题总是充满挑战与趣味。今天,我们来深入探讨一道经典的字符串问题——寻找字符串中最长有效括号子串的长度。这不仅是对字符串操作技巧的考验,更是对逻辑思维和数据结构运用能力的一次锻炼。
一、问题描述
给定一个只包含 ‘(’ 和 ‘)’ 的字符串,我们的任务是找出其中最长有效(格式正确且连续)括号子串的长度。例如,对于字符串 “(()())”,最长有效括号子串就是其本身,长度为6;而对于字符串 “(()”,最长有效括号子串为 “()”,长度为2。
二、解题思路与代码实现
解决这个问题,我们可以巧妙地运用栈(Stack)这一数据结构。在Java中,我们可以使用Deque
接口结合LinkedList
类来模拟栈的操作。下面是详细的代码实现:
class Solution {
public int longestValidParentheses(String s) {
int maxans = 0;
Deque<Integer> stack = new LinkedList<Integer>();
stack.push(-1);
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
stack.push(i);
} else {
stack.pop();
if (stack.isEmpty()) {
stack.push(i);
} else {
maxans = Math.max(maxans, i - stack.peek());
}
}
}
return maxans;
}
}
- 初始化部分:
maxans
用于记录我们找到的最长有效括号子串的长度,初始值设为0。因为在还未开始处理字符串时,我们还未找到任何有效子串。stack
是一个双端队列,这里我们使用LinkedList
来实现栈的功能。特别地,我们首先将 -1 压入栈中。这个 -1 起到了一个重要的边界标记作用,它为后续计算有效子串长度提供了便利。
- 遍历字符串:
- 遇到左括号 ‘(’:当我们在字符串中遇到左括号时,执行
stack.push(i)
,将当前左括号的索引i
压入栈中。这是因为左括号需要等待对应的右括号出现来完成匹配,我们通过记录其索引,方便后续找到与之匹配的右括号。 - 遇到右括号 ‘)’:当遇到右括号时,首先执行
stack.pop()
。这一步是尝试将当前右括号与栈顶的左括号进行匹配。如果栈为空,说明当前右括号没有找到与之匹配的左括号,此时执行stack.push(i)
,将当前右括号的索引i
压入栈中,作为新的边界。如果栈不为空,说明找到了匹配的左括号,此时计算当前有效括号子串的长度。计算方式为i - stack.peek()
,即当前右括号的索引i
减去栈顶元素(最近匹配的左括号的前一个索引)。然后使用Math.max(maxans, i - stack.peek())
来更新maxans
,确保maxans
始终记录着到目前为止找到的最长有效括号子串的长度。
- 遇到左括号 ‘(’:当我们在字符串中遇到左括号时,执行
- 返回结果:
- 当整个字符串遍历结束后,
maxans
中存储的值就是我们要找的最长有效括号子串的长度,直接返回maxans
即可。
- 当整个字符串遍历结束后,
三、代码示例分析
为了更直观地理解代码的运行过程,我们以字符串 “(()” 为例来详细分析。
- 初始化阶段:
maxans
为0,stack
中只有一个元素 -1。
- 遍历过程:
- 当
i = 0
,字符为(
,执行stack.push(0)
,此时stack
为[-1, 0]
。 - 当
i = 1
,字符为(
,执行stack.push(1)
,此时stack
为[-1, 0, 1]
。 - 当
i = 2
,字符为)
,执行stack.pop()
,stack
变为[-1, 0]
。由于栈不为空,计算maxans = Math.max(0, 2 - 0) = 2
,此时maxans
更新为2。
- 当
- 最终结果:
- 遍历结束,返回
maxans
,值为2,即字符串 “(()” 的最长有效括号子串长度为2,最长有效括号子串为 “()”。
- 遍历结束,返回
再看字符串 “(()())” 的情况:
- 初始化阶段:
- 同样,
maxans
为0,stack
中只有 -1。
- 同样,
- 遍历过程:
- 当
i = 0
,字符为(
,stack.push(0)
,stack
为[-1, 0]
。 - 当
i = 1
,字符为(
,stack.push(1)
,stack
为[-1, 0, 1]
。 - 当
i = 2
,字符为)
,stack.pop()
,stack
变为[-1, 0]
,计算maxans = Math.max(0, 2 - 0) = 2
。 - 当
i = 3
,字符为(
,stack.push(3)
,stack
为[-1, 0, 3]
。 - 当
i = 4
,字符为(
,stack.push(4)
,stack
为[-1, 0, 3, 4]
。 - 当
i = 5
,字符为)
,stack.pop()
,stack
变为[-1, 0, 3]
,计算maxans = Math.max(2, 5 - 3) = 2
。 - 当
i = 6
,字符为)
,stack.pop()
,stack
变为[-1, 0]
,计算maxans = Math.max(2, 6 - 0) = 6
。
- 当
- 最终结果:
- 遍历结束,返回
maxans
,值为6,即字符串 “(()())” 的最长有效括号子串长度为6,最长有效括号子串就是 “(()())” 本身。
- 遍历结束,返回
四、复杂度分析
- 时间复杂度:代码中通过一个
for
循环遍历字符串一次,时间复杂度为 O ( n ) O(n) O(n),其中 n n n是字符串的长度。因为每个字符只被访问一次,所以时间复杂度与字符串长度成正比。 - 空间复杂度:使用了一个栈来辅助计算,栈中最多可能存储 n n n个元素(在极端情况下,如字符串全部是左括号),所以空间复杂度为 O ( n ) O(n) O(n)。
通过对这道题目的详细分析,我们不仅掌握了如何使用栈来解决最长有效括号子串问题,还深入理解了代码的执行过程和复杂度。希望这篇博客能帮助大家在字符串处理和算法学习的道路上更进一步。在实际编程中,类似的思路和技巧可以应用到各种复杂的字符串处理场景中,提升代码的效率和质量。