目录
参考了很多大佬的题解,仅作为自己学习笔记用。
1、09.两个栈实现队列
题意:
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
--------------------------------------------------
输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]
题解:
双栈可实现列表倒序,倒序之后删除栈顶元素。in 栈用来处理入栈(push)操作,另一个栈用来处理出栈(pop)操作。
假设 in 栈先进 1 再进 2,那么从 in 栈进入 out 栈的时候顺序就是先进 2 后进 1,再从 out 出栈的时候就是先出 1 后出 2,这已经和队列的 FIFO 顺序一样了,大家可以自行画图一目了然。
class CQueue {
Deque<Integer> in; // 此处准备用 LinkedList,用 Stack 也行
Deque<Integer> out;
public CQueue() {
in = new LinkedList<>();
out = new LinkedList<>();
}
public void appendTail(int value) {
in.push(value);
}
public int deleteHead() { // 一共三种情况
if(out.isEmpty()){
if(in.isEmpty()) // out 为空,in 为空,直接返回 -1
return -1;
while(!in.isEmpty()) // out 为空,in 不空,把 in 栈都放到 out 栈,返回 out 栈顶
out.push(in.pop());
return out.pop();
}else // out 不空,直接返回 out 栈顶
return out.pop();
}
}
2、30.包含 min 函数的栈
题意:
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 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.
题解:
数据栈 + 辅助栈。
数据栈 data 存放栈数据,辅助栈 min 存放当前数据栈中最小值。所以在数据栈 data 每次放入数据 x 之后,都要将 x 和 min 栈顶进行比较,将较小值再放入 min 栈。
这样每次调用 min 函数,直接取辅助栈 min 的栈顶值即可。data 每次 pop 的时候,min 也要 pop,保持两个栈大小相等。
但是这里会发现辅助栈有重复放的情况。所以可以这样:
放入辅助栈之前加个判断,如果 x <= 辅助栈顶,那么就放入 x,否则就不放。
弹栈也加个判断,如果数据栈弹出的值 = 辅助栈顶,说明最小值被弹出了,这个时候辅助栈也弹出,否则不弹出。
class MinStack {
Stack<Integer> data;
Stack<Integer> min;
/** initialize your data structure here. */
public MinStack() {
data = new Stack<>();
min = new Stack<>();
}
public void push(int x) {
data.push(x);
if(min.isEmpty() || x <= min.peek())
min.push(x);
}
public void pop() {
if(data.pop().equals(min.peek()))
min.pop();
}
public int top() {
return data.peek();
}
public int min() {
return min.peek();
}
}
3、31.栈的压入、弹出序列
题意:
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
输入: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
-------------------------------------------------------
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
题解:
使用一个栈来模拟压入弹出操作。每次入栈一个元素后,都要判断一下栈顶元素是不是当前出栈序列popped 的第一个元素,如果是的话则执行出栈操作并将 j 往后移一位,继续进行判断。
public boolean validateStackSequences(int[] pushed, int[] popped) {
int length = pushed.length, j = 0;
Stack<Integer> stack = new Stack<>();
for(int i=0; i<length; i++){
stack.push(pushed[i]);
while(j<length && !stack.isEmpty() && stack.peek() == popped[j]){
stack.pop();
j++;
}
}
return stack.isEmpty();
}
4、40.最小的 k 个数
题意:
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
--------------------------
输入:arr = [0,1,2,1], k = 1
输出:[0]
题解1:
维护一个大小为 K 的最大顶堆。往堆中先加入 k 个元素,这些元素会自动变为大顶堆的排列方式,当再往里面加一个元素,又会重新排列,这时候就弹出堆顶元素,这个元素肯定堆中最大值。到最后的时候,堆中留下的全是较小的元素,因为堆中最大元素一直在被删除。
public int[] getLeastNumbers(int[] arr, int k) {
if(arr == null || arr.length == 0)
return new int[0];
int[] res = new int[k];
// PriorityQueue实现了堆,需要传入比较器,改为大顶堆,默认是小顶堆
PriorityQueue<integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
for(int a : arr){
maxHeap.add(a);
if(maxHeap.size() > k){
maxHeap.poll();
}
}
for(int i = 0; i < k; i++){
res[i] = maxHeap.poll();
}
return res;
}
题解2:
快排变形。其实是快速选择算法。只不过和快速排序比较像而已。允许修改数组才可以用。
partition 后左边的元素都比 v 小,右边的都比 v 大。
此时如果 v 的下标为 j,且 j = k,那么左边的元素正好是 k 个,就是要找的前 k 个小值,否则递归寻找 j = k 。
public int[] getLeastNumbers(int[] arr, int k) {
if (k == 0 || arr.length == 0)
return new int[0];
// 最后一个参数表示找的是下标为 k 的数,最后返回下标 k 之前数即可
quickSelect(arr, 0, arr.length-1, k);
int[] res = new int[k];
for(int i=0; i<k; i++)
res[i] = arr[i];
return res;
}
private void quickSelect(int[] arr, int lo, int hi, int k) {
if(lo >= hi) return;
// 切分1次,基准元素的下标为 j,如果 j 恰好等于下标 k 就返回 j 左边所有的数,否则切分左或右
int j = partition(arr, lo, hi);
if(j == k)
return ;
else if(j > k)
quickSelect(arr, lo, j-1, k); //基准下标 > k,说明前k小的数在左边数组中
else
quickSelect(arr, j+1, hi, k); //基准下标 < k,说明右边数组也有前 k 小的
}
// 切分,返回基准元素最后的下标j,使得比nums[j]小的数都在j的左边,比nums[j]大的数都在j的右边
private int partition(int[] arr, int lo, int hi) {
int i = lo, j = hi + 1; // 左右扫描指针
int v = arr[lo]; // 切分元素,基准值
while (true) {
while (arr[++i] < v) if(i == hi) break;
while (arr[--j] > v) if(j == lo) break;
if (i >= j) break;
swap(arr, i, j);
}
swap(arr, lo, j);
return j;
}
public void swap(int[] a, int i, int j){
int t = a[i];
a[i] = a[j];
a[j] = t;
}
5、41.数据流中的中位数
题意:
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
设计一个支持以下两种操作的数据结构:
- void addNum(int num) - 从数据流中添加一个整数到数据结构中。
- double findMedian() - 返回目前所有元素的中位数。
输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]
------------------------------------------
输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]
题解:
一个大顶堆,存放左半边元素,一个小顶堆,存放右半边元素。保持两个堆处于平衡状态。
class MedianFinder {
PriorityQueue<Integer> left, right;
int cnt;
/** initialize your data structure here. */
public MedianFinder() {
// 左边大顶堆,存储左半边元素
left = new PriorityQueue<>((a, b) -> b - a);
// 右边小顶堆,存储右半边元素,存储的都比左边的大
right = new PriorityQueue<>();
// 当前数据流读入的元素个数
cnt = 0;
}
public void addNum(int num) {
// 保证两个堆处于平衡状态, 右边有时会多一个
// 先插入到右边,但是插入的元素不一定比左边的大,所以先插入左边,从左边弹出最大的到右边
if(cnt % 2 == 0){
left.add(num);
right.add(left.poll());
}else{
right.add(num);
left.add(right.poll());
}
cnt++;
}
public double findMedian() {
if(cnt % 2 == 0){
return (left.peek() + right.peek()) / 2.0;
}else{
return (double)right.peek();
}
}
}
6、59-1.滑动窗口的最大值
题意:
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
输入: 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
题解:
双端队列。
如果待加入的值比队尾值小,那么就加入;
如果待加入的值比队尾值大,那么队尾值肯定不是最大值,可以删掉,继续比较待加入的值和队尾值,如果队尾值还是小,继续删,直到队尾值比待加入的值大,再将待加入的值放入。
形成窗口后,每次都要将队首元素放入结果数组 res 中。如果滑动窗口左边出去的元素 = 队首元素,那么队首元素也需要删除。队列是非严格递减的。
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 i = 0; i < k; i++) {
while(!deque.isEmpty() && deque.peekLast() < nums[i]){
deque.removeLast();
}
deque.addLast(nums[i]);
}
res[0] = deque.peekFirst(); // 已经形成窗口,队首元素放入 res
// 形成窗口后
for(int i = k; i < nums.length; i++) {
if(deque.peekFirst() == nums[i-k]){
deque.removeFirst();
}
while(!deque.isEmpty() && deque.peekLast() < nums[i]){
deque.removeLast();
}
deque.addLast(nums[i]);
res[i - k + 1] = deque.peekFirst();
}
return res;
}
7、59-2.队列的最大值
题意:
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数 max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。若队列为空,pop_front 和 max_value 需要返回 -1。
输入:
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
-------------------------------
输入:
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]
题解:
定义队列并不难,获得最大值我们可以定义一个变量保存最大值,每次元素加入都进行更新,但是弹出元素后,次最大值就不知道了。所以,定义变量不行,此时利用双端队列。和上题类似。
class MaxQueue {
Queue<Integer> queue; // 要求定义的队列
Deque<Integer> deque; // 辅助的双端队列
public MaxQueue() {
queue = new LinkedList<>();
deque = new LinkedList<>();
}
public int max_value() {
if(queue.isEmpty())
return -1;
return deque.peekFirst();
}
public void push_back(int value) {
queue.add(value);
while(!deque.isEmpty() && value > deque.peekLast()){
deque.removeLast();
}
deque.addLast(value);
}
public int pop_front() {
if(queue.isEmpty())
return -1;
int temp = queue.poll();
if(temp == deque.peekFirst())
deque.removeFirst();
return temp;
}
}