Loading

LeetCode-Stack-Easy 栈

简单题

1. 有效的括号(leetcode-20)

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

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

示例 1:       输入: "()"          输出: true
示例 2:       输入: "()[]{}"      输出: true
示例 3:       输入: "(]"          输出: false
示例 4:       输入: "([)]"        输出: false
示例 5:       输入: "{[]}"        输出: true 

Related Topics 栈 字符串

1) 栈

遍历输入字符串

  1. 当前字符是左括号时,入栈;

  2. 当前字符是右括号时,观察栈顶元素

    • 栈顶元素为与当前右括号匹配的左括号,弹出左括号,继续遍历字符串(说明两者相匹配)。
    • 栈顶元素不是当前右括号匹配的左括号,返回false(括号不匹配)。
使用栈+Hashmap实现

import java.util.HashMap;
import java.util.Stack;

class Solution {

public boolean isValid(String s) {
    if(s.length()%2==1){
        return false;
    }

    HashMap<Character, Character> map = new HashMap<>();
    map.put(')', '(');
    map.put(']', '[');
    map.put('}', '{');

    Stack<Character> stack = new Stack<>();

    for(int i=0; i<s.length(); i++){
        char ch = s.charAt(i);
        if(ch == '(' || ch == '[' || ch == '{'){
            stack.push(ch);
        }else if(!stack.empty() && stack.peek() == map.get(ch)){
            stack.pop();
        }else{
            return false;
        }
    }

    return stack.empty();

}

}

也可以不使用Hashmap

栈实现

public boolean isValid(String s) {
        if(s.isEmpty())
            return true;
        Stack stack=new Stack();
        for(char c:s.toCharArray()){
            if(c=='(')
                stack.push(')');
            else if(c=='{')
                stack.push('}');
            else if(c=='[')
                stack.push(']');
            else if(stack.empty()||c!=stack.pop())
                return false;
        }
        return stack.empty();
    }

2)字符串替换

不推荐 复杂度高,仅作为了解。时间复杂度是O(n平方)。

展示代码

public boolean isValid(String s) {
        if (s.contains("()") || s.contains("[]") || s.contains("{}")) {
            return isValid(s.replace("()", "").replace("[]", "").replace("{}", ""));
        } else {
            return "".equals(s);
        }
}

2. 最小栈(leetcode-155)

设计一个支持 push ,pop ,top 操作,并能在【常数时间】内检索到最小元素的栈。 
1. push(x) —— 将元素 x 推入栈中。 
2. pop() —— 删除栈顶的元素。 
3. top() —— 获取栈顶元素。 
4. getMin() —— 检索栈中的最小元素。 

示例: 
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

提示: pop、top 和 getMin 操作总是在 非空栈 上调用。 

Related Topics 栈 设计

1)辅助栈 MinStack存储最小值

思路一

对于栈来说,如果一个元素 a 在入栈时,栈里有其它的元素 b, c, d,那么无论这个栈在之后经历了什么操作,只要 a 在栈中,b, c, d 就一定在栈中,因为在 a 被弹出之前,b, c, d 不会被弹出。

因此,在操作过程中的任意一个时刻,只要栈顶的元素是 a,那么就可以确定栈里面现在的元素一定是 a, b, c, d。

那么,在每个元素 a 入栈时把当前栈的最小值 m 存储起来。在这之后无论何时,如果栈顶元素是 a,就可以直接返回存储的最小值 m。

基于上述思路

只需要设计一个数据结构,使得每个元素 a 与其相应的最小值 m 时刻保持一一对应。因此可以使用一个辅助栈,与元素栈同步插入与删除,用于存储与每个元素对应的最小值。

  1. 当一个元素要入栈时,取当前辅助栈的栈顶存储的最小值,与当前元素比较得出最小值,将这个最小值插入辅助栈中;

  2. 当一个元素要出栈时,把辅助栈的栈顶元素也一并弹出;

  3. 在任意一个时刻,栈内元素的最小值就存储在辅助栈的栈顶元素中。

辅助栈 MinStack存储最小值

import java.util.Stack;

