栈:删除最外层的括号 (Leetcode 1021 / 155 / 1172 / 剑指31 / 面试 03.03)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

/**
* 解法一:暴力解法思路分析
* 1.定义容器存储原语子串
* new ArrayList<String>();
* 2.定义左括号、右括号计数器:
* int left = 0, right = 0;
* 3.遍历字符串,读取到括号时对应计数器自增
* 4.检查是否到达原语结尾,截取原语子串并添加到容器中
* 5.遍历容器,删除最外层括号后合并成新串
*
* 边界问题:
* 遍历字符串,注意索引越界:i < S.length()
* 截取原语字符串时,注意起止索引:[start, end)
* 细节问题:
* 需要记录上一次截取原语子串之后的位置
* 删除原语子串的最外层括号,其实就是重新截取
* @param S
* @return
*/
public String removeOuterParentheses(String S) {
  int len = S.length();
  // 1.定义容器存储原语子串
  List<String> list = new ArrayList<>();
  // 2.定义左括号、右括号计数器
  int left = 0, right = 0, lastOpr = 0;
  // 3.遍历字符串,读取到括号时对应计数器自增
  for (int i = 0; i < len; i++) {
    char c = S.charAt(i);
    if (c == '(') {
      left++;
    } else if (c == ')') {
      right++;
    }
    // 4.检查是否到达某个原语结尾,截取原语子串添加到容器
    if (left == right) {
      list.add(S.substring(lastOpr, i + 1));
      lastOpr = i + 1;
    }
  }
  // 5.遍历容器中的原语子串,删除最外层后合并成新串
  StringBuilder sb = new StringBuilder();
  for (String s : list) {
    sb.append(s.substring(1, s.length() - 1));
  }
  return sb.toString();
}

 

 

 

 

 

 

