232.用栈实现队列
232. 用栈实现队列
简单
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push
、pop
、peek
、empty
):
实现 MyQueue
类:
void push(int x)
将元素 x 推到队列的末尾int pop()
从队列的开头移除并返回元素int peek()
返回队列开头的元素boolean empty()
如果队列为空,返回true
;否则,返回false
说明:
- 你 只能 使用标准的栈操作 —— 也就是只有
push to top
,peek/pop from top
,size
, 和is empty
操作是合法的。 - 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
import java.util.Deque;
import java.util.LinkedList;
class MyQueue {
private Deque<Integer> stackIn; // 输入栈,用于入队操作
private Deque<Integer> stackOut; // 输出栈,用于出队和获取队首元素操作
public MyQueue() {
this.stackIn = new LinkedList<Integer>();
this.stackOut = new LinkedList<Integer>();
}
// 将元素推入输入栈
public void push(int x) {
stackIn.push(x);
}
// 从队列中弹出元素
public int pop() {
unload(); // 确保输出栈不为空
return stackOut.pop();
}
// 获取队列首部的元素
public int peek() {
unload(); // 确保输出栈不为空
return stackOut.peek();
}
// 检查队列是否为空
public boolean empty() {
if(stackIn.isEmpty() && stackOut.isEmpty()){
return true;
}
return false;
}
// 从输入栈向输出栈转移元素
private void unload(){
if(!stackOut.isEmpty()){
return; // 如果输出栈不为空,无需转移
}
// 当输出栈为空时,将输入栈的元素逐个弹出并压入输出栈
while (!stackIn.isEmpty()) {
stackOut.push(stackIn.pop());
}
}
}
/**
* 您的 MyQueue 对象将被实例化并按如下方式调用:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
上述代码的主要思路是将元素从输入栈中转移到输出栈中时将元素的顺序颠倒,此时再从输出栈中pop或者peek元素时的顺序就是队列的先入先出的顺序。
225.用队列实现栈
225. 用队列实现栈
简单
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push
、top
、pop
和 empty
)。
实现 MyStack
类:
void push(int x)
将元素 x 压入栈顶。int pop()
移除并返回栈顶元素。int top()
返回栈顶元素。boolean empty()
如果栈是空的,返回true
;否则,返回false
。
注意:
- 你只能使用队列的基本操作 —— 也就是
push to back
、peek/pop from front
、size
和is empty
这些操作。 - 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
import java.util.Queue;
import java.util.LinkedList;
class MyStack {
Queue<Integer> first; // 第一个队列
Queue<Integer> second; // 第二个队列
public MyStack() {
first = new LinkedList<>(); // 初始化第一个队列
second = new LinkedList<>(); // 初始化第二个队列
}
// 将元素推入栈顶
public void push(int x) {
first.offer(x); // 将元素添加到第一个队列中
}
// 弹出栈顶元素
public int pop() {
// 如果第一个队列不为空,则将其除了最后一个元素外的所有元素转移到第二个队列中
if(first.isEmpty()){
Queue<Integer> temp = first;
first = second;
second = temp;
}
int size = first.size();
for(int i = 0; i < size - 1; i ++){
second.offer(first.poll());
}
return first.poll(); // 返回第一个队列的最后一个元素(即栈顶元素)
}
// 获取栈顶元素
public int top() {
if(first.isEmpty()){
Queue<Integer> temp = first;
first = second;
second = temp;
}
int size = first.size();
for(int i = 0; i < size - 1; i ++){
second.offer(first.poll());
}
return first.peek(); // 返回第一个队列的最后一个元素(即栈顶元素),但不移除
}
// 检查栈是否为空
public boolean empty() {
if(first.isEmpty() && second.isEmpty()){
return true;
}
return false;
}
}
/**
* 您的 MyStack 对象将被实例化并按如下方式调用:
* MyStack obj = new MyStack();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.top();
* boolean param_4 = obj.empty();
*/
上述代码的主要思路是元素从第一个队列转移到第二个队列,取第一个队列的第一个元素,此时元素的顺序存在第二个队列中是没有改变的,每次进行取出操作时将非空的数组当做第一个队列
20.有效的括号
20. 有效的括号
简单
提示
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
import java.util.Deque;
import java.util.LinkedList;
class Solution {
public boolean isValid(String s) {
Deque<Character> deque = new LinkedList<>(); // 使用双端队列来存储左括号
for(int i = 0; i < s.length(); i++){
char sChar = s.charAt(i);
// 如果是左括号,则将对应的右括号入栈
if(sChar == '('){
deque.push(')');
}
else if(sChar == '{'){
deque.push('}');
}
else if(sChar == '['){
deque.push(']');
}
// 如果是右括号,但栈为空或栈顶元素不匹配当前右括号,则返回false
else if(deque.isEmpty() || deque.peek() != sChar){
return false;
}
// 如果是右括号且与栈顶元素匹配,则将栈顶元素出栈
else{
deque.pop();
}
}
// 如果栈不为空,则说明左括号多于右括号,返回false
if(!deque.isEmpty()){
return false;
}
return true; // 栈为空,表示所有括号都匹配,返回true
}
}
主要的思路是每遇到一个左括号就放一个与他对应的右括号到栈中,每遇到一个右括号就去栈中找最新一个放进去的右括号匹配,如果匹配就从栈中移除一个,不匹配则失败。如果最后栈中还有右括号没有被匹配掉,就失败
1047.删除字符串中的所有相邻重复项
1047. 删除字符串中的所有相邻重复项
简单
提示
给出由小写字母组成的字符串 S
,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
示例:
输入:"abbaca" 输出:"ca" 解释: 例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。
import java.util.Deque;
import java.util.LinkedList;
class Solution {
public String removeDuplicates(String s) {
// 使用一个栈来存储字符
Deque<Character> stack = new LinkedList<>();
// 遍历输入字符串
for(int i = 0; i < s.length(); i++){
// 如果栈不为空并且当前字符与栈顶字符相同
if(!stack.isEmpty() && stack.peek() == s.charAt(i)){
// 移除栈顶字符
stack.pop();
} else {
// 将当前字符压入栈中
stack.push(s.charAt(i));
}
}
// 使用 StringBuilder 构建结果字符串
StringBuilder builder = new StringBuilder();
// 将栈中字符出栈并添加到字符串构建器中
while(!stack.isEmpty()){
builder.append(stack.pop());
}
// 反转字符串构建器中的内容
builder.reverse();
// 返回去除重复字符后的结果字符串
return builder.toString();
}
}
本质上是匹配问题
150.逆波兰表达式
150. 逆波兰表达式求值
中等
给你一个字符串数组 tokens
,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
- 有效的算符为
'+'
、'-'
、'*'
和'/'
。 - 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是 向零截断 。
- 表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用 32 位 整数表示。
示例 1:
输入:tokens = ["2","1","+","3","*"] 输出:9 解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
输入:tokens = ["4","13","5","/","+"] 输出:6 解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:
输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"] 输出:22 解释:该算式转化为常见的中缀算术表达式为: ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 = ((10 * (6 / (12 * -11))) + 17) + 5 = ((10 * (6 / -132)) + 17) + 5 = ((10 * 0) + 17) + 5 = (0 + 17) + 5 = 17 + 5 = 22
提示:
1 <= tokens.length <= 104
tokens[i]
是一个算符("+"
、"-"
、"*"
或"/"
),或是在范围[-200, 200]
内的一个整数
import java.util.Deque;
import java.util.LinkedList;
class Solution {
// 计算逆波兰表达式
public int evalRPN(String[] tokens) {
// 使用栈存储操作数
Deque<Integer> stack = new LinkedList<Integer>();
int n = tokens.length;
// 遍历逆波兰表达式的每个元素
for (int i = 0; i < n; i++) {
String token = tokens[i];
// 如果是数字,则压入栈中
if (isNumber(token)) {
stack.push(Integer.parseInt(token));
} else {
// 如果是运算符,则取出栈顶的两个操作数进行计算,并将结果压入栈中
int num2 = stack.pop();
int num1 = stack.pop();
switch (token) {
case "+":
stack.push(num1 + num2);
break;
case "-":
stack.push(num1 - num2);
break;
case "*":
stack.push(num1 * num2);
break;
case "/":
stack.push(num1 / num2);
break;
default:
// 对于无效的运算符,不做任何操作
}
}
}
// 返回栈顶元素,即为最终计算结果
return stack.pop();
}
// 判断字符串是否为数字
public boolean isNumber(String token) {
// 如果不是加减乘除运算符,则认为是数字
return !("+".equals(token) || "-".equals(token) || "*".equals(token) || "/".equals(token));
}
}
难点在于什么是逆波兰表达式,代码思路很好理解
栈的最后表演! | LeetCode:150. 逆波兰表达式求值_哔哩哔哩_bilibili
239.滑动窗口最大值
239. 滑动窗口最大值
困难
提示
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 输出:[3,3,5,5,6,7] 解释: 滑动窗口的位置 最大值 --------------- ----- [1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7
示例 2:
输入:nums = [1], k = 1 输出:[1]
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length
注意这里的提示k的范围
暴力解法,通过40/51,超时
class Solution {
// 滑动窗口的最大值
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
// 初始化输出数组的长度
int [] output = new int[n - k + 1];
// 遍历数组,计算滑动窗口的最大值
for (int i = 0; i < n - k + 1; i++) {
// 初始化当前窗口的最大值为整数最小值
int max = Integer.MIN_VALUE;
// 遍历当前窗口内的元素,寻找最大值
for(int j = i; j < i + k; j++) {
max = Math.max(max, nums[j]);
}
// 将当前窗口的最大值存入输出数组
output[i] = max;
}
// 返回结果数组
return output;
}
}
class MyQueue {
Deque<Integer> deque = new LinkedList<>();
//弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
//同时判断队列当前是否为空
void poll(int val) {
if (!deque.isEmpty() && val == deque.peek()) {
deque.poll();
}
}
//添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出
//保证队列元素单调递减
//比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2
void add(int val) {
while (!deque.isEmpty() && val > deque.getLast()) {
deque.removeLast();
}
deque.add(val);
}
//队列队顶元素始终为最大值
int peek() {
return deque.peek();
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums.length == 1) {
return nums;
}
int len = nums.length - k + 1;
//存放结果元素的数组
int[] res = new int[len];
int num = 0;
//自定义队列
MyQueue myQueue = new MyQueue();
//先将前k的元素放入队列
for (int i = 0; i < k; i++) {
myQueue.add(nums[i]);
}
res[num++] = myQueue.peek();
for (int i = k; i < nums.length; i++) {
//滑动窗口移除最前面的元素,移除是判断该元素是否放入队列
myQueue.poll(nums[i - k]);
//滑动窗口加入最后面的元素
myQueue.add(nums[i]);
//记录对应的最大值
res[num++] = myQueue.peek();
}
return res;
}
}
239. 滑动窗口最大值
困难
提示
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 输出:[3,3,5,5,6,7] 解释: 滑动窗口的位置 最大值 --------------- ----- [1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7
示例 2:
输入:nums = [1], k = 1 输出:[1]
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length
class Solution {
public int[] topKFrequent(int[] nums, int k) {
// 统计每个数字出现的频率
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0; i < nums.length; i++){
map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
}
// 创建一个大顶堆,按照出现频率排序
Queue<int[]> queue = new PriorityQueue<>((m, n) -> n[1] - m[1]);
// 将每个数字和其频率加入堆中
for(Map.Entry<Integer,Integer> entry : map.entrySet()){
queue.add(new int[]{entry.getKey(), entry.getValue()});
}
// 从堆中弹出前k个高频数字
int[] ans = new int[k];
for(int i = 0; i < k; i++){
ans[i] = queue.poll()[0];
}
return ans;
}
}
Java中的队列和栈-CSDN博客