class MinStack {

private <Integer> s;
private <Integer> minStack;

/** initialize your data structure here. */
public MinStack() {
    s = new Stack<>();
    minStack = new Stack<>();
}

public void push(int x) {
    s.push(x);
    if(s.isEmpty()){
        minStack.push(x);
    }else{
        minStack.push(Math.min(x, minStack.peek()));
    }
}

public void pop() {
    if(!s.isEmpty()){
        s.pop();
        minStack.pop();
    }
}

public int top() {
    if(!s.isEmpty()){
        return s.peek();
    }else{
        throw new RuntimeException("stack is empty");
    }
}

public int getMin() {
    if(!s.isEmpty()){
        return minStack.peek();
    }else{
        throw new RuntimeException("stack is empty");
    }
}

}

思路二

  1. 将第一个元素入栈。

  2. 新加入的元素如果【大于】栈顶元素,那么新加入的元素就【不处理】。

  3. 新加入的元素如果【小于等于】栈顶元素,那么就将新元素【入栈】。

  4. 出栈元素【不等于】栈顶元素,【不操作】。

  5. 出栈元素【等于】栈顶元素,那么就将栈顶元素【出栈】。

辅助栈思路二

class MinStack {
/** initialize your data structure here. */
private Stack<Integer> stack;
private Stack<Integer> minStack;

public MinStack() {
    stack = new Stack<>();
    minStack = new Stack<>();
}

public void push(int x) {
    stack.push(x);
    if (!minStack.isEmpty()) {
        int top = minStack.peek();
        //小于的时候才入栈
        if (x <= top) {
            minStack.push(x);
        }
    }else{
        minStack.push(x);
    }
}

public void pop() {
    int pop = stack.pop();

    int top = minStack.peek();
    //等于的时候再出栈
    if (pop == top) {
        minStack.pop();
    }

}

public int top() {
    return stack.peek();
}

public int getMin() {
    return minStack.peek();
}

}

2)单向链表(增加一个min值域)

用一个链表即可实现栈的基本功能,在 Node 节点中增加一个 min 字段,每次加入一个节点时,只要同时确定它的 min 值即可。

单向链表实现最小栈

class MinStack {
class Node{

    int value;
    int min;
    Node next;

    Node(int v, int min){
        this.value = v;
        this.min = min;
        next = null;
    }
}

Node head;
/** initialize your data structure here. */
public MinStack() {
    head = new Node(-1,-1);
}

public void push(int x) {
    if(head.next == null){
        head.next = new Node(x, x);
    }else{
        // 头插:每次像链表头插入数据
        Node t = new Node(x, Math.min(x, head.next.min));
        t.next = head.next;
        head.next = t;
    }
}

public void pop() {
    if(head.next != null){
        head.next = head.next.next;
    }else{
        throw new RuntimeException("stack is empty");
    }
}

public int top() {
    if(head.next != null){
        return head.next.value;
    }else{
        throw new RuntimeException("stack is empty");
    }
}

public int getMin() {
    if(head.next != null){
        return head.next.min;
    }else{
        throw new RuntimeException("stack is empty");
    }
}

}

3)一个栈同时存储元素与最小值

  • push操作,更新最小值时,同时将之前的最小值入栈。

  • pop操作,当删除的元素为最小值时,一次弹出两个元素,第二次弹出的元素为新的最小值

入栈 3 
|   |   min = 3
|   |     
|_3_|    
stack   

入栈 5 
|   |   min = 3
| 5 |     
|_3_|    
stack  

入栈 2 ,同时将之前的 min 值 3 入栈,再把 2 入栈,同时更新 min = 2
| 2 |   min = 2
| 3 |  
| 5 |     
|_3_|    
stack  

入栈 6 
| 6 |  min = 2
| 2 |   
| 3 |  
| 5 |     
|_3_|    
stack  

出栈 6     
| 2 |   min = 2
| 3 |  
| 5 |     
|_3_|    
stack  

出栈 2     
| 2 |   min = 2
| 3 |  
| 5 |     
|_3_|    
stack  

出栈 2     
|   |  min = 3   
| 5 |   
|_3_|    
stack  
一个栈同时存储元素与最小值

class MinStack {
int min = Integer.MAX_VALUE;
Stack<Integer> stack = new Stack<Integer>();
public void push(int x) {
    //当前值更小
    if(x <= min){   //等于的时候要push,连续多个相等,就push多个。因为出栈的时候,会pop多个。
        //将之前的最小值保存
        stack.push(min);
        //更新最小值
        min=x;
    }
    stack.push(x);
}

public void pop() {
    //如果弹出的值是最小值,那么将下一个元素更新为最小值
    if(stack.pop() == min) {
        min=stack.pop();
    }
}

public int top() {
    return stack.peek();
}

public int getMin() {
    return min;
}

}

4)一个栈存储元素差值

  1. 每次存入的是原来值 - 当前最小值

  2. 当原来值大于等于当前最小值的时候,存入的肯定就是非负数,所以出栈的时候就是栈中的值 + 当前最小值

  3. 当原来值小于当前最小值的时候,存入的肯定就是负值,此时的值不入栈,用 min 保存起来,同时将差值入栈。

  4. 当后续如果出栈元素是负数的时候,那么要出栈的元素其实就是 min。此外之前的 min 值,可以通过栈顶的值和当前 min 值进行还原,就是用 min 减去栈顶元素即可。

入栈 3,存入 3 - 3 = 0
|   |   min = 3
|   |     
|_0_|    
stack   

入栈 5,存入 5 - 3 = 2
|   |   min = 3
| 2 |     
|_0_|    
stack  

入栈 2,因为出现了更小的数,所以我们会存入一个负数,这里很关键
也就是存入  2 - 3 = -1, 并且更新 min = 2 
对于之前的 min 值 3, 我们只需要用更新后的 min - 栈顶元素 -1 就可以得到    
| -1|   min = 2
| 5 |     
|_3_|    
stack  

入栈 6,存入  6 - 2 = 4
| 4 |   min = 2
| -1| 
| 5 |     
|_3_|    
stack  

出栈,返回的值就是栈顶元素 4 加上 min,就是 6
|   |   min = 2
| -1| 
| 5 |     
|_3_|    
stack  

出栈,此时栈顶元素是负数,说明之前对 min 值进行了更新。
入栈元素 - min = 栈顶元素,入栈元素其实就是当前的 min 值 2
所以更新前的 min 就等于入栈元素 2 - 栈顶元素(-1) = 3
|   | min = 3
| 5 |     
|_3_|    
stack     

一个栈存储元素差值

public class MinStack {
  long min;
  Stack<Long> stack;

  public MinStack(){
        stack=new Stack<>();
  }

  public void push(int x) {
        if (stack.isEmpty()) {
              min = x;
              stack.push(x - min);
        } else {
              stack.push(x - min);
              if (x < min){
                    min = x; // 更新最小值
              }
        }
  }

  public void pop() {
        if(stack.isEmpty())
              return;

        long pop = stack.pop();
        //弹出的是负值,要更新 min
        if (pop < 0) {
            min = min - pop;
        }
  }

  public int top() {
        long top = stack.peek();
        //负数的话,出栈的值保存在 min 中
        if (top < 0) {
              return (int) (min);
        //出栈元素加上最小值即可
        } else {
              return (int) (top + min);
        }
  }

  public int getMin() {
        return (int) min;
  }

}

3. 用队列实现栈(leetcode-225)

使用队列实现栈的下列操作: 
1. push(x) -- 元素 x 入栈 
2. pop() -- 移除栈顶元素 
3. top() -- 获取栈顶元素 
4. empty() -- 返回栈是否为空 

注意: 
你只能使用队列的基本操作, 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。 
你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列, 只要是标准的队列操作即可。 
你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。 

Related Topics 栈 设计

用队列实现栈

用 queue 去保存数据

  1. push 操作正常的加到队列;

  2. pop 操作,因为队列是先进先出,栈是先进后出,所以此时应该将队列最后一个元素出队列。只需要将队列中除去最后一个元素,其他元素全部出队列,剩下的最后一个就是要弹出的。然后把之前出了队列的元素再保存起来即可。

