带你思考经典栈问题

213 阅读2分钟

这是我参与更文挑战的第 8 天,活动详情查看: 更文挑战

栈的特性及简单使用

栈是我们在面试以及工作中常用到的一种数据结构。

它在计算机中也广泛的被应用着,如典型的方法调用上。

栈的特性--先进后出(LIFO)

经典题--判断括号的合法性

【题目】字符串中只有字符'('和')'。合法字符串需要括号可以配对。比如:

输入:"()"

输出:true

解释:(),()(),(())是合法的。)(,()(,(()是非法的。

请你实现一个函数,来判断给定的字符串是否合法。

分析题目的时候,我们可以通过以下四步来进行:

  1. 模拟:模拟正确的运行情况,从行为上完成。
  2. 找规律。
  3. 匹配:找到适合的数据结构与算法。
  4. 考虑边界,即特殊情况。

通过书写一个正确的例子,我们对其进行模拟,可以得到以下规律:

  • 只有所有的括号都完成匹配,才是合法的。
  • 配对的符号可以消除掉,直到最后没有任何字符,便是合法的。

匹配:我们可以发现,括号消除的这个行为跟栈的行为十分相似,因此可以用栈来进行模拟。

边界:

  • 字符串为空
  • 字符串个数为奇数个

可通过画图来辅助自己理解

boolean isValid(String s) {
  // 当字符串本来就是空的时候,我们可以快速返回true
  if (s == null || s.length() == 0) {
    return true;
  }
  
  // 当字符串长度为奇数的时候,不可能是一个有效的合法字符串
  if (s.length() % 2 == 1) {
    return false;
  }
  
  // 消除法的主要核心逻辑: 
  Stack<Character> t = new Stack<Character>();
  for (int i = 0; i < s.length(); i++) {
  
    // 取出字符
    char c = s.charAt(i);
    if (c == '(') {
      // 如果是'(',那么压栈
      t.push(c);
    } else if (c == ')') {
      // 如果是')',那么就尝试弹栈
      if (t.empty()) {
        // 如果弹栈失败,那么返回false
        return false;
      }
      t.pop();
    }
  return t.empty();
}

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

做完一道题,我们还要从2个角度进行深度思考:

  • 深度:这种解法还可以怎么优化?
  • 广度:这种解法具有普适性吗?可以推广吗?

深度扩展

如果仔细观察,你会发现,栈中存放的元素是一样的。全部都是左括号'(',除此之外,再也没有别的元素,优化方法如下。

栈中元素都相同时,实际上没有必要使用栈,只需要记录栈中元素个数。

public boolean isValid(String s) {

  // 当字符串本来就是空的时候,我们可以快速返回true
  if (s == null || s.length() == 0) {
    return true;
  }

  // 当字符串长度为奇数的时候,不可能是一个有效的合法字符串
  if (s.length() % 2 == 1) {
    return false;
  }

  // 消除法的主要核心逻辑:
  int leftBraceNumber = 0;

  for (int i = 0; i < s.length(); i++) {
    // 取出字符
    char c = s.charAt(i);
    
    if (c == '(') {
      // 如果是'(',那么压栈
      leftBraceNumber++;
    } else if (c == ')') {
      // 如果是')',那么就尝试弹栈
      if (leftBraceNumber <= 0) {
        // 如果弹栈失败,那么返回false
        return false;
      }
      --leftBraceNumber;
    }
  }
  return leftBraceNumber == 0;
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

广度扩展

观察题目可以发现,栈中只存放了一个维度的信息:左括号'('和右括号')'。如果栈中的内容变得更加丰富一点,就可以得到下面这道扩展题。

【题目扩展】给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。有效字符串需满足:

左括号必须用相同类型的右括号闭合

左括号必须以正确的顺序闭合

注意空字符串可被认为是有效字符串

class Solution {
    public boolean isValid(String s) {
        if (s == null || s.length() == 0) {
            return true;
        }
        if (s.length() % 2 == 1) {
            return false;
        }
        Stack<Character> t = new Stack<Character>();

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '{' || c == '(' || c == '[') {
                t.push(c);
            } else if (c == '}') {
                if (t.empty() || t.peek() != '{') {
                    return false;
                }
                t.pop();
            } else if (c == ']') {
                if (t.empty() || t.peek() != '[') {
                    return false;
                }
                t.pop();
            } else if (c == ')') {
                if (t.empty() || t.peek() != '(') {
                    return false;
                }
                t.pop();
            } else {
                return false;
            }
        }
        return t.empty();
    }
}

因此,以后你在看到题目中类似配对、消除之类的动作时,可以采用栈来操作。