<p align="center">
<a href="https://www.programmercarl.com/xunlian/xunlianying.html" target="_blank">
  <img src="../pics/训练营.png" width="1000"/>
</a>
<p align="center"><strong><a href="./qita/join.md">参与本项目</a>，贡献其他语言版本的代码，拥抱开源，让更多学习算法的小伙伴们受益！</strong></p>



> 数据结构与算法应用往往隐藏在我们看不到的地方

# 20. 有效的括号

[力扣题目链接](https://leetcode.cn/problems/valid-parentheses/)

给定一个只包括 '('，')'，'{'，'}'，'['，']' 的字符串，判断字符串是否有效。

有效字符串需满足：
* 左括号必须用相同类型的右括号闭合。
* 左括号必须以正确的顺序闭合。
* 注意空字符串可被认为是有效字符串。

示例 1:
* 输入: "()"  
* 输出: true     

示例 2:
* 输入: "()[]{}"    
* 输出: true     

示例 3:
* 输入: "(]"   
* 输出: false    

示例 4:
* 输入: "([)]"   
* 输出: false 

示例 5:
* 输入: "{[]}"
* 输出: true

## 算法公开课

**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html)：[栈的拿手好戏！| LeetCode：20. 有效的括号](https://www.bilibili.com/video/BV1AF411w78g)，相信结合视频再看本篇题解，更有助于大家对本题的理解**。

## 思路

### 题外话

**括号匹配是使用栈解决的经典问题。**

题意其实就像我们在写代码的过程中，要求括号的顺序是一样的，有左括号，相应的位置必须要有右括号。

如果还记得编译原理的话，编译器在 词法分析的过程中处理括号、花括号等这个符号的逻辑，也是使用了栈这种数据结构。

再举个例子，linux系统中，cd这个进入目录的命令我们应该再熟悉不过了。

```
cd a/b/c/../../
```

这个命令最后进入a目录，系统是如何知道进入了a目录呢 ，这就是栈的应用（其实可以出一道相应的面试题了）

所以栈在计算机领域中应用是非常广泛的。

有的同学经常会想学的这些数据结构有什么用，也开发不了什么软件，大多数同学说的软件应该都是可视化的软件例如APP、网站之类的，那都是非常上层的应用了，底层很多功能的实现都是基础的数据结构和算法。

**所以数据结构与算法的应用往往隐藏在我们看不到的地方！**

这里我就不过多展开了，先来看题。

### 进入正题

由于栈结构的特殊性，非常适合做对称匹配类的题目。

首先要弄清楚，字符串里的括号不匹配有几种情况。

**一些同学，在面试中看到这种题目上来就开始写代码，然后就越写越乱。**

建议在写代码之前要分析好有哪几种不匹配的情况，如果不在动手之前分析好，写出的代码也会有很多问题。

先来分析一下 这里有三种不匹配的情况，


1. 第一种情况，字符串里左方向的括号多余了 ，所以不匹配。
![括号匹配1](https://code-thinking-1253855093.file.myqcloud.com/pics/2020080915505387.png)

2. 第二种情况，括号没有多余，但是 括号的类型没有匹配上。
![括号匹配2](https://code-thinking-1253855093.file.myqcloud.com/pics/20200809155107397.png)

3. 第三种情况，字符串里右方向的括号多余了，所以不匹配。
![括号匹配3](https://code-thinking-1253855093.file.myqcloud.com/pics/20200809155115779.png)



我们的代码只要覆盖了这三种不匹配的情况，就不会出问题，可以看出 动手之前分析好题目的重要性。

动画如下：

![20.有效括号](https://code-thinking.cdn.bcebos.com/gifs/20.有效括号.gif)


第一种情况：已经遍历完了字符串，但是栈不为空，说明有相应的左括号没有右括号来匹配，所以return false

第二种情况：遍历字符串匹配的过程中，发现栈里没有要匹配的字符。所以return false

第三种情况：遍历字符串匹配的过程中，栈已经为空了，没有匹配的字符了，说明右括号没有找到对应的左括号return false

那么什么时候说明左括号和右括号全都匹配了呢，就是字符串遍历完之后，栈是空的，就说明全都匹配了。

分析完之后，代码其实就比较好写了，

但还有一些技巧，在匹配左括号的时候，右括号先入栈，就只需要比较当前元素和栈顶相不相等就可以了，比左括号先入栈代码实现要简单的多了！

实现C++代码如下：


```CPP
class Solution {
public:
    bool isValid(string s) {
        if (s.size() % 2 != 0) return false; // 如果s的长度为奇数，一定不符合要求
        stack<char> st;
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == '(') st.push(')');
            else if (s[i] == '{') st.push('}');
            else if (s[i] == '[') st.push(']');
            // 第三种情况：遍历字符串匹配的过程中，栈已经为空了，没有匹配的字符了，说明右括号没有找到对应的左括号 return false
            // 第二种情况：遍历字符串匹配的过程中，发现栈里没有我们要匹配的字符。所以return false
            else if (st.empty() || st.top() != s[i]) return false;
            else st.pop(); // st.top() 与 s[i]相等，栈弹出元素
        }
        // 第一种情况：此时我们已经遍历完了字符串，但是栈不为空，说明有相应的左括号没有右括号来匹配，所以return false，否则就return true
        return st.empty();
    }
};

```
* 时间复杂度: O(n)
* 空间复杂度: O(n)

技巧性的东西没有固定的学习方法，还是要多看多练，自己灵活运用了。


## 其他语言版本

### Java：

```Java
class Solution {
    public boolean isValid(String s) {
        Deque<Character> deque = new LinkedList<>();
        char ch;
        for (int i = 0; i < s.length(); i++) {
            ch = s.charAt(i);
            //碰到左括号，就把相应的右括号入栈
            if (ch == '(') {
                deque.push(')');
            }else if (ch == '{') {
                deque.push('}');
            }else if (ch == '[') {
                deque.push(']');
            } else if (deque.isEmpty() || deque.peek() != ch) {
                return false;
            }else {//如果是右括号判断是否和栈顶元素匹配
                deque.pop();
            }
        }
        //最后判断栈中元素是否匹配
        return deque.isEmpty();
    }
}
```

### Python：

```python
# 方法一，仅使用栈，更省空间
class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        
        for item in s:
            if item == '(':
                stack.append(')')
            elif item == '[':
                stack.append(']')
            elif item == '{':
                stack.append('}')
            elif not stack or stack[-1] != item:
                return False
            else:
                stack.pop()
        
        return True if not stack else False
```

```python
# 方法二，使用字典
class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        mapping = {
            '(': ')',
            '[': ']',
            '{': '}'
        }
        for item in s:
            if item in mapping.keys():
                stack.append(mapping[item])
            elif not stack or stack[-1] != item: 
                return False
            else: 
                stack.pop()
        return True if not stack else False
```

### Go：

```Go
// 思路： 使用栈来进行括号的匹配
// 时间复杂度 O(n)
// 空间复杂度 O(n)
func isValid(s string) bool {
	// 使用切片模拟栈的行为
	stack := make([]rune, 0)

	// m 用于记录某个右括号对应的左括号
	m := make(map[rune]rune)
	m[')'] = '('
	m[']'] = '['
	m['}'] = '{'

	// 遍历字符串中的 rune
	for _, c := range s {
		// 左括号直接入栈
		if c == '(' || c == '[' || c == '{' {
			stack = append(stack, c)
		} else {
			// 如果是右括号，先判断栈内是否还有元素
			if len(stack) == 0 {
				return false
			}
			// 再判断栈顶元素是否能够匹配
			peek := stack[len(stack)-1]
			if peek != m[c] {
				return false
			}
			// 模拟栈顶弹出
			stack = stack[:len(stack)-1]
		}
	}

	// 若栈中不再包含元素，则能完全匹配
	return len(stack) == 0
}
```

### Ruby:

```ruby
def is_valid(strs)
    symbol_map = {')' => '(', '}' => '{', ']' => '['}
    stack = []
    strs.size.times {|i|
        c = strs[i]
        if symbol_map.has_key?(c)
            top_e = stack.shift
            return false if symbol_map[c] != top_e
        else
            stack.unshift(c)
        end
    }
    stack.empty?
end
```

### Javascript:

```javascript
var isValid = function (s) {
  const stack = [];
  for (let i = 0; i < s.length; i++) {
    let c = s[i];
    switch (c) {
      case '(':
        stack.push(')');
        break;
      case '[':
        stack.push(']');
        break;
      case '{':
        stack.push('}');
        break;
      default:
        if (c !== stack.pop()) {
          return false;
        }
    }
  }
  return stack.length === 0;
};
// 简化版本
var isValid = function(s) {
    const stack = [], 
        map = {
            "(":")",
            "{":"}",
            "[":"]"
        };
    for(const x of s) {
        if(x in map) {
            stack.push(x);
            continue;
        };
        if(map[stack.pop()] !== x) return false;
    }
    return !stack.length;
};
```

### TypeScript:

版本一：普通版

```typescript
function isValid(s: string): boolean {
    let helperStack: string[] = [];
    for (let i = 0, length = s.length; i < length; i++) {
        let x: string = s[i];
        switch (x) {
            case '(':
                helperStack.push(')');
                break;
            case '[':
                helperStack.push(']');
                break;
            case '{':
                helperStack.push('}');
                break;
            default:
                if (helperStack.pop() !== x) return false;
                break;
        }
    }
    return helperStack.length === 0;
};
```

版本二：优化版

```typescript
function isValid(s: string): boolean {
    type BracketMap = {
        [index: string]: string;
    }
    let helperStack: string[] = [];
    let bracketMap: BracketMap = {
        '(': ')',
        '[': ']',
        '{': '}'
    }
    for (let i of s) {
        if (bracketMap.hasOwnProperty(i)) {
            helperStack.push(bracketMap[i]);
        } else if (i !== helperStack.pop()) {
            return false;
        }
    }
    return helperStack.length === 0;
};
```

### Swift:

```swift
func isValid(_ s: String) -> Bool {
    var stack = [String.Element]()
    for ch in s {
        if ch == "(" {
            stack.append(")")
        } else if ch == "{" {
            stack.append("}")
        } else if ch == "[" {
            stack.append("]")
        } else {
            let top = stack.last
            if ch == top {
                stack.removeLast()
            } else {
                return false
            }
        }
    }
    return stack.isEmpty
}
```

### C:

```C
//辅助函数：判断栈顶元素与输入的括号是否为一对。若不是，则返回False
int notMatch(char par, char* stack, int stackTop) {
    switch(par) {
        case ']':
            return stack[stackTop - 1] != '[';
        case ')':
            return stack[stackTop - 1] != '(';
        case '}':
            return stack[stackTop - 1] != '{';
    }
    return 0;
}

bool isValid(char * s){
    int strLen = strlen(s);
    //开辟栈空间
    char stack[5000];
    int stackTop = 0;

    //遍历字符串
    int i;
    for(i = 0; i < strLen; i++) {
        //取出当前下标所对应字符
        char tempChar = s[i];
        //若当前字符为左括号，则入栈
        if(tempChar == '(' || tempChar == '[' || tempChar == '{')
            stack[stackTop++] = tempChar;
        //若当前字符为右括号，且栈中无元素或右括号与栈顶元素不符，返回False
        else if(stackTop == 0 || notMatch(tempChar, stack, stackTop))
            return 0;
        //当前字符与栈顶元素为一对括号，将栈顶元素出栈
        else
            stackTop--;
    }
    //若栈中有元素，返回False。若没有元素（stackTop为0），返回True
    return !stackTop;
}
```

### C#:

```csharp
public class Solution {
    public bool IsValid(string s) {
        var len = s.Length;
        if(len % 2 == 1) return false; // 字符串长度为单数，直接返回 false
        // 初始化栈
        var stack = new Stack<char>();
        // 遍历字符串
        for(int i = 0; i < len; i++){
            // 当字符串为左括号时，进栈对应的右括号
            if(s[i] == '('){
                stack.Push(')');
            }else if(s[i] == '['){
                stack.Push(']');
            }else if(s[i] == '{'){
                stack.Push('}');
            }
            // 当字符串为右括号时，当栈为空（无左括号） 或者 出栈字符不是当前的字符
            else if(stack.Count == 0 || stack.Pop() != s[i])
                return false;
        }
        // 如果栈不为空，例如“((()”，右括号少于左括号，返回false
        if (stack.Count > 0)
            return false;
        // 上面的校验都满足，则返回true
        else
            return true;
    }
}
```

### PHP:

```php
// https://www.php.net/manual/zh/class.splstack.php
class Solution
{
    function isValid($s){
        $stack = new SplStack();
        for ($i = 0; $i < strlen($s); $i++) {
            if ($s[$i] == "(") {
                $stack->push(')');
            } else if ($s[$i] == "{") {
                $stack->push('}');
            } else if ($s[$i] == "[") {
                $stack->push(']');
            // 2、遍历匹配过程中，发现栈内没有要匹配的字符 return false
            // 3、遍历匹配过程中，栈已为空，没有匹配的字符了，说明右括号没有找到对应的左括号 return false
            } else if ($stack->isEmpty() || $stack->top() != $s[$i]) {
                return false;
            } else {//$stack->top() == $s[$i]
                $stack->pop();
            }
        }
        // 1、遍历完，但是栈不为空,说明有相应的括号没有被匹配,return false
        return $stack->isEmpty();
    }
}
```

### Scala:

```scala
object Solution {
  import scala.collection.mutable
  def isValid(s: String): Boolean = {
    if(s.length % 2 != 0) return false    // 如果字符串长度是奇数直接返回false
    val stack = mutable.Stack[Char]()
    // 循环遍历字符串
    for (i <- s.indices) {
      val c = s(i)
      if (c == '(' || c == '[' || c == '{') stack.push(c)
      else if(stack.isEmpty) return false   // 如果没有(、[、{则直接返回false
      // 以下三种情况，不满足则直接返回false        
      else if(c==')' && stack.pop() != '(') return false 
      else if(c==']' && stack.pop() != '[') return false 
      else if(c=='}' && stack.pop() != '{') return false 
    }
    // 如果为空则正确匹配，否则还有余孽就不匹配
    stack.isEmpty
  }
}
```

### Rust:

```rust
impl Solution {
    pub fn is_valid(s: String) -> bool {
        if s.len() % 2 == 1 {
            return false;
        }
        let mut stack = vec![];
        let mut chars: Vec<char> = s.chars().collect();
        while let Some(s) = chars.pop() {
            match s {
                ')' => stack.push('('),
                ']' => stack.push('['),
                '}' => stack.push('{'),
                _ => {
                    if stack.is_empty() || stack.pop().unwrap() != s {
                        return false;
                    }
                }
            }
        }
        stack.is_empty()
    }
}
```

<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
  <img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a>