用队列实现栈

import java.util.LinkedList;
import java.util.Queue;

//leetcode submit region begin(Prohibit modification and deletion)
class MyStack {

Queue<Integer> queue;
/** Initialize your data structure here. */
public MyStack() {
    queue = new LinkedList<>();
}

/** Push element x onto stack. */
public void push(int x) {
    //add()和remove()方法在失败的时候会抛出异常(不推荐)
    queue.offer(x);
}

/** Removes the element on top of the stack and returns that element. */
public int pop() {
    int qSize = queue.size();
    while(qSize > 1){
        queue.offer(queue.poll());
        qSize--;
    }

    return queue.poll();
}

/** Get the top element. */
public int top() {
    int qSize = queue.size();
    while(qSize > 1){
        queue.offer(queue.poll());
        qSize--;
    }
    int res = queue.poll();
    queue.offer(res);
    return res;
}

/** Returns whether the stack is empty. */
public boolean empty() {
    return queue.isEmpty();
}

}

队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。

Java中,LinkedList类实现了Queue接口,因此可以把LinkedList当成Queue来用。

Queue<Integer> queue = new LinkedList<Integer>();

错误用法

Queue<Integer> q1 = new Queue<Integer>();

会报错:error: Queue is abstract; cannot be instantiated

4. 用栈实现队列(leetcode-232)

使用栈实现队列的下列操作: 
1. push(x) -- 将一个元素放入队列的尾部。 
2. pop() -- 从队列首部移除元素。 
3. peek() -- 返回队列首部的元素。 
4. empty() -- 返回队列是否为空。 

示例: 
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);  
queue.peek();  // 返回 1
queue.pop();   // 返回 1
queue.empty(); // 返回 false 

说明: 
你只能使用标准的栈操作 -- 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。 
假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)。 

Related Topics 栈 设计

1)使用两个栈(推荐)

使用两个栈,一个栈输入,一个栈输出。(每个元素只会遍历一次)

  • 当需要查看或者出队的时候,若输出栈为空,就将输入栈元素依次放入到输出栈中;若输出栈不为空,直接出栈即可。输出栈的输出顺序和队列是相符的。

  • 入栈时,直接入输入栈即可。

使用两个栈实现队列

class MyQueue {

Stack<Integer> input;
Stack<Integer> output;

/** Initialize your data structure here. */
public MyQueue() {
    input = new Stack<>();
    output = new Stack<>();
}

/** Push element x to the back of queue. */
public void push(int x) {
    input.push(x);
}

/** Removes the element from in front of queue and returns that element. */
public int pop() {
    if(!output.isEmpty()){
        return output.pop();
    }

    while(!input.isEmpty()){
        output.push(input.pop());
    }

    return output.pop();
}

/** Get the front element. */
public int peek() {
    if(!output.isEmpty()){
        return output.peek();
    }

    while(!input.isEmpty()){
        output.push(input.pop());
    }

    return output.peek();
}

/** Returns whether the queue is empty. */
public boolean empty() {
    return input.isEmpty() && output.isEmpty();
}

}

2)使用临时栈

通过一个临时栈,每次 pop 的时候将原来的元素都保存到临时栈中,只剩下最后一个元素,这个元素是第一个加入栈中的,对于队列就是第一个应该弹出的。然后再把原来的元素还原到栈中即可。
(每个元素会遍历两遍)

使用临时栈

class MyQueue {
Stack<Integer> s1;

/** Initialize your data structure here. */
public MyQueue() {
    s1 = new Stack<>();
}

/** Push element x to the back of queue. */
public void push(int x) {
    s1.push(x);
}

/** Removes the element from in front of queue and returns that element. */
public int pop() {
    Stack<Integer> s2 = new Stack<>();
    while(s1.size()>1){
        s2.push(s1.pop());
    }
    int res = s1.pop();
    while(!s2.isEmpty()){
        s1.push(s2.pop());
    }
    return res;
}

/** Get the front element. */
public int peek() {
    Stack<Integer> s2 = new Stack<>();
    while(s1.size()>1){
        s2.push(s1.pop());
    }
    int res = s1.pop();
    s1.push(res);
    while(!s2.isEmpty()){
        s1.push(s2.pop());
    }
    return res;
}

/** Returns whether the queue is empty. */
public boolean empty() {
    return s1.isEmpty();
}

}

5. 下一个更大元素 I(leetcode-496)

给定两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。找到 nums1 中每个元素在 nums2 中的下一个比其大的值。 
nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。 

示例 1: 
      输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
      输出: [-1,3,-1]

解释:
- 对于num1中的数字4,你无法在第二个数组中找到下一个更大的数字,因此输出 -1。
- 对于num1中的数字1,第二个数组中数字1右边的下一个较大数字是 3。
- 对于num1中的数字2,第二个数组中没有下一个更大的数字,因此输出 -1。 

示例 2: 
      输入: nums1 = [2,4], nums2 = [1,2,3,4].
      输出: [3,-1]

解释:
- 对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。
- 对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出 -1 。

提示: 
- nums1和nums2中所有元素是唯一的。 
- nums1和nums2 的数组大小都不超过1000。 

Related Topics 栈

单调栈+HashMap

思路:可以忽略数组 nums1,先对将 nums2 中的每一个元素,求出其下一个更大的元素。随后对于将这些答案放入哈希映射(HashMap)中,再遍历数组 nums1,并直接找出答案。对于 nums2,可以使用单调栈来解决这个问题。

单调栈+HashMap

class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
    int[] res = new int[nums1.length];
    Stack<Integer> s = new Stack<>();
    HashMap<Integer, Integer> map = new HashMap<>();

    for(int i=0; i<nums2.length; i++){

        while(!s.isEmpty() && s.peek()<nums2[i]){
            map.put(s.pop(), nums2[i]);
        }
        s.push(nums2[i]);
    }

    while(!s.isEmpty()){
        map.put(s.pop(), -1);
    }

    for(int i=0; i<nums1.length; i++){
        res[i] = map.get(nums1[i]);
    }

    return res;

}

}

时间复杂度:O(M+N),其中 M 和 N 分别是数组 nums1 和 nums2 的长度。

空间复杂度:O(N)。我们在遍历 nums2 时,需要使用栈,以及哈希映射用来临时存储答案。

6. 棒球比赛(leetcode-682)

你现在是棒球比赛记录员。 
给定一个字符串列表,每个字符串可以是以下四种类型之一: 
1.整数(一轮的得分):直接表示您在本轮中获得的积分数。 
2. "+"(一轮的得分):表示本轮获得的得分是前两轮有效回合得分的总和。 
3. "D"(一轮的得分):表示本轮获得的得分是前一轮有效回合得分的两倍。 
4. "C"(一个操作,这不是一个回合的分数):表示您获得的最后一个有效回合的分数是无效的,应该被移除。 
每一轮的操作都是永久性的,可能会对前一轮和后一轮产生影响。 你需要返回你在所有回合中得分的总和。 

示例 1: 
输入: ["5","2","C","D","+"]      输出: 30
解释: 
第1轮:你可以得到5分。总和是:5。
第2轮:你可以得到2分。总和是:7。
操作1:第2轮的数据无效。总和是:5。
第3轮:你可以得到10分(第2轮的数据已被删除)。总数是:15。
第4轮:你可以得到5 + 10 = 15分。总数是:30。

示例 2: 
输入: ["5","-2","4","C","D","9","+","+"]      输出: 27
解释: 
第1轮:你可以得到5分。总和是:5。
第2轮:你可以得到-2分。总数是:3。
第3轮:你可以得到4分。总和是:7。
操作1:第3轮的数据无效。总数是:3。
第4轮:你可以得到-4分(第三轮的数据已被删除)。总和是:-1。
第5轮:你可以得到9分。总数是:8。
第6轮:你可以得到-4 + 9 = 5分。总数是13。
第7轮:你可以得到9 + 5 = 14分。总数是27。

注意: 
输入列表的大小将介于1和1000之间。 列表中的每个整数都将介于-30000和30000之间。 

Related Topics 栈

