2021.01.02 滑动窗口的最大值
今日题目:给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
- 思路:
- 法一:用一个数组记录滑动窗口内的最大值,然后直接用暴力法找出每个滑动窗口内的最大值。时间复杂度为O(n),空间复杂度为O(n)。
- 法二:大佬的代码是用双端队列解决的。先判断数组是否为空或者数组长度为1,若是,则直接返回数组;定义一个双端队列(存放的是对应的数组下标而不是数据本身),遍历数组中各元素,在队列不空的情况下,与队尾元素进行比较,若是大于等于队尾元素,队尾元素就出队,一直到队列空或者小于队尾元素,则插入队尾。判断此时队首元素是否失效(即是否已位于滑动窗口外),若已失效,则出队。再判断是否已经遍历完整个滑动窗口,若是则保存队首元素(此滑动窗口的最大值)。
自己的菜鸡代码
// 法一class Solution {public int[] maxSlidingWindow(int[] nums, int k) {// 暴力法解决if(nums.length < 2) return nums;int []record = new int[nums.length - k + 1];int max, j;for(int i = 0; i < record.length; i++){max = Integer.MIN_VALUE;for(j = i; j < i + k; j++){if(nums[j] > max) max = nums[j];}record[i] = max;}return record;}}
正确解法:采用双端队列
思路见下边代码的注解 (俺太菜了,大佬的代码看懂了但是也不一定会用)
// 法二class Solution {public int[] maxSlidingWindow(int[] nums, int k) {// 先判断数组是否为空或者数组长度为1,若是,则直接返回数组if(nums == null || nums.length < 2) return nums;// 结果数组int a[] = new int[nums.length - k + 1];// 双端队列(存放的是对应的数组下标而不是数据本身)LinkedList<Integer> queue = new LinkedList();// 遍历数组中各元素for(int i = 0; i < nums.length; i++){// 判断队列中是否为空,若不空,再将此时遍历到的元素nums[i]从队尾开始向前比较// 遇到比nums[i]小的数,一律弹出,直到遇到比它大的或者队列为空while(!queue.isEmpty() && nums[queue.peekLast()] <= nums[i]){queue.pollLast();// 队尾插入nums[i]对应的数组下标queue.addLast(i);// 若此时队首元素不在滑动窗口内,则弹出队首元素if(queue.peek() <= i-k) queue.poll();// 是否遍历完整个滑动窗口,若是则将队首元素(此时滑动窗口的最大值)存入结果数组中if(i-k+1 >= 0) a[i-k+1] = nums[queue.peek()];}return a;}}
运行结果:
法一(左),法二(右)

2021.01.05 用两个栈实现队列
今日题目:用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
提示:1 <= values <= 10000,最多会对 appendTail、deleteHead 进行 10000 次调用
思路:因为java中stack的容量会自动增长,初始值为10。因此入队操作不用考虑容量,出队操作时,可以先把s1中的数据全部存入s2中,s2栈顶元素即队首元素。
- 版本一:入队操作即s1栈的入栈操作,出队操作——先把s1中的数据全部存入s2中,弹出s2的栈顶元素,再把s2中的全部数据重新存入s1中。这样子大部分时间都用于s1与s2之间数据的存放。
- 版本二:入队操作即s1栈的入栈操作;出队操作——若s2中为空,则将s1中的数据全部存入s2中,弹出s2的栈顶元素,此时不再存入s1中,出队操作即s2的出栈操作;若s2中不为空,则直接执行s2的出队操作。(这样子应该可以省不少时间)
自己的菜鸡代码:
// 第一版,时间花费太多class CQueue {Stack<Integer> s1;Stack<Integer> s2;public CQueue() {// 初始化操作this.s1 = new Stack<Integer>();this.s2 = new Stack<Integer>();}public void appendTail(int value) {// 实现入队操作,因为java中的stack容量会自动增长,初始值为10s1.push(value);}public int deleteHead() {// 实现出队操作int result;if(!s1.isEmpty()){while(!s1.isEmpty()){s2.push(s1.peek());s1.pop();}result = s2.peek();s2.pop();while(!s2.isEmpty()){s1.push(s2.peek());s2.pop();}}else{result = -1;}return result;}}/*** Your CQueue object will be instantiated and called as such:* CQueue obj = new CQueue();* obj.appendTail(value);* int param_2 = obj.deleteHead();*/// 第二版,时间应该会少很多(flag先立)===========================================class CQueue {Stack<Integer> s1;Stack<Integer> s2;public CQueue() {// 初始化操作this.s1 = new Stack<Integer>();this.s2 = new Stack<Integer>();}public void appendTail(int value) {// 实现入队操作,因为java中的stack容量会自动增长,初始值为10s1.push(value);}public int deleteHead() {int result;if(!s2.isEmpty()){result = s2.peek();s2.pop();} else{if(s1.isEmpty()) return -1;while(!s1.isEmpty()){s2.push(s1.peek());s1.pop();}result = s2.peek();s2.pop();}return result;}}// 力扣官方出的====================================================================class CQueue {Deque<Integer> stack1;Deque<Integer> stack2;public CQueue() {stack1 = new LinkedList<Integer>();stack2 = new LinkedList<Integer>();}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 {int deleteItem = stack2.pop();return deleteItem;}}}
运行结果(版本一):zhwm级别的慢,感觉老人家走路都比这个快。
运行结果(版本二):怎么还是这么慢??