/**
* 解法二:优化解法思路分析
* 1.定义容器存储删除外层括号后的原语子串
* new StringBuilder();
* 2.定义左括号、右括号计数器:
* int left = 0, right = 0;
* 3.遍历字符串,读取到括号时对应计数器自增
* 4.检查是否到达原语结尾,截取不包含最外层的原语子串并拼接到容器中
*
* @param S
* @return
*/
public String removeOuterParentheses(String S) {
  int len = S.length();
  // 1.定义容器存储删除外层括号后的原语子串
  StringBuilder sb = new StringBuilder();
  // 2.定义左括号、右括号计数器
  int left = 0, right = 0, lastOpr = 0;
  // 3.遍历字符串,读取到括号时对应计数器自增
  for (int i = 0; i < len; i++) {
    char c = S.charAt(i);
    if (c == '(') {
      left++;
    } else if (c == ')') {
     right++;
    }
    // 4.检查是否到达某个原语结尾,截取不包含最外层的原语子串添加到容器
    if (left == right) {
      sb.append(S.substring(++lastOpr, i));
      lastOpr = i + 1;
    }
  }
  return sb.toString();
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

/**
* 栈:后进先出
* @param <E>
*/
class Stack<E> {
  Object[] elements = new Object[10000];
  int index = -1; // 栈顶索引
  int size = 0; // 栈中元素个数
  public Stack() {
  }
  /**
  * 往栈顶插入元素
  * @param c
  */
  public void push(E c) {
    elements[++index] = c;
    size++;
  }
  /**
  * 从栈顶获取数据,不移出
  栈解法优化,使用栈的思想,直接用数组取代栈:
  * @return
  */
  public E peek() {
    if (index < 0) {
      return null;
    }
    return (E)elements[index];
  }
  /**
  * 从栈顶移出元素
  * @return
  */
  public E pop() {
    E e = peek();
    if (e != null) {
      elements[index] = null; // 移出动作
      index--; // 栈顶下移
      size--;
    }
  return e;
}
  public boolean isEmpty() {
    return size == 0;
  }
}
/**
* 最优解:栈解法
* 1.使用数组模拟一个栈,临时存储左括号字符
* push(Character) ; pop(); isEmpty()
* 2.遍历字符串,根据情况进行入栈/出栈操作
栈实现代码:
* 读取到左括号,左括号入栈
* 读取到右括号,左括号出栈
* 3.判断栈是否为空,若为空,找到了一个完整的原语
* 4.截取不含最外层括号的原语子串并进行拼接
*
* @param S
* @return
*/
public String removeOuterParentheses(String S) {
  StringBuilder result = new StringBuilder();
  // 1.使用数组模拟一个栈,临时存储字符,替代计数器
  Stack stack = new Stack();
  int lastOpr = 0;
  // 2.遍历字符串,根据情况进行入栈 / 出栈操作
  for (int i = 0; i < S.length(); i++) {
    char ch = S.charAt(i);
    if (ch == '(') { // 遇到左括号,左括号入栈
      stack.push(ch);
    } else if (ch == ')') { // 遇到右括号,左括号出栈
      stack.pop(); // 栈顶的左括号出栈
    }
    // 3.判断栈是否为空,若为空,找到了一个完整的原语
    if (stack.isEmpty()) {
      // 4.截取不含最外层括号的原语子串并进行拼接
      result.append(S.substring(lastOpr + 1, i));// 去掉原语的最外层括号并追加到结果
      lastOpr = i + 1;// 往后找,再次初始化原语开始位置
    }
  }
  return result.toString();
}

 

 

 

 

 

 

/**
* 最优解:代码优化:
* 1.直接用数组取代栈
* 创建数组、栈顶索引,使用数组操作入栈和出栈
* 2.将原字符串转为数组进行遍历
* char[] s = S.toCharArray();
* 3.去掉截取子串的操作,将原语字符直接拼接
* 读取到左括号:此前有数据,当前必属原语
* 读取到右括号:匹配后不为0,当前必属原语
* @param S
* @return
*/
public String removeOuterParentheses(String S) {
  StringBuilder result = new StringBuilder();
  // 1.直接用数组取代栈
  int index = -1; // 栈顶索引
  int len = S.length();
  char[] stack = new char[len];
  char[] s = S.toCharArray();
  // 2.遍历字符串,根据情况进行入栈 / 出栈操作
  for (int i = 0; i < len; i++) {
    char ch = s[i];
    if (ch == '(') { // 遇到左括号,左括号入栈
      // 3.去掉截取子串的操作,将原语字符直接拼接
      if (index > -1) { // 此前有数据,当前必属原语
        result.append(ch);
      }
      stack[++index] = ch;
    } else /*if (ch == ')')*/ { // 遇到右括号,左括号出栈
      stack[index--] = '\u0000'; // 栈顶的左括号出栈
      if (index > -1) {
        result.append(ch);
      }
    }
  }
  return result.toString();
}

最最优解

 

 

 

 

 

 

 

 

 

 

 

 

Leetcode 155 -  最小栈

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

  • push(x) —— 将元素 x 推入栈中。
  • pop() —— 删除栈顶的元素。
  • top() —— 获取栈顶元素。
  • 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 操作总是在 非空栈 上调用。
//思路:使用两个队列实现最小栈
class MinStack { 
    Deque<Integer> xStack; //主栈,存放所有入栈数据
    Deque<Integer> minStack; //每次入栈当前最小数据
    public MinStack() {
        xStack = new LinkedList<Integer>();
        minStack = new LinkedList<Integer>();
        minStack.push(Integer.MAX_VALUE);//默认最小数据为整形最大值
    }

    public void push(int x) {
        xStack.push(x);//入栈
        minStack.push( minStack.peek()< x? minStack.peek():x); //将当前最小值入栈,为保证数量一致,所以会重复存放最小值
    }

    public void pop() {
        xStack.pop();//出栈
        minStack.pop();//当前最小值也出栈
    }

    public int top() {
        return xStack.peek();//获取栈顶元素
    }

    public int getMin() {
        return minStack.peek();//通过最小栈获取当前最小元素
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

 

剑指Offer 31 -  栈的压入、弹出序列

 

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。
假设压入栈的所有数字均不相等。
例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,
但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。

示例 1:

  输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
  输出:true
  解释:我们可以按以下顺序执行:
  push(1), push(2), push(3), push(4), pop() -> 4,
  push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:

  输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
  输出:false
  解释:1 不能在 2 之前弹出。

提示:

  0 <= pushed.length == popped.length <= 1000
  0 <= pushed[i], popped[i] < 1000
  pushed 是 popped 的排列。

//思路:每次比较栈顶元素与弹出序列是否一致,一致则出栈
class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
       Stack<Integer> stack = new Stack<>();
        int i = 0;
        for(int num : pushed) {
            stack.push(num); // num 入栈
            while(!stack.isEmpty() && stack.peek() == popped[i]) { // 循环判断与出栈
                stack.pop();
                i++;
            }
        }
        return stack.isEmpty();
    }
}

 

堆盘子 - 面试 03.03

堆盘子。设想有一堆盘子,堆太高可能会倒下来。

因此,在现实生活中,盘子堆到一定高度时,我们就会另外堆一堆盘子。请实现数据结构SetOfStacks,模拟这种行为。

SetOfStacks应该由多个栈组成,并且在前一个栈填满时新建一个栈。

此外,SetOfStacks.push()和SetOfStacks.pop()应该与普通栈的操作方法相同(也就是说,pop()返回的值,应该跟只有一个栈时的情况一样)。

进阶:实现一个popAt(int index)方法,根据指定的子栈,执行pop操作。

当某个栈为空时,应当删除该栈。当栈中没有元素或不存在该栈时,pop,popAt 应返回 -1.

 

示例1:

输入:
  ["StackOfPlates", "push", "push", "popAt", "pop", "pop"]
  [[1], [1], [2], [1], [], []]
输出:
  [null, null, null, 2, 1, -1]


示例2:

输入:
  ["StackOfPlates", "push", "push", "push", "popAt", "popAt", "popAt"]
  [[2], [1], [2], [3], [0], [0], [0]]
输出:
  [null, null, null, null, 2, 1, 3]

class StackOfPlates {
    private List<Stack<Integer>> stackList;
    private int cap;
    public StackOfPlates(int cap) {
        stackList = new ArrayList<>(); //使用ArrayList来存储所有的栈
        this.cap = cap;
    }

    public void push(int val) {
        if (cap <= 0) {//处理边界
            return;
        }

        if (stackList.isEmpty() || stackList.get(stackList.size() - 1).size() == cap) {//处理最后一个栈满的情况
            Stack<Integer> stack = new Stack<>();
            stack.push(val);
            stackList.add(stack);
            return;
        }

        stackList.get(stackList.size() - 1).push(val);
    }

    public int pop() {
        return popAt(stackList.size() - 1);
    }

    public int popAt(int index) {
        if (index < 0 || index >= stackList.size()) {
            return -1;
        }

        Stack<Integer> stack = stackList.get(index);
        if (stack.isEmpty()) {
            return -1;
        }

        int res = stack.pop();

        if (stack.isEmpty()) {
            stackList.remove(index);
        }

        return res;
    }
}

 

餐盘栈 - Leetcode 1172

我们把无限数量 ∞ 的栈排成一行,按从左到右的次序从 0 开始编号。每个栈的的最大容量 capacity 都相同。

实现一个叫「餐盘」的类 DinnerPlates:

DinnerPlates(int capacity) - 给出栈的最大容量 capacity。
void push(int val) - 将给出的正整数 val 推入 从左往右第一个 没有满的栈。
int pop() - 返回 从右往左第一个 非空栈顶部的值,并将其从栈中删除;如果所有的栈都是空的,请返回 -1。
int popAtStack(int index) - 返回编号 index 的栈顶部的值,并将其从栈中删除;如果编号 index 的栈是空的,请返回 -1。

示例:

输入:
  ["DinnerPlates","push","push","push","push","push","popAtStack","push","push","popAtStack","popAtStack","pop","pop","pop","pop","pop"]
  [[2],[1],[2],[3],[4],[5],[0],[20],[21],[0],[2],[],[],[],[],[]]
输出:
  [null,null,null,null,null,null,2,null,null,20,21,5,4,3,1,-1]

解释:
DinnerPlates D = DinnerPlates(2); // 初始化,栈最大容量 capacity = 2
D.push(1);
D.push(2);
D.push(3);
D.push(4);
D.push(5); // 栈的现状为:
2 4
1 3 5
﹈ ﹈ ﹈
D.popAtStack(0); // 返回 2。栈的现状为:
4
1 3 5
﹈ ﹈ ﹈
D.push(20); // 栈的现状为:
20 4
1 3 5
﹈ ﹈ ﹈
D.push(21); // 栈的现状为:
20 4 21
1 3 5
﹈ ﹈ ﹈
D.popAtStack(0); // 返回 20。栈的现状为:
4 21
1 3 5
﹈ ﹈ ﹈
D.popAtStack(2); // 返回 21。栈的现状为:
4
1 3 5
﹈ ﹈ ﹈
D.pop() // 返回 5。栈的现状为:
4
1 3
﹈ ﹈
D.pop() // 返回 4。栈的现状为:
1 3
﹈ ﹈
D.pop() // 返回 3。栈的现状为:
1

D.pop() // 返回 1。现在没有栈。
D.pop() // 返回 -1。仍然没有栈。

 

//采用双指针思想,后面后讲到的
class DinnerPlates {
    private int capacity = 0;
    private int left = 0;//从左边开始第一个不满的,严格控制
    private int right = 0;//从右边开始第一个非空的,宽松
    private List<Stack<Integer>> list = new ArrayList<>();

    public DinnerPlates(int capacity) {
        this.capacity = capacity;
        Stack<Integer> stack2 = new Stack();
        list.add(stack2);
    }

    /*
        直接在left所在位置进行入栈,push后需要遍历查找下一个未满栈
        细节问题:
            1. 需要对list进行扩容操作
            2. 需要更新right位置(right宽松,不保证一定是非空,可以在第一个非空的右边)
     */
    public void push(int val) {
        if (left < 0)
            left = 0;
        Stack<Integer> stack = list.get(left);
        stack.push(val);
        //达到容量,则找后面的第一个未满栈
        while (left < list.size() && list.get(left).size() == capacity) {
            left++;
        }

        if (left >= list.size()) { //需要增加栈的数量
            Stack<Integer> stack2 = new Stack();
            list.add(stack2);
        }

        if (right < left) { //新的栈在right以右,更新right的位置
            right = left;
        }
    }

    /*
        以right为起点,往左遍历查找第一个非空栈
        细节问题:
            移除数据后,需要判断所在位置是不是比left更小,如果满足则需要更新left
            移除数据后,需要判断所在位置是不是空栈,空栈则将right左移一个(right宽松,不保证一定是非空,可以在第一个非空的右边)
     */
    public int pop() {
        for (int i = right; i >= 0; i--) {//从right开始找第一个非空栈,乐观情况right就是想要的位置
            Stack<Integer> stack = list.get(i);
            if (!stack.isEmpty()) {
                right = i;
                Integer val = stack.pop();
                if (left > right) { //如果移除的位置在left左边,需要将left修复到right位置
                    left = right;
                }
                if (stack.isEmpty()) {//pop之后,right需要再向左移动一个(这里不保证right的新位置一定为非空栈)
                    right--;
                }
                return val;
            }
        }
        return -1;
    }

    /*
      移除数据后,需要判断所在位置是不是比left更小,如果满足则需要更新left
     */
    public int popAtStack(int index) {
        if (index > list.size() - 1) //越界检查
            return -1;
        Stack<Integer> stack = list.get(index);
        if (!stack.isEmpty()) { //栈不为空
            if (left > index) { //在左边界之前,更新左边界执行更靠左的未满栈
                left = index;
            }
            return stack.pop();
        } else {
            return -1;
        }
    }
}
/**
 * Your DinnerPlates object will be instantiated and called as such:
 * DinnerPlates obj = new DinnerPlates(capacity);
 * obj.push(val);
 * int param_2 = obj.pop();
 * int param_3 = obj.popAtStack(index);
 */

 

posted @ 2021-07-23 15:56  Jasper2003  阅读(52)  评论(0编辑  收藏  举报