在处理数据时保持栈上每个有效回合的值。栈是理想的,因为只处理涉及最后或倒数第二轮的操作。

栈实现棒球比赛

class Solution {
public int calPoints(String[] ops) {
    Stack<Integer> stack = new Stack<>();
    int res = 0;

    for(String s : ops){
        if("C".equals(s)){
            stack.pop();
        }else if("D".equals(s)){
            stack.push(stack.peek()*2);
        }else if("+".equals(s)){
            int t = stack.pop();
            int newT = stack.peek() + t;
            stack.push(t);
            stack.push(newT);
        }else{
            stack.push(Integer.valueOf(s));
        }
    }

    while(!stack.isEmpty()){
        res += stack.pop();
    }

    return res;
}

}

7. 比较含退格的字符串(leetcode-844)

给定 S 和 T 两个字符串,当它们分别被输入到空白的文本编辑器后,判断二者是否相等,并返回结果。 # 代表退格字符。 
注意:如果对空文本输入退格字符,文本继续为空。 

示例 1: 
输入:S = "ab#c", T = "ad#c"            输出:true
解释:S 和 T 都会变成 “ac”。

示例 2: 
输入:S = "ab##", T = "c#d#"            输出:true
解释:S 和 T 都会变成 “”。

示例 3: 输入:S = "a##c", T = "#a#c"            输出:true
解释:S 和 T 都会变成 “c”。

示例 4: 
输入:S = "a#c", T = "b"            输出:false
解释:S 会变成 “c”,但 T 仍然是 “b”。 

提示: 
1 <= S.length <= 200 
1 <= T.length <= 200 
S 和 T 只含有小写字母以及字符 '#'。 

进阶: 
你可以用 O(N) 的时间复杂度和 O(1) 的空间复杂度解决该问题吗? 

Related Topics 栈 双指针

1)双指针(推荐)

🍓🍓🍓

一个字符是否属于最终字符串的一部分,取决于它后面有多少个退格符。

如果反向遍历字符串,就可以先知道有多少个退格符,然后知道退格符左边有多少个字符会被删除,对应的也就知道哪些字符会保留在最终的字符串中。

双指针比较字符串

class Solution {
public boolean backspaceCompare(String S, String T) {
    int i = S.length() - 1, j = T.length() - 1;
    int skipS = 0, skipT = 0;

    while (i >= 0 || j >= 0) { // While there may be chars in build(S) or build (T)
        while (i >= 0) { // Find position of next possible char in build(S)
            if (S.charAt(i) == '#') {skipS++; i--;}
            else if (skipS > 0) {skipS--; i--;}
            else break;
        }
        while (j >= 0) { // Find position of next possible char in build(T)
            if (T.charAt(j) == '#') {skipT++; j--;}
            else if (skipT > 0) {skipT--; j--;}
            else break;
        }
        // If two actual characters are different
        if (i >= 0 && j >= 0 && S.charAt(i) != T.charAt(j))
            return false;
        // If expecting to compare char vs nothing
        if ((i >= 0) != (j >= 0))
            return false;
        i--; j--;
    }
    return true;
}

}

时间复杂度:O(M + N),其中 M, N 是字符串 S 和 T 的长度。

空间复杂度:O(1)。

2)重构字符串

构造函数(使用栈)去除退格符和被删除字符后的字符串,然后比较它们是否相等。

栈比较字符串

class Solution {
public boolean backspaceCompare(String S, String T) {
    return build(S).equals(build(T));
}

public String build(String S) {
    Stack<Character> ans = new Stack();
    for (char c: S.toCharArray()) {
        if (c != '#')
            ans.push(c);
        else if (!ans.empty())
            ans.pop();
    }
    return String.valueOf(ans);  //🍓(从栈底到栈顶)
}

}

时间复杂度:O(M + N),其中 M, N 是字符串 S 和 T 的长度。

空间复杂度:O(M + N)

8. 删除最外层的括号(leetcode-1021)

有效括号字符串为空 ("")、"(" + A + ")" 或 A + B,其中 A 和 B 都是有效的括号字符串,+ 代表字符串的连接。例如,"","()","(())()" 和 "(()(()))" 都是有效的括号字符串。 
如果有效字符串 S 非空,且不存在将其拆分为 S = A+B 的方法,我们称其为原语(primitive),其中 A 和 B 都是非空有效括号字符串。 
给出一个非空有效字符串 S,考虑将其进行原语化分解,使得:S = P_1 + P_2 + ... + P_k,其中 P_i 是有效括号字符串原语。 
对 S 进行原语化分解,删除分解中每个原语字符串的最外层括号,返回 S 。 

示例 1: 
输入:"(()())(())"            输出:"()()()"
解释:
输入字符串为 "(()())(())",原语化分解得到 "(()())" + "(())",
删除每个部分中的最外层括号后得到 "()()" + "()" = "()()()"。 

示例 2: 
输入:"(()())(())(()(()))"            输出:"()()()()(())"
解释:
输入字符串为 "(()())(())(()(()))",原语化分解得到 "(()())" + "(())" + "(()(()))",
删除每个部分中的最外层括号后得到 "()()" + "()" + "()(())" = "()()()()(())"。

示例 3: 
输入:"()()"            输出:""
解释:
输入字符串为 "()()",原语化分解得到 "()" + "()",
删除每个部分中的最外层括号后得到 "" + "" = ""。

提示: 
S.length <= 10000 
S[i] 为 "(" 或 ")" 
S 是一个有效括号字符串 

Related Topics 栈

1)借用一个int值记录左右括号数量(推荐)

使用一个值记录左右括号的数量。

展示代码

class Solution {
public String removeOuterParentheses(String S) {
    StringBuilder sb = new StringBuilder();
    int level = 0;
    for(char c: S.toCharArray()){
        //下述三条语句顺序不可改变!
        if(c == ')') level--;      //(1)
        if(level>=1) sb.append(c); //(2)
        if(c == '(') level++;      //(3)
    }
    return sb.toString();
}

}

2)使用栈

遍历字符串,遇到左括号就入栈,遇到右括号就出栈,每次栈空的时候,都说明找到了一个原语,记录下每个原语的起始位置和结束位置,取原字符串在原语的起始位置+1到原语的结束位置的子串便得到原语删除了最外层括号的字符串,拼接,即可解出答案。

栈方法

class Solution {
public String removeOuterParentheses(String S) {
    StringBuilder ans = new StringBuilder();
    Stack<Character> stack = new Stack<>();

    int start = 0;// 初始化原语的起始位置
    int end = 0;// 初始化原语的结束位置
    boolean flag = false;// 标志每个原语

    for (int i = 0;i < S.length();i++) {
        char ch = S.charAt(i);

        if (ch == '(') {// 遇到左括号,入栈
            stack.push(ch);
            if (!flag) {// 遇到的第一个左括号,是原语的开始位置,记录下原语开始位置
                start = i;
                flag = true;
            }
        }

        if (ch == ')') {// 遇到右括号,出栈
            stack.pop();
            if (stack.isEmpty()) {// 当栈空的时候,找到了一个完整的原语
                end = i;// 记录下结束位置
                ans.append(S.substring(start + 1,end));// 去掉原语的最外层括号,并追加到答案中
                flag = false;// 置标志为false,往后接着找下一个原语
                start = end;// 往后找,再次初始化原语开始位置
            }
        }
    }

    return ans.toString();
}

}

9. 删除字符串中的所有相邻重复项(leetcode-1047)

给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。在 S 上反复执行重复项删除操作,直到无法继续删除。 
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。 

示例: 
输入:"abbaca"      输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。

提示: 
1 <= S.length <= 20000 
S 仅由小写英文字母组成。 

Related Topics 栈

1)栈(推荐)

可以用栈来维护没有重复项的字母序列:

  • 若当前的字母和栈顶的字母相同,则弹出栈顶的字母;

  • 若当前的字母和栈顶的字母不同,则放入当前的字母。

实现1