运行结果(官方版本的):
跟力扣官方版本的主要区别就是数据结构用的不太一样,但是现在我还不能很准确地给出这两个数据结构之间的不同。
2021.01.07 包含min函数的栈。(有点难)
今日题目:定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
要降低时间复杂度无非就使用空间换时间,主要就是看如何换使得花费的空间更少就是了。
提示:各函数的调用总次数不超过 20000 次
- 思路:(自己想不出来,卡在了当目前最小值被pop出去后如何在O(1)的时间复杂度情况下求栈中的最小值,俺好菜,好了,俺想到可以用辅助栈,每push一个数,就在辅助栈中记录当前栈的最小值。)大佬的算法中没有用到辅助栈,也没有用java自带的stack类型(空间复杂度不如直接用数组但是很方便)。先初始化栈,min = Integer.MAX_VALUE;而后执行push()操作时,先比较min与要入栈值x的大小,若x大于min则直接入栈,否则先将原min值入栈,然后min = x,再将x入栈。出栈的时候判断栈顶元素是否等于min值,若是,则先pop,然后使min = data[top],若是不相等则直接pop。
示意图(是这么个想法):

自己的菜鸡代码: ```java class MinStack { // 这个算法题最难的就是push和pop这两个方法 // 不用考虑栈为空的情况吗 private int data[]; // 用一个数组作为数据栈 private int top; // 栈顶指针 private int min; // 记录最小值 private int maxSize; // 最大长度
/* initialize your data structure here. / public MinStack() {
maxSize = 10000;data = new int[maxSize];top = -1;min = Integer.MAX_VALUE;
}
public void push(int x) {
if(x <= min) {data[++top] = min;min = x;}data[++top] = x;
}
public void pop() {
// 栈为空//if(top == -1) return;if(min == data[top]){min = data[--top];}--top;
}
public int top() {
// 栈为空//if(top == -1) return null;return data[top];
}
public int min() {
return min;
} }
/**
- 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(); */ ```
- 运行结果:

2021.01.08 栈的压入、弹出序列。(有点难,俺好菜)
今日题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。


提示:
- 0 <= pushed.length == popped.length <= 1000
- 0 <= pushed[i], popped[i] < 1000
pushed 是 popped 的排列
思路:(看了题解的,神奇,有点难理解)使用一个辅助栈,一个序列索引 i 来模拟栈 stack 的输入输出。遍历pushed数组,对于遍历到的数组元素num先入栈,再循环判断栈顶元素是否等于popped[i],若相等,则执行pop与i++;若不相等则对下一个pushed元素做相同处理。时间复杂度为:O(n);空间复杂度为O(n)。
- 自己的菜鸡代码:
```java
// 用java自带的Stack类型
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
} }if(pushed.length < 2) return true;Stack<Integer> stack = new Stack<Integer>();int i = 0;for(int num: pushed){stack.push(num);while(!stack.isEmpty() && stack.peek() == popped[i]){stack.pop();++i;}}return stack.isEmpty();
// 用数组实现
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
if(pushed.length < 2) return true;
//Stack
- 运行结果(左为用Stack类,右为用数组模拟栈实现):<a name="T9JR4"></a>### 2021.01.08 反转单词顺序今日题目:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. ",则输出"student. a am I"。<br /><br />说明:- 无空格字符构成一个单词。- 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。- 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。- 思路:1. 法一:可以用StringBuilder类来实现,先对字符串采取消除首尾空格的操作,对字符串从后往前遍历,每遇到一个单词,就截取下来加上一个空格加入StringBuilder的末尾最后再消除首尾空格。1. 法二:利用java自带的字符串分割函数,分割后再进行拼接,但是这种方法在面试的时候最好不要用(不过确实好好用)。法一法二的时间复杂度都为O(n),空间复杂度都为O(n)。- 自己的菜鸡代码:```java// 改动前class Solution {public String reverseWords(String s) {if(s == null) return s;s = s.trim();StringBuilder sentence = new StringBuilder();int j = s.length() - 1;for(int i = s.length() - 1; i >= 0;){j = i;while(i >= 0 && s.charAt(i) != ' ') --i;sentence.append(s.substring(i+1, j+1) + " ");while(i >= 0 && s.charAt(i) == ' ')--i;}return sentence.toString().trim();}}// 法二class Solution {public String reverseWords(String s) {String []str = s.trim().split(" ");StringBuilder sentence = new StringBuilder();for(int i = str.length - 1; i >= 0; i--){if(str[i].equals("")) continue;sentence.append(str[i]);sentence.append(" ");}return sentence.toString().trim();}}
- 运行结果:


