目录
每日温度
1 题目描述
请根据每日 气温 列表 temperatures ,请计算在每一天需要等几天才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 0 来代替。
示例 1:
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
示例 2:
输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]
示例 3:
输入: temperatures = [30,60,90]
输出: [1,1,0]
提示:
- 1 <= temperatures.length <= 105
- 30 <= temperatures[i] <= 100
2 解题(Java)
单调栈
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int len = temperatures.length;
int[] res = new int[len];
Deque<Integer> stack = new LinkedList<>();
for (int i=0; i<len; i++) {
while (!stack.isEmpty() && temperatures[i]>temperatures[stack.peek()]) {
int top = stack.pop();
res[top] = i - top;
}
stack.push(i);
}
return res;
}
}
3 复杂性分析
- 时间复杂度:O(n),其中 n 是温度列表的长度。正向遍历温度列表一遍,对于温度列表中的每个下标,最多有一次进栈和出栈的操作;
- 空间复杂度:O(n),其中 n 是温度列表的长度。需要维护一个单调栈存储温度列表中的下标;
字符串解码
1 题目描述
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
示例 1:
输入:s = “3[a]2[bc]”
输出:“aaabcbc”
示例 2:
输入:s = “3[a2[c]]”
输出:“accaccacc”
示例 3:
输入:s = “2[abc]3[cd]ef”
输出:“abcabccdcdcdef”
示例 4:
输入:s = “abc3[cd]xyz”
输出:“abccdcdcdxyz”
2 解题(Java)
2.1 解法一:栈
本题难点在于括号内嵌套括号,需要从内向外生成与拼接字符串,这与栈的先入后出特性对应。
解题思路:
-
构建辅助栈 stack, 遍历字符串 s 中每个字符 c;
- 当 c 为数字时,将数字字符转化为数字 multi,用于后续倍数计算;
- 当 c 为字母时,在 res 尾部添加 c;
- 当 c 为 [ 时,将当前 multi 和 res 入栈,并分别置空:
- 记录此 [ 前的倍数 multi 至栈,用于发现对应 ] 后,获取 multi × […] 字符串。
- 记录此 [ 前的临时结果 res 至栈,用于发现对应 ] 后的拼接操作;
- 进入到新 [ 后,res 和 multi 重新记录。
- 当 c 为 ] 时,stack 出栈,拼接字符串 res = last_res + cur_multi * res,其中:
- cur_multi是当前 [ 到 ] 内字符串的重复倍数,例如 “3[a2[c]]” 中的 2。
- last_res是上个 [ 到当前 [ 的字符串,例如 “3[a2[c]]” 中的 a;
-
返回字符串 res。
class Solution {
public String decodeString(String s) {
StringBuilder res = new StringBuilder();
int multi = 0;
Deque<Integer> multiStack = new LinkedList<>();
Deque<String> resStack = new LinkedList<>();
for(char c : s.toCharArray()) {
if(c == '[') {
multiStack.push(multi);
resStack.push(res.toString());
multi = 0;
res = new StringBuilder();
}
else if(c == ']') {
StringBuilder tmp = new StringBuilder();
int curMulti = multiStack.pop();
for(int i = 0; i < curMulti; i++) tmp.append(res);
res = new StringBuilder(resStack.pop() + tmp.toString());
}
else if(Character.isDigit(c)) multi = multi * 10 + Integer.parseInt(c + "");
else res.append(c);
}
return res.toString();
}
}
复杂性分析
- 时间复杂度:O(N),遍历一次 s;
- 空间复杂度:O(N),辅助栈在极端情况下需要线性空间,例如 2[2[2[a]]];
2.2 解法二:分治
解题思路:
- 当s[i]是数字时,计算multi;
- 当s[i] == ‘[’ 时,开启新一层递归,得到 […]的结果,并拼接到res后面;
- 当s[i]为字符时,直接拼接到res后面;
- 遍历完毕后返回 res;
class Solution {
public String decodeString(String s) {
StringBuilder res = new StringBuilder();
int multi = 0;
for (int i=0; i<s.length(); i++) {
if (Character.isDigit(s.charAt(i))) {
multi = multi * 10 + Integer.parseInt(s.charAt(i) + "");
}
else if (s.charAt(i) == '[') {
int j = i + 1;
int counterPartition = 1;
while (counterPartition > 0) {
if (s.charAt(j) == '[') counterPartition++;
else if (s.charAt(j) == ']') counterPartition--;
j++;
}
String temp = decodeString(s.substring(i+1, j-1));
while (multi > 0) {
res.append(temp);
multi--;
}
i = j - 1;
}
else {
while (i < s.length() && !Character.isDigit(s.charAt(i))) {
res.append(s.charAt(i) + "");
i++;
}
i--;
}
}
return res.toString();
}
}
复杂性分析
- 时间复杂度:O(N),遍历一次 s;
- 空间复杂度:O(N),极端情况下递归深度将会达到线性级别;
最大矩形
1 题目描述
给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
示例 1:
输入:matrix = [[“1”,“0”,“1”,“0”,“0”],[“1”,“0”,“1”,“1”,“1”],[“1”,“1”,“1”,“1”,“1”],[“1”,“0”,“0”,“1”,“0”]]
输出:6
解释:最大矩形如上图所示。
示例 2:
输入:matrix = []
输出:0
示例 3:
输入:matrix = [[“0”]]
输出:0
示例 4:
输入:matrix = [[“1”]]
输出:1
示例 5:
输入:matrix = [[“0”,“0”]]
输出:0
提示:
- rows == matrix.length
- cols == matrix[0].length
- 0 <= row, cols <= 200
- matrix[i][j] 为 ‘0’ 或 ‘1’
2 解题(Java)
class Solution {
public int maximalRectangle(char[][] matrix) {
if (matrix.length == 0) return 0;
int row = matrix.length;
int col = matrix[0].length;
int res = 0;
Deque<Integer> mono_stack = new LinkedList<>();
int[] heights = new int[col];
int[] left = new int[col];
int[] right = new int[col];
for(int i = 0; i < row; i++) {
for(int j = 0; j < col; j++) {
if (matrix[i][j] == '1') heights[j]++;
else heights[j] = 0;
}
for(int j = 0; j < col; j++) {
while(!mono_stack.isEmpty() && heights[j] <= heights[mono_stack.peek()]) {
mono_stack.pop();
}
left[j] = mono_stack.isEmpty() ? -1 : mono_stack.peek();
mono_stack.push(j);
}
mono_stack.clear();
for (int j = col - 1; j >= 0; j--) {
while(!mono_stack.isEmpty() && heights[j] <= heights[mono_stack.peek()]) {
mono_stack.pop();
}
right[j] = mono_stack.isEmpty() ? col : mono_stack.peek();
mono_stack.push(j);
}
mono_stack.clear();
for (int j = 0; j < col; j++) {
res = Math.max(res, (right[j] - left[j] - 1) * heights[j]);
}
}
return res;
}
}
3 复杂性分析
- 时间复杂度O(mn):for循环使用O(mn)时间;输入数组里的每一个元素入栈一次,出栈一次,使用O(mn)时间;
- 空间复杂度O(n):新建数组以及单调栈各自占用O(N)空间;
滑动窗口的最大值
1 题目描述
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
示例:
提示:
你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。
2 解题(Java)
2.1 解题思路
本题难点: 如何在每次窗口滑动后,将 “获取窗口内最大值” 的时间复杂度从 O(k) 降低至 O(1);
回忆 包含min函数的栈,其使用 单调栈 实现了随意入栈、出栈情况下的 O(1) 时间获取 “栈内最小值” ,本题同理。
窗口对应的数据结构为 双端队列 ,本题使用 单调队列 即可解决以上问题。遍历数组时,每轮保证单调非严格递减队列 deque:
- deque 内仅包含窗口内的元素 ⇒ 每轮窗口滑动移除了元素 nums[left−1] ,如果上一个窗口[left-1,right-1]最大值deque.peekFirst()等于nums[left-1],还需删除deque的队头元素;
- deque 内的元素 非严格递减 ⇒ 每轮窗口滑动添加了元素 nums[right + 1],需将 deque 内所有 < nums[right + 1] 的元素删除;
2.2 算法流程
- 初始化: 双端队列deque ,结果列表res,数组长度 n;
- 滑动窗口: 左边界范围 left∈[1−k,n−k] ,右边界范围 right∈[0,n−1] ;
- 若 left > 0 且 队首元素 deque[0] == 被删除元素 nums[left - 1]:则队首元素出队;
- 删除 deque 内所有<nums[right] 的元素,以保持 deque 非严格递减;
- 将 nums[right] 添加至 deque 尾部;
- 若已形成窗口(left≥0 ):将窗口最大值(即队首元素 deque[0])添加至数组 res;
- 返回值: 返回结果列表 res ;
2.3 代码
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums.length == 0 || k == 0) return new int[0];
Deque<Integer> deque = new LinkedList<>();
int[] res = new int[nums.length - k + 1];
for (int right=0, left=1-k; right<nums.length; left++, right++) {
// 保持deque非严格递减
while (!deque.isEmpty() && deque.peekLast() < nums[right]) {
deque.pollLast();
}
deque.offer(nums[right]);
if (left >= 0) {
res[left] = deque.peek();// 记录窗口最大值
}
// 如果窗口[left,right]最大值恰好就是nums[left],右移之后最大值作废,所以删除队列头
if (left >= 0 && deque.peek() == nums[left]) {
deque.poll();
}
}
return res;
}
}
3 复杂性分析
- 时间复杂度 O(N) : 其中 N 为数组 nums 长度;线性遍历 nums 占用 O(N);每个元素最多仅入队和出队一次,因此单调队列 deque 占用 O(2N) ;
- 空间复杂度 O(k) : 双端队列 deque 中最多同时存储 k 个元素(即窗口大小);
字符串变形
1 题目描述
对于一个给定的字符串,我们需要在线性(也就是O(n))的时间里对它做一些变形。首先这个字符串中包含着一些空格,就像"Hello World"一样,然后我们要做的是把着个字符串中由空格隔开的单词反序,同时反转每个字符的大小写。比如"Hello World"变形后就变成了"wORLD hELLO"。
输入描述:
给定一个字符串s以及它的长度n(1≤n≤500)
返回值描述:
请返回变形后的字符串。题目保证给定的字符串均由大小写字母和空格构成。
示例1
输入
“This is a sample”,16
返回值
“SAMPLE A IS tHIS”
2 解题(Java)
public class Solution {
public String trans(String s, int n) {
String res = "";
String tempStr = "";
for(int i = 0; i < n; i++){
char c = s.charAt(i);
if(Character.isLowerCase(c))
tempStr += Character.toUpperCase(c);
else if(Character.isUpperCase(c))
tempStr += Character.toLowerCase(c);
else{
tempStr = c + tempStr;
res = tempStr + res;
tempStr = "";
}
}
res = tempStr + res;
return res;
}
}
3 复杂性分析
- 时间复杂度O(N):字符串的长度为N,线性遍历字符串的时间复杂度为O(N);
- 空间复杂度O(N):临时字符串tempstr最多占用O(N)空间;
表达式求值
1 题目描述
请写一个整数计算器,支持加减乘三种运算和括号。
示例1
输入
“1 + 2”
返回值
3
示例2
输入
“( 2 * ( 3 - 4 ) ) * 5”
返回值
-10
示例3
输入
“3 + 2 * 3 * 4 - 1”
返回值
26
2 解题(Java)
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
* 返回表达式的值
* @param s string字符串 待计算的表达式
* @return int整型
*/
public int solve(String s) {
s = s.trim();
Deque<Integer> stack = new LinkedList<>();
int number = 0;
char sign = '+';
char[] charArray = s.toCharArray();
for (int i = 0; i < charArray.length; i++) {
char c = charArray[i];
if (c == ' ') {
continue;
}
if (Character.isDigit(c)) {
number = number * 10 + c - '0';
}
if (c == '(') {
int j = i + 1;
int counterPartition = 1;
while (counterPartition > 0) {
if (charArray[j] == '(') {
counterPartition++;
}
if (charArray[j] == ')') {
counterPartition--;
}
j++;
}
number = solve(s.substring(i + 1, j - 1));
i = j - 1;
}
if (!Character.isDigit(c) || i == charArray.length - 1) {
if (sign == '+') {
stack.push(number);
} else if (sign == '-') {
stack.push(-1 * number);
} else if (sign == '*') {
stack.push(stack.pop() * number);
} else if (sign == '/') {
stack.push(stack.pop() / number);
}
number = 0;
sign = c;
}
}
int ans = 0;
while (!stack.isEmpty()) {
ans += stack.pop();
}
return ans;
}
}
3 复杂性分析
- 时间复杂度O(N):N为字符串长度,需线性遍历一遍字符串;
- 空间复杂度O(N):N为字符串长度,栈中存储的元素数量不超过N;
基本计算器
1 题目描述
实现一个基本的计算器来计算一个简单的字符串表达式 s 的值。
示例 1:
输入:s = “1 + 1”
输出:2
示例 2:
输入:s = " 2-1 + 2 "
输出:3
示例 3:
输入:s = “(1+(4+5+2)-3)+(6+8)”
输出:23
提示:
- 1 <= s.length <= 3 * 105
- s 由数字、‘+’、‘-’、‘(’、‘)’、和 ’ ’ 组成
- s 表示一个有效的表达式
2 解题(Java)
class Solution {
public int calculate(String s) {
s = s.trim();
int sign = 1, res = 0;
Deque<Integer> stack = new LinkedList<>();
stack.push(sign);
for (int i=0; i<s.length(); i++) {
char c = s.charAt(i);
if (c == ' ') {
continue;
}
else if (Character.isDigit(c)) {
int num = 0;
while (i < s.length() && Character.isDigit(s.charAt(i))) {
num = num * 10 + s.charAt(i) - '0';
i++;
}
res += sign * num;
i--;
}
else if (c == '+') {
sign = stack.peek();
}
else if (c == '-') {
sign = -stack.peek();
}
else if (c == '(') {
stack.push(sign);
}
else if (c == ')') {
stack.pop();
}
}
return res;
}
}
3 复杂性分析
- 时间复杂度O(N):N为字符串s的长度,线性遍历s一次的时间复杂度为O(N);
- 空间复杂度O(N):N为字符串s的长度,空间复杂度取决于栈的空间,栈中元素数量不超过N;
从尾到头打印链表
1 题目描述
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例:
输入:head = [1,3,2]
输出:[2,3,1]
2 解题(Java)
2.1 解题思路
- 链表是从头到尾访问每个结点;
- 题目要求倒序输出结点值;
- 这种先入后出的需求可以借助栈来实现;
2.2 算法流程
- 入栈:遍历链表,将各结点值push入栈;
- 出栈:将各结点值pop出栈,存储于数组中;
- 返回数组;
2.3 代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
Deque<Integer> stack = new LinkedList<>();
while (head != null) {
stack.push(head.val);
head = head.next;
}
int[] res = new int[stack.size()];
int i = 0;
while (!stack.isEmpty()) res[i++] = stack.pop();
return res;
}
}
3 复杂性分析
- 时间复杂度O(N):入栈和出栈共使用O(N)时间;
- 空间复杂度O(N):辅助栈stack使用O(N)的额外空间;
简化路径
1 题目描述
以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。
在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (..) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。
请注意,返回的规范路径必须始终以斜杠 / 开头,并且两个目录名之间必须只有一个斜杠 /。最后一个目录名(如果存在)不能以 / 结尾。此外,规范路径必须是表示绝对路径的最短字符串。
示例 1:
输入:"/home/"
输出:"/home"
解释:注意,最后一个目录名后面没有斜杠。
示例 2:
输入:"/../"
输出:"/"
解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。
示例 3:
输入:"/home//foo/"
输出:"/home/foo"
解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。
示例 4:
输入:"/a/./b/../../c/"
输出:"/c"
示例 5:
输入:"/a/../../b/../c//.//"
输出:"/c"
示例 6:
输入:"/a//bc/d//././/.."
输出:"/a/b/c"
2 解题(Java)
- 用栈的思想来解决。
- 对于Deque接口,栈的应用是队头插队头删,队列的应用是队尾插队头删。
class Solution {
public String simplifyPath(String path) {
Deque<String> stack = new LinkedList<>();
for (String item : path.split("/")) {
if (item.equals("..")) {
if (!stack.isEmpty()) {
stack.pop();
}
} else if (!item.equals("") && !item.equals(".")) {
stack.push(item);
}
}
String res = "";
while (!stack.isEmpty()) res = "/" + stack.pop() + res;
return res.length() == 0 ? "/" : res;
}
}
3 复杂性分析
- 时间复杂度O(N):遍历字符串数组和栈使用线性时间;
- 空间复杂度O(N):字符串数组和栈占用线性空间;
有效的括号
1 题目描述
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 注意空字符串可被认为是有效字符串。
示例 1:
输入: “()”
输出: true
示例 2:
输入: “()[]{}”
输出: true
示例 3:
输入: “(]”
输出: false
示例 4:
输入: “([)]”
输出: false
2 解题(Java)
栈和哈希表:
class Solution {
public boolean isValid(String s) {
int n = s.length();
if (n % 2 == 1) {
return false;
}
Map<Character, Character> pairs = new HashMap<>() {{
put(')', '(');
put(']', '[');
put('}', '{');
}};
Deque<Character> stack = new LinkedList<>();
for (int i = 0; i < n; i++) {
char ch = s.charAt(i);
if (pairs.containsKey(ch)) {
if (stack.isEmpty() || stack.peek() != pairs.get(ch)) {
return false;
}
stack.pop();
} else {
stack.push(ch);
}
}
return stack.isEmpty();
}
}
3 复杂性分析
- 时间复杂度 O(N):需要遍历一遍 s;
- 空间复杂度 O(N):栈使用线性的空间大小;
柱状图中最大的矩形
1 题目描述
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。
图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。
示例:
输入: [2,1,5,6,2,3]
输出: 10
2 解题(Java)
单调栈(严格递增)+哨兵:
class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] left = new int[n];
int[] right = new int[n];
Deque<Integer> monoStack = new LinkedList<>();
for (int i = 0; i < n; i++) {
while (!monoStack.isEmpty() && heights[i] <= heights[monoStack.peek()]) {
monoStack.pop();
}
left[i] = monoStack.isEmpty() ? -1 : monoStack.peek();
monoStack.push(i);
}
monoStack.clear();
for (int i = n - 1; i >= 0; i--) {
while (!monoStack.isEmpty() && heights[i] <= heights[monoStack.peek()]) {
monoStack.pop();
}
right[i] = monoStack.isEmpty() ? n : monoStack.peek();
monoStack.push(i);
}
int ans = 0;
for (int i = 0; i < n; i++) {
ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
}
return ans;
}
}
3 复杂性分析
- 时间复杂度O(N):for循环使用O(N)时间;输入数组里的每一个元素入栈一次,出栈一次,使用O(N)时间;
- 空间复杂度O(N):新建数组以及单调栈各自占用O(N)空间;
包含min函数的栈
1 题目描述
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
示例:
MinStack minStack = new MinStack(); minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.min(); --> 返回 -2.
提示:
各函数的调用总次数不超过 20000 次
2 解题(Java)
class MinStack {
private Deque<Integer> contentStack;
private Deque<Integer> minStack;
public MinStack() {
contentStack = new LinkedList<>();
minStack = new LinkedList<>();
}
public void push(int x) {
contentStack.push(x);
if(minStack.isEmpty()) minStack.push(x);
else minStack.push(Math.min(x, minStack.peek()));
}
public void pop() {
contentStack.pop();
minStack.pop();
}
public int top() {
return contentStack.peek();
}
public int min() {
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.min();
*/
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/bao-han-minhan-shu-de-zhan-lcof
3 复杂性分析
- 时间复杂度 O(1): push(), pop(), top(), min() 四个函数的时间复杂度均为常数级别;
- 空间复杂度 O(N): 辅助栈使用 O(N) 额外空间;
栈的压入、弹出序列
1 题目描述
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {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 的排列。
2 解题(Java)
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
Deque<Integer> stack = new LinkedList<>();
int i = 0;
for(int num : pushed) {
stack.push(num); // num 入栈
while(!stack.isEmpty() && stack.peek() == popped[i]) { // 循环判断与出栈
stack.pop();
i++;
}
}
return stack.isEmpty();
}
}
3 复杂性分析
- 时间复杂度O(N):其中N为数组pushed的长度,每个元素最多入栈与出栈一次,即最多共2N次出入栈操作;
- 空间复杂度O(N):辅助栈stack最多同时存储N个元素;
队列的最大值
1 题目描述
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1
示例 1:
输入:
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
示例 2:
输入:
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]
限制:
- 1 <= push_back,pop_front,max_value的总操作数 <= 10000
- 1 <= value <= 10^5
2 解题(Java)
class MaxQueue {
// data_queue为队列
Deque<Integer> data_queue;
// max_deque为非严格递减的双端队列
Deque<Integer> max_deque;
public MaxQueue() {
data_queue = new LinkedList<>();
max_deque = new LinkedList<>();
}
public int max_value() {
if (max_deque.isEmpty()) {
return -1;
}
return max_deque.peek();
}
public void push_back(int value) {
// 维护max_deque从头到尾非严格递减,这样max_deque的队头元素始终代表队列的最大值
while (!max_deque.isEmpty() && max_deque.peekLast() < value) {
max_deque.pollLast();
}
max_deque.offer(value);
// data_queue无论如何加入元素
data_queue.offer(value);
}
public int pop_front() {
if (data_queue.isEmpty()) {
return -1;
}
// 如果data_queue队头元素等于max_deque队头元素,让max_deque队头元素出队列,否则不出
if (data_queue.peek().equals(max_deque.peek())) {
max_deque.poll();
}
// data_queue队头元素无论如何出队列
return data_queue.poll();
}
}
/**
* Your MaxQueue object will be instantiated and called as such:
* MaxQueue obj = new MaxQueue();
* int param_1 = obj.max_value();
* obj.push_back(value);
* int param_3 = obj.pop_front();
*/
/**
* Your MaxQueue object will be instantiated and called as such:
* MaxQueue obj = new MaxQueue();
* int param_1 = obj.max_value();
* obj.push_back(value);
* int param_3 = obj.pop_front();
*/
3 复杂性分析
- 时间复杂度O(1):删除操作与求最大值操作显然只需要 O(1) 的时间。而插入操作虽然看起来有循环,做一个插入操作时最多可能会有 n 次出队操作。但要注意,由于每个数字只会出队一次,因此对于所有的 n 数字的插入过程,对应的所有出队操作也不会大于 n 次。因此将出队的时间均摊到每个插入操作上,时间复杂度为 O(1)。
- 空间复杂度O(N):需要用队列存储所有插入的元素。
用两个栈实现队列
1 题目要求
用两个栈实现一个队列。
队列的声明如下:
请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
2 代码实现
class CQueue {
Deque<Integer> stack1;
Deque<Integer> stack2;
public CQueue() {
stack1 = new LinkedList();
stack2 = new LinkedList();
}
public void appendTail(int value) {
stack1.push(value);
}
public int deleteHead() {
if (stack2.isEmpty()){
while (!stack1.isEmpty()) stack2.push(stack1.pop());
}
if (stack2.isEmpty()) {
return -1;
} else {
return stack2.pop();
}
}
}
3 复杂性分析
- 时间复杂度O(1):插入不多说,对于删除操作,由于每个元素只会至多被插入和弹出 stack2 一次,因此均摊下来每个元素被删除的时间复杂度仍为 O(1);
- 空间复杂度O(N):需要使用两个栈存储已有的元素;