class Solution {
public String removeDuplicates(String S) {
    StringBuilder sb = new StringBuilder();
    int sbLength = 0;
    for (char character : S.toCharArray()) {
        if (sbLength != 0 && character == sb.charAt(sbLength - 1))
            sb.deleteCharAt(sbLength-- - 1);
        else {
            sb.append(character);
            sbLength++;
        }
    }
    return sb.toString();
}

}

实现2

class Solution {
public String removeDuplicates(String S) {
    Stack<Character> stack = new Stack<>();
    for(int i=0; i<S.length(); i++){
        if(!stack.isEmpty() && stack.peek().equals(S.charAt(i)) ){
            stack.pop();
        }else {
            stack.push(S.charAt(i));
        }
    }
    StringBuilder str = new StringBuilder();
    for (Character c : stack){ // 栈的for循环,从栈底开始?  用while循环遍历,要反转字符串
        str.append(c);
    }

    return str.toString();
}

}

时间复杂度:O(N),其中 N 是字符串的长度。

空间复杂度:O(N)。

2)替换函数(不推荐)

可以用字符串自带的替换函数,由于字符串仅包含小写字母,因此只有 26 种不同的重复项。

将 aa 到 zz 的 26 种重复项放入集合中;遍历这 26 种重复项,并用字符串的替换函数把重复项替换成空串。

注意,在进行过一次替换之后,可能会出现新的重复项。例如对于字符串 abbaca,如果替换了重复项 bb,字符串会变为 aaca,出现了新的重复项 aa。因此,上面的过程需要背重复若干次,直到字符串在一整轮替换过程后保持不变(即长度不变)为止。

展示代码

class Solution {
public String removeDuplicates(String S) {
  // generate 26 possible duplicates
    HashSet<String> duplicates = new HashSet();
    StringBuilder sb = new StringBuilder();
    for (char i = 'a'; i <= 'z'; ++i) {
        sb.setLength(0);
        sb.append(i); sb.append(i);
        duplicates.add(sb.toString());
    }

    int prevLength = -1;
    while (prevLength != S.length()) {
        prevLength = S.length();
        for (String d : duplicates) S = S.replace(d, "");
    }

  return S;
}

}

10. 用栈操作构建数组(leetcode-1441)

给你一个目标数组 target 和一个整数 n。每次迭代,需要从 list = {1,2,3..., n} 中依序读取一个数字。 

请使用下述操作来构建目标数组 target : 
Push:从 list 中读取一个新元素, 并将其推入数组中。 
Pop:删除数组中的最后一个元素。 
如果目标数组构建完成,就停止读取更多元素。 

题目数据保证目标数组严格递增,并且只包含 1 到 n 之间的数字。 

请返回构建目标数组所用的操作序列。 

题目数据保证答案是唯一的。 

示例 1: 
输入:target = [1,3], n = 3
输出:["Push","Push","Pop","Push"]
解释: 
读取 1 并自动推入数组 -> [1]
读取 2 并自动推入数组,然后删除它 -> [1]
读取 3 并自动推入数组 -> [1,3]

示例 2: 
输入:target = [1,2,3], n = 3
输出:["Push","Push","Push"]

示例 3: 
输入:target = [1,2], n = 4
输出:["Push","Push"]
解释:只需要读取前 2 个数字就可以停止。

示例 4: 
输入:target = [2,3,4], n = 4
输出:["Push","Pop","Push","Push","Push"]

提示: 
1 <= target.length <= 100 
1 <= target[i] <= 100 
1 <= n <= 100 
target 是严格递增的 

Related Topics 栈

遍历[1~target的最大值]

遍历[1~target的最大值],并与target内的元素比较,若target内存在该元素,则只有Push操作,若不存在,则Push操作后又执行Pop操作。

展示代码

class Solution {
public List<String> buildArray(int[] target, int n) {
    List<String> str = new ArrayList<>();
    int len = target.length;
    int maxv = target[len-1];
    int j=0;
    for(int i=1; i<=maxv && j<len; i++){
        if(target[j] == i){
            str.add("Push");
            j++;
        }else{
            str.add("Push");
            str.add("Pop");
        }
    }
    return str;
}

}

posted @ 2020-06-29 14:15  喵喵巫  阅读(249)  评论(0编辑  收藏  举报