参考自 代码随想录
总结
学会逆向思维
比如225. 用队列实现栈:
入栈操作时,首先获得入栈前的元素个数 n,然后将元素入队到队列,再将队列中的前 n 个元素(即除了新入栈的元素之外的全部元素)依次出队并入队到队列,此时队列的前端的元素即为新入栈的元素,且队列的前端和后端分别对应栈顶和栈底。
比如20. 有效的括号:
在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了。可以大幅度简化代码
简单
232. 用栈实现队列
class MyQueue {
Stack<Integer> s1,s2;
public MyQueue() {
s1=new Stack<>();// 负责进栈
s2=new Stack<>();// 负责出栈
}
public void push(int x) {
s1.push(x); // 进栈
}
// 从栈的栈顶移除并返回元素
public int pop() {
while(s2.isEmpty()){
while(!s1.isEmpty()){
// 如果s2为空则把s1的数据全部导入s2
s2.push(s1.pop());
}
}
return s2.pop();// 出栈
}
// 返回栈顶元素
public int peek() {
while(s2.isEmpty()){
while(!s1.isEmpty()){
s2.push(s1.pop());
}
}
return s2.peek(); // 返回栈顶元素
}
public boolean empty() {
// 两个栈都空说明队列为空
return s1.isEmpty()&&s2.isEmpty();
}
}
225. 用队列实现栈
方法一:使用两个队列实现一个后入先出(LIFO)的栈
- 入 栈 时 存 入 q u e 1 入栈时存入que1 入栈时存入que1
-
出
栈
时
把
q
u
e
1
中
的
元
素
(
除
最
后
一
个
入
队
的
元
素
)
导
入
q
u
e
2
出栈时把que1中的元素(除最后一个入队的元素)导入que2
出栈时把que1中的元素(除最后一个入队的元素)导入que2
获 取 q u e 1 的 最 后 一 个 元 素 并 出 队 , 再 把 q u e 2 导 入 q u e 1 获取que1的最后一个元素并出队,再把que2导入que1 获取que1的最后一个元素并出队,再把que2导入que1 -
获
取
栈
顶
时
把
q
u
e
1
中
的
元
素
(
除
最
后
一
个
入
队
的
元
素
)
导
入
q
u
e
2
获取栈顶时把que1中的元素(除最后一个入队的元素)导入que2
获取栈顶时把que1中的元素(除最后一个入队的元素)导入que2
获取que1的最后一个元素并出队,再把这个元素导入que2,然后把que2导入que1
import java.util.*;
class MyStack {
Queue<Integer> que1;
Queue<Integer> que2;
public MyStack() {
que1 = new LinkedList<>();
que2 = new LinkedList<>(); // 用来备份que1中的元素
}
public void push(int x) {
que1.offer(x); // 入队 que1.add(x);也可以
}
public int pop() {
// 返回que1的最后一个入队的元素
int size = que1.size();
while (size > 1) {
// 把que1中的元素(除最后一个入队的元素)导入que2
que2.offer(que1.poll());
size--;
}
int result = que1.poll();// 要返回的值
// 再把que2导入que1
while (!que2.isEmpty()) {
que1.offer(que2.poll());
}
return result;
}
public int top() {
// 返回que1队尾元素
int size = que1.size();
while (size > 1) {
// 把que1中的元素(除最后一个入队的元素)导入que2
que2.offer(que1.poll());
size--;
}
int result = que1.poll();// 要返回的值
que2.offer(result); // 再把que1中最后一个入队的元素也导入que2
// 再把que2导入que1
while (!que2.isEmpty()) {
que1.offer(que2.poll());
}
return result;
}
public boolean empty() {
return que1.isEmpty();
}
public static void main(String[] args) {
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
System.out.println(myStack.top()); // 返回 2
System.out.println(myStack.pop()); // 返回 2
System.out.println(myStack.empty());// 返回 False
}
}
方法二:使用两个队列实现一个后入先出(LIFO)的栈
参考:用队列实现栈 - 用队列实现栈 - 力扣(LeetCode)
class MyStack {
Queue<Integer> queue1;
Queue<Integer> queue2;
/** Initialize your data structure here. */
public MyStack() {
queue1 = new LinkedList<Integer>();
queue2 = new LinkedList<Integer>();
}
/** Push element x onto stack. */
public void push(int x) {
queue2.offer(x);
while (!queue1.isEmpty()) {
queue2.offer(queue1.poll());
}
Queue<Integer> temp = queue1;
queue1 = queue2;
queue2 = temp;
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {
return queue1.poll();
}
/** Get the top element. */
public int top() {
return queue1.peek();
}
/** Returns whether the stack is empty. */
public boolean empty() {
return queue1.isEmpty();
}
}
方法三:使用一个队列实现一个后入先出(LIFO)的栈
和方法一类似
- 入 栈 时 存 入 q u e u e 入栈时存入queue 入栈时存入queue
-
出
栈
时
把
q
u
e
u
e
中
的
元
素
依
次
出
队
(
除
最
后
一
个
入
队
的
元
素
)
并
重
新
添
加
到
q
u
e
u
e
末
尾
出栈时把queue中的元素依次出队(除最后一个入队的元素)并重新添加到queue末尾
出栈时把queue中的元素依次出队(除最后一个入队的元素)并重新添加到queue末尾
再 获 取 q u e u e 的 队 头 元 素 并 出 队 再获取queue的队头元素并出队 再获取queue的队头元素并出队 -
获
取
栈
顶
时
把
q
u
e
u
e
中
的
元
素
依
次
出
队
(
除
最
后
一
个
入
队
的
元
素
)
并
重
新
添
加
到
q
u
e
u
e
末
尾
获取栈顶时把queue中的元素依次出队(除最后一个入队的元素)并重新添加到queue末尾
获取栈顶时把queue中的元素依次出队(除最后一个入队的元素)并重新添加到queue末尾
获取queue的队头并出队,再把这个元素导入queue末尾
class MyStack {
Queue<Integer> queue;
public MyStack() {
queue=new LinkedList<>();
}
public void push(int x) {
queue.offer(x);
}
public int pop() {
// 返回que1的最后一个入队的元素
int size=queue.size();
while(size>1){
// 把queue中的元素(除最后一个入队的元素)导入queue末尾
queue.offer(queue.poll());
size--;
}
int result=queue.poll();// 要返回的值
return result;
}
public int top() {
// 返回queue队尾元素
int size=queue.size();
while(size>1){
// 把queue中的元素(除最后一个入队的元素)导入queue末尾
queue.offer(queue.poll());
size--;
}
int result = queue.poll();// 要返回的值
queue.offer(result); // 再把queue中最后一个入队的元素也导入queue末尾
return result;
}
public boolean empty() {
return queue.isEmpty();
}
}
方法四:使用一个队列实现一个后入先出(LIFO)的栈
参考:用队列实现栈 - 用队列实现栈 - 力扣(LeetCode)
class MyStack {
Queue<Integer> queue;
/** Initialize your data structure here. */
public MyStack() {
queue = new LinkedList<Integer>();
}
/** Push element x onto stack. */
public void push(int x) {
int n = queue.size();
queue.offer(x);
for (int i = 0; i < n; i++) {
queue.offer(queue.poll());
}
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {
return queue.poll();
}
/** Get the top element. */
public int top() {
return queue.peek();
}
/** Returns whether the stack is empty. */
public boolean empty() {
return queue.isEmpty();
}
}
20. 有效的括号
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '(' || c == '[' || c == '{') {
stack.push(c);//入栈
} else {
if (stack.isEmpty())
return false;//栈中无元素来匹配括号
else {
Character p = stack.pop();//出栈来匹配括号
if (p=='(') {
if (c != ')') return false;
} else if (p=='[') {
if (c != ']') return false;
} else if (p=='{') {
if (c != '}') return false;
}
}
}
if (stack.isEmpty())
return true;//栈中为空表示匹配成功
else
return false;
}
}
技巧:在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '(') {
stack.push(')');//入栈右括号
}else if (c == '[') {
stack.push(']');
}else if (c == '{') {
stack.push('}');
}else if(stack.isEmpty() || stack.peek()!=c){
return false;
}else{
stack.pop(); // 出栈
}
}
return stack.isEmpty();
}
}
1047. 删除字符串中的所有相邻重复项
【参考:1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode)】
可以把字符串顺序放到一个栈中,然后如果相同的话 栈就弹出,这样最后栈里剩下的元素都是相邻不相同的元素了。
class Solution {
public String removeDuplicates(String s) {
if (s.length() <= 1)
return s;
Stack<Character> stack = new Stack<>();
stack.push(s.charAt(0));
for (int i = 1; i < s.length(); i++) {
if (!stack.isEmpty() && s.charAt(i) == stack.peek()) {
stack.pop();
} else {
stack.push(s.charAt(i));
}
}
StringBuilder sb = new StringBuilder();
while (!stack.isEmpty()) {
sb.append(stack.pop());
}
return sb.reverse().toString();
}
}
滑动窗口(双指针实现)
class Solution {
public String removeDuplicates(String s) {
if (s.length() <= 1)
return s;
char[] arr = s.toCharArray();
int left = 0, right = 1;
int len = arr.length;
while (right < len) {
if (arr[left] == arr[right]) {
// 前移两位
for (int i = left; i < len - 2; i++) {
arr[i] = arr[i + 2];
}
len -= 2;
if (left >= 1) {
left--;
right--;
}
} else {
left++;
right++;
}
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++) {
sb.append(arr[i]);
}
return sb.toString();
}
}
中等
150. 逆波兰表达式求值
参考:150. 逆波兰表达式求值 - 力扣(LeetCode)
class Solution {
public int evalRPN(String[] tokens) {
Stack<String> stack = new Stack<>();
for (int i = 0; i < tokens.length; i++) {
// 操作符
if (tokens[i].equals("+") || tokens[i].equals("-")
|| tokens[i].equals("*") || tokens[i].equals("/")) {
int num1 = Integer.valueOf(stack.pop());
int num2 = Integer.valueOf(stack.pop());
if (tokens[i].equals("+"))
stack.push(String.valueOf(num2 + num1));
else if (tokens[i].equals("-"))
stack.push(String.valueOf(num2 - num1));
else if (tokens[i].equals("*"))
stack.push(String.valueOf(num2 * num1));
else if (tokens[i].equals("/"))
stack.push(String.valueOf(num2 / num1));
} else {
stack.push(tokens[i]); // 数字
}
}
return Integer.valueOf(stack.peek());
}
}
347. 前 K 个高频元素 ***
参考:347. 前 K 个高频元素 - 力扣(LeetCode)
优先队列 PriorityQueue
从队头取元素,从队尾添加元素,再无其他取元素的方式。
参考:Java优先队列(PriorityQueue)_try to do-优快云博客_java优先队列
PriorityQueue是基于优先堆的一个无界队列,这个优先队列中的元素可以默认自然排序或者通过提供的Comparator(比较器)在队列实例化的时排序。(不指定Comparator时默认为小顶堆)
参考:347. 前 K 个高频元素 - 前 K 个高频元素 - 力扣(LeetCode)
- 借助 哈希表 来建立数字和其出现次数的映射,遍历一遍数组统计元素的频率
- 维护一个元素数目为 k 的最小堆
- 每次都将新的元素与堆顶元素(堆中频率最小的元素)进行比较,如果新的元素的频率比堆顶端的元素大,则弹出堆顶端的元素,将新的元素添加进堆中
- 最终,堆中的 k 个元素即为前 k 个高频元素
leetcode有bug,编译不通过,但本地执行正常
class Solution {
public List<Integer> topKFrequent(int[] nums, int k) {
// 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值
HashMap<Integer, Integer> map = new HashMap<>();
for (int item : nums) {
if (map.containsKey(item)) {
map.put(item, map.get(item) + 1);
} else {
map.put(item, 1);
}
}
// 遍历map,用小顶堆保存频率最大的k个元素
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return map.get(o1) - map.get(o2);
}
});
for (Integer key : map.keySet()) {
if (pq.size() < k) {
pq.add(key);
} else if (map.get(key) > map.get(pq.peek())) {
pq.poll();
pq.add(map.get(key));
}
}
// 取出最小堆中的元素
ArrayList<Integer> list = new ArrayList<>();
while (!pq.isEmpty()) {
list.add(pq.poll());
}
Collections.reverse(list);// 反转
return list;
}
}
1823. 找出游戏的获胜者
【参考:1823. 找出游戏的获胜者 - 力扣(LeetCode)】
双端队列 Deque
本题需要用循环队列实现,但java没有循环队列的api,所以使用双端队列 Deque来模拟
// 把队首前k-1个元素依次出队加入到队尾
头 尾
1 2 3 4 5
1 2 3依次出队并加入队尾
4 5 1 2 3
头 尾
class Solution {
public int findTheWinner(int n, int k) {
Deque<Integer> queue = new ArrayDeque<>();
for(int i=1;i<=n;i++)
queue.offerLast(i);
while(queue.size()>1){
for(int i=1;i<k;i++)
queue.offerLast(queue.pollFirst()); // 把队首前k-1个元素依次出队加入到队尾
queue.pollFirst();
}
return queue.getFirst();
}
}
循环队列
push poll 那里要统一 先进再移 先出再移
class CircleQueue{
int maxSize;
int[] queue;
int front;
int rear;
public CircleQueue(int size){
maxSize = size+1; // 必须预留一个空间当做标识
queue = new int[maxSize];
front = 0; // front指向队头元素的当前位置
rear = 0; // rear指向队尾元素的下一个位置
}
public boolean isEmpty(){
return front == rear;
}
public boolean isFull(){
return (rear +1 )% maxSize == front;
}
public int size(){
return (rear - front + maxSize) % maxSize;
}
public boolean push(int num){
if(isFull())
throw new RuntimeException("队满,无法插入队");
queue[rear]=num; // 先进再移
rear=(rear+1)%maxSize;
return true;
}
public int poll(){
if(isEmpty()) throw new RuntimeException("队空,无法出队");
int temp = queue[front]; // 先出再移
front = (front+1)%maxSize;
return temp;
}
public int peek(){
if(isEmpty()) throw new RuntimeException("队空,无法出队");
return queue[front];
}
}
class Solution {
public int findTheWinner(int n, int k) {
CircleQueue queue=new CircleQueue(n);
for(int i=0;i<n;i++)
queue.push(i+1); // 编号从1开始
while(queue.size()>1){
for(int i=0;i<k-1;i++)
queue.push(queue.poll()); // 把尾部前k-1个元素依次出队加入到头部
queue.poll();
}
return queue.peek();
}
}
困难
239. 滑动窗口最大值 ***
参考:239. 滑动窗口最大值 - 力扣(LeetCode)
参考:单调队列结构解决滑动窗口问题 :: labuladong的算法小抄
「单调队列」队列中的元素全都是单调递增(或递减)的
// 暴力法
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int len=nums.length;
int[] result=new int[len-k+1];
for(int i=0;i<len-k+1;i++){
int max=nums[i];
for(int j=i+1;j<i+k;j++){
max= nums[j]>max ? nums[j] : max;
}
result[i]=max;
}
return result;
}
}
//
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
MyQueue window = new MyQueue();
int len = nums.length;
int[] result = new int[len - k + 1];
int index = -1;
//先将前k的元素放入队列
for (int i = 0; i < k; i++) {
window.push(nums[i]);
}
result[++index] = window.max();
for (int i = k; i < len; i++) {
// 滑动窗口移除最前面的元素,队列判断是否要移除头部的元素
window.poll(nums[i - k]);
// 滑动窗口加入元素
window.push(nums[i]);
// 记录对应的最大值
result[++index] = window.max();
}
return result;
}
}
/* 单调队列的实现 */
class MyQueue {
Deque<Integer> deque = new LinkedList<>();
//弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
//同时判断队列当前是否为空
public void poll(int num) {
if (!deque.isEmpty() && deque.getFirst() == num) {
deque.pollFirst();
}
}
//添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出
//保证队列元素单调递减
//比如此时队列元素3,1,而2将要入队,比1大,所以1弹出,此时队列:3,2
public void push(int num) {
// 将小于 num 的元素全部删除
while (!deque.isEmpty() && deque.getLast() < num) {
deque.pollLast();
}
// 然后将 num 加入尾部
deque.addLast(num);
}
//队列队顶元素始终为最大值
public int max() {
return deque.getFirst(); // peek()也可以
}
}
42. 接雨水
【参考:如何高效解决接雨水问题 :: labuladong的算法小抄】
部分参考:《代码随想录》第129-136页
以下都按列方向计算
常规解法
class Solution {
public int trap(int[] height) {
int sum=0;
int len=height.length;
for(int i=0;i<len;i++){
// 第一根和最后一根柱子不接水
if(i==0||i==len-1) continue;
int rHeight=height[i];// 右边柱子的最高高度
int lHeight=height[i];// 左边柱子的最高高度
for(int r=i+1;r<len;r++){
if(height[r]>rHeight)
rHeight=height[r];
}
for(int l=i-1;l>=0;l--){
if(height[l]>lHeight)
lHeight=height[l];
}
// 计算该柱子所在列雨水的面积
int h=Math.min(lHeight,rHeight)-height[i];// h>=0
if(h>0) sum+=h;
}
return sum;
}
}
动态规划(备忘录)
maxLeft[i] 和 maxRight[i] 分别代表 height[0…i] 和 height[i…end] 的最高柱子高度
class Solution {
public int trap(int[] height) {
int sum=0;
int len=height.length;
int[] maxLeft=new int[len];
int[] maxRight=new int[len];
// 初始化
maxLeft[0]=height[0];
maxRight[len-1]=height[len-1];
// 记录每个柱子的左边最大高度 从左向右计算
for(int i=1;i<len;i++){
maxLeft[i]=Math.max(height[i],maxLeft[i-1]);
}
// 记录每个柱子的右边最大高度 从右向左计算
for(int i=len-2;i>=0;i--){
maxRight[i]=Math.max(height[i],maxRight[i+1]);
}
for(int i=0;i<len;i++){
int h=Math.min(maxLeft[i],maxRight[i])-height[i];
if(h>0) sum+=h;
}
return sum;
}
}
双指针法
再把备忘录精简一下,用双指针边走边算
maxLeft是 height[0…left] 中最高柱子的高度,left 指针左边的最高柱子
maxRight是 height[right…end] 的最高柱子的高度 right指针右边的最高柱子
class Solution {
public int trap(int[] height) {
int sum=0;
int left=0,right=height.length-1;
int maxLeft=0, maxRight=0;
while(left<right){
maxLeft=Math.max(maxLeft,height[left]);
maxRight=Math.max(maxRight,height[right]);
if(maxLeft<maxRight){
sum+=maxLeft-height[left];
left++;
}else{
sum+=maxRight-height[right];
right--;
}
}
return sum;
}
}
单调栈法待定