Day13-2021.1.21-剑指offer-栈和队列的整理。
3.栈和队列
名字 | 备注 |
---|---|
剑指 Offer 09.用两个栈实现队列.简单 | 形式新,实现新函数功能。 |
剑指 Offer 30.包含min函数的栈.简单 | 形式新,实现新函数功能。 |
剑指 Offer 31.栈的压入、弹出序列.中等 | |
剑指 Offer 58 - I.翻转单词顺序.简单 | |
剑指 Offer 59 - I.滑动窗口的最大值.简单 | |
剑指 Offer 59 - II.队列的最大值.中等 |
1.剑指 Offer 09 .用两个栈实现队列 【需要重刷】
完成在队列尾部插入整数和在队列头部删除整数的功能
思考:看着简单,但是这种题型新啊。另外,有视频讲解的挺清楚:简洁版+视频讲解的挺清楚:详细版+课后文字题解版本:
一个队列具备的两个功能分别由两个栈来完成:栈A实现入队功能,栈B实现出队功能。
栈无法实现队列功能: 栈底元素(对应队首元素)无法直接删除,需要将上方所有元素出栈。
双栈可实现列表倒序: 设有含三个元素的栈 stack1 = [1,2,3]和空栈 stack2 = []。若循环执行 stack1 元素出栈并添加入栈 stack2 ,直到栈 stack1 为空,则 stack1 = [], stack2 = [3,2,1],即 栈 stack2 元素实现栈 stack1 元素倒序 。
利用栈 B 删除队首元素: 倒序后,B 执行出栈则相当于删除了 A 的栈底元素,即对应删除了队列的队首元素。
import java.util.Stack;
// 一个队列具备的两个功能分别由两个栈来完成:栈A实现入队功能,栈B实现出队功能
public class Offer09 {
Stack<Integer> stack1;
Stack<Integer> stack2;
int size;
// 两个栈1和2
public void CQueue() {
stack1 = new Stack<>();
stack2 = new Stack<>();
size = 0;
}
// 存的时候存到1里
public void appendTail(int value) {
stack1.push(value);
size++;
}
// 取的时候看2里有没有元素,如果有则将2里的元素pop出,如果没有则将1里的元素全部pop到2中
public int deleteHead() {
if (size == 0) {
return -1;
}
if (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
// 然后再从2中pop出对应个数的元素
size--;
return stack2.pop();
}
}
2.剑指 Offer 30 .包含min函数的栈 【需要重刷】
实现一个能够得到栈的最小元素的 min 函数
—>>>栈A实现元素存储,栈B实现最小元素的存储。
注意:本题与主站 155 题相同:https://leetcode-cn.com/problems/min-stack/
思考:简单类型
序号 | 方法描述—Java Stack 类,后进先出。 |
---|---|
1 | boolean empty() 测试堆栈是否为空。 |
2 | Object peek( ) 查看堆栈顶部的对象,但不从堆栈中移除它。 |
3 | Object pop( ) 移除堆栈顶部的对象,并作为此函数的值返回该对象。 |
4 | Object push(Object element) 把项压入堆栈顶部。 |
5 | int search(Object element) 返回对象在堆栈中的位置,以 1 为基数。 |
import java.util.Stack;
public class Offer30 {
Stack<Integer> A, B;
// 在提交代码时,不能有 void ,具体原因我也不知道。
public void MinStack() {
// public MinStack() {
A = new Stack<>();
B = new Stack<>();
}
public void push(int x) {
A.push(x);
if (B.empty() || B.peek() >= x)
B.push(x);
}
public void pop() {
if (A.pop().equals(B.peek()))
B.pop();
}
public int top() {
return A.peek();
}
// 115题的函数名字是getMin()
public int min() {
return B.peek();
}
}
3.剑指 Offer 31 .栈的压入、弹出序列
注意:本题与主站 946 题相同:https://leetcode-cn.com/problems/validate-stack-sequences/
(1)
思考:视频讲解:,判断某一个序列是不是栈的弹出序列,那么需要模拟栈的压入和弹出。
下面的代码是按照视频上敲的,但是力扣官方通过不了,原因未知。
import java.util.Stack;
public class Offer31_1 {
public static void main(String[] args) {
int[] pushed = new int[]{1, 2, 3, 4, 5};
int[] popped = new int[]{4, 5, 3, 2, 1};
System.out.println(validateStackSequences(pushed, popped));
}
public static boolean validateStackSequences(int[] pushed, int[] popped) {
// 如果两个数组大小不一致,则返回false。
if (pushed.length != popped.length) {
return false;
}
// 这个相当于 输入的数组为空数组。
if (pushed.length < 1) {
return true;
}
Stack<Integer> stack = new Stack<>();
// 一号元素入栈
stack.push(pushed[0]);
int i = 1;
for (int j = 0; j < popped.length; j++) {
int num = popped[j];
// 不停的入栈,直到找到相同的代码
while (stack.peek() != num && i < pushed.length) {
stack.push(pushed[i++]);
}
// 找到相同的
if (stack.peek() == num) {
stack.pop();
// 结束运行
continue;
}
// 这里相当于没有找到
return false;
}
return true;
}
}
(2)
4.剑指 Offer 58 - I 翻转单词顺序
思考:视频讲解:
看起来也是简单的题型,通过入栈操作来处理,栈的原则就是先入后出,正好实现了翻转。
面试时,建议使用双指针,而不是分割+倒序的做法。
(2)分割+倒序【简单做法,常规思路】
import java.util.Stack;
public class Offer58_1 {
public static void main(String[] args) {
String s = new String("the sky is blue");
System.out.println(reverseWords(s));
}
public static String reverseWords(String s) {
String[] strs = s.trim().split(" "); // 删除首尾空格,分割字符串
StringBuilder res = new StringBuilder();
for (int i = strs.length - 1; i >= 0; i--) { // 倒序遍历单词列表
// 这个,是
if (strs[i].equals("")) continue; // 遇到空单词则跳过
res.append(strs[i] + " "); // 将单词拼接至 StringBuilder
}
return res.toString().trim(); // 转化为字符串,删除尾部空格,并返回
}
}
5.剑指 Offer 58 - II 左旋转字符串
思考:视频讲解:
(1)字符串切片
public class Offer58_2 {
public static void main(String[] args) {
String s = "abcdefg";
int k = 2;
System.out.println(reverseLeftWords(s, k));
}
public static String reverseLeftWords(String s, int n) {
// 1 <= k < s.length <= 10000
String s1 = s.substring(0, n);
String s2 = s.substring(n, s.length());
return s2 + s1;
}
}
(2)列表遍历拼接【也简单】
若面试规定不允许使用 切片函数 ,则使用此方法。
public class Offer58_2_2 {
public static void main(String[] args) {
String s = "abcdefg";
int k = 2;
System.out.println(reverseLeftWords(s, k));
}
public static String reverseLeftWords(String s, int n) {
StringBuffer res = new StringBuffer();
for (int i = n; i < s.length(); i++) {
res.append(s.charAt(i));
}
for (int i = 0; i < n; i++) {
res.append(s.charAt(i));
}
return res.toString();
}
}
6.剑指 Offer 59 - I 滑动窗口的最大值
队列,其实呢,你看文字解析看的是不太清楚的,去看视频吧。
思考:视频讲解:
注意:本题与主站 239 题相同:https://leetcode-cn.com/problems/sliding-window-maximum/
(1)暴力
两层 for
循环,每次都从窗口中找最大值即可。这样做的缺点是性能差。
import java.util.Deque;
import java.util.LinkedList;
public class Offer59_1_1 {
public static void main(String[] args) {
int[] nums = new int[]{1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
System.out.println(maxSlidingWindow(nums, k));
}
public static int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
if (n == 0) {
return nums;
}
int result[] = new int[n - k + 1];
for (int i = 0; i < result.length; i++) {
int max = Integer.MIN_VALUE;
for (int j = 0; j < k; j++) {
max = Math.max(max, nums[i + j]);
}
result[i] = max;
}
return result;
}
}
时间复杂度的话是 O(nk)
。
(2)单调队列
参考 这里。
我们可以用一个单调递减队列。单调递减队列添加元素的算法如下。
如果当前元素比队列的最后一个元素大,那么就将最后一个元素出队,重复这步直到当前元素小于队列的最后一个元素或者队列为空。进行下一步。
如果当前元素小于等于队列的最后一个元素或者队列为空,那么就直接将当前元素入队。
按照上边的方法添加元素,队列中的元素就刚好是一个单调递减的序列,而最大值就刚好是队头的元素。
当队列的元素等于窗口的大小的时候,由于添加元素的时候我们进行了出队操作,所以我们不能像解法二那样每次都删除第一个元素,需要先判断一下队头元素是否是我们要删除的元素。
// 业务代码
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> max = new ArrayDeque<>();
int n = nums.length;
if (n == 0) {
return nums;
}
int result[] = new int[n - k + 1];
int index = 0;
for (int i = 0; i < n; i++) {
if (i >= k) {
if (max.peekFirst() == nums[i - k]) {
max.removeFirst();
}
}
while (!max.isEmpty() && nums[i] > max.peekLast()) {
max.removeLast();
}
max.addLast(nums[i]);
if (i >= k - 1) {
result[index++] = max.peekFirst();
}
}
return result;
}
时间复杂度就是 O(n)
了,因为每个元素最多只会添加到队列和从队列删除两次操作。
import java.util.Deque;
import java.util.LinkedList;
public class Offer59_1 {
public static void main(String[] args) {
int[] nums = new int[]{1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
System.out.println(maxSlidingWindow(nums, k));
}
public static int[] maxSlidingWindow(int[] nums, int k) {
// 这里是说,如果数组的长度为0,或者窗口的宽度为0,那么直接返回一个结果。
if (nums.length == 0 || k == 0) {
return new int[0];
}
// Deque 双端队列,
Deque<Integer> deque = new LinkedList<>();
// 用来存储结果
int[] res = new int[nums.length - k + 1];
// i,j,分别去控制长度
for (int j = 0, i = 1 - k; j < nums.length; i++, j++) {
// i >= 1这里是为了控制 i-1≥0的。
if (i >= 1 && deque.peekFirst() == nums[i - 1])
deque.removeFirst(); // 删除 deque 中对应的 nums[i-1]
while (!deque.isEmpty() && deque.peekLast() < nums[j])
deque.removeLast(); // 保持 deque 递减
deque.addLast(nums[j]);
if (i >= 0)
res[i] = deque.peekFirst(); // 记录窗口最大值
}
return res;
}
}
7.剑指 Offer 59 - II 队列的最大值
思考:视频讲解: