包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
可通过建立辅助栈实现,栈A用于存储所有元素,栈B中中存储栈 A中非严格降序 的元素,栈A中最小元素始终位于栈B的顶端
Stack<Integer> A, B;
public MinStack() {
A = new Stack<>();
B = new Stack<>();
}
public void push(int x) {
A.add(x);
if(B.empty() || B.peek() >= x)
B.add(x);
}
public void pop() {
if(A.pop().equals(B.peek()))
B.pop();
}
public int top() {
return A.peek();
}
public int min() {
return B.peek();
}
用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
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;
}
}
}
栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
考虑借用一个辅助栈 stack ,模拟 压入 / 弹出操作的排列。根据是否模拟成功,即可得到结果。
public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack<Integer> stack = new Stack<>();
int i = 0;
for(int num : pushed) {
stack.push(num); // num 入栈
while(!stack.isEmpty() && stack.peek() == popped[i]) { // 循环判断与出栈
stack.pop();
i++;
}
}
return stack.isEmpty();
}
栈排序
对栈进行排序使最小元素位于栈顶
用一个辅助栈
class SortedStack {
private Stack<Integer> data = new Stack<>();
private Stack<Integer> help = new Stack<>();
public SortedStack() {
super();
}
public void push(int val) {
if(isEmpty()) { // 如果data ,没有数据,添加数据,添加help中数据
data.push(val);
while(!help.isEmpty()) {
data.push(help.pop());
}
} else { // 获取data 顶部元素。比较大小,如果大于,那么添加元素,否则help添加元素
int top = peek();
if(top >= val) {
data.push(val);
while(!help.isEmpty()) {
data.push(help.pop());
}
} else {
help.push(data.pop());
push(val);
}
}
}
public void pop() {
if(!isEmpty()) {
data.pop();
}
}
public int peek() {
if(isEmpty()) {
return -1;
} else {
return data.peek();
}
}
public boolean isEmpty() {
return data.isEmpty();
}
}
用队列实现栈
class MyStack {
private Queue<Integer> q1 = new LinkedList<>();
private Queue<Integer> q2 = new LinkedList<>();
int top = 0;
MyStack() {
}
public void push(int x) {
q1.add(x);
top = x;
}
public int pop() {
while (q1.size() > 1) {
top = q1.remove();
q2.add(top);
}
int a = q1.remove();
Queue<Integer> temp = q1;
q1 = q2;
q2 = temp;
return a;
}
public int top() {
return top;
}
public boolean empty() {
return q1.isEmpty();
}
};
三合一
描述如何只用一个数组来实现三个栈。
首先要构建数组,3倍长度的size
然后让另一个3的数组,存放和管理下标
然后在根据结构分析
class TripleInOne {
int[] a;
int[] tt = new int[3];
int size;
public TripleInOne(int statckSize){
a = new int[3 * statckSize + 10];
size = statckSize;
for(int i = 0; i < 3; i++){
tt[i] = i * statckSize - 1;
}
}
public boolean isFull(int stackNum){
return tt[stackNum] >= size * (stackNum + 1) - 1;
}
public void push(int stackNum, int value){
if(isFull(stackNum)) return;
a[++tt[stackNum]] = value;
}
public int pop(int stackNum){
if(isEmpty(stackNum)) return -1;
tt[stackNum]--;
return a[tt[stackNum] + 1];
}
public int peek(int stackNum){
if(isEmpty(stackNum)) return -1;
return a[tt[stackNum]];
}
public boolean isEmpty(int stackNum){
return tt[stackNum] < stackNum * size;
}
}
汉诺塔问题
在经典汉诺塔问题中,有 3 根柱子及 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。移动圆盘时受到以下限制:
(1) 每次只能移动一个盘子;
(2) 盘子只能从柱子顶端滑出移到下一根柱子;
(3) 盘子只能叠在比它大的盘子上。
请编写程序,用栈将所有盘子从第一根柱子移到最后一根柱子。
解题思路:递归与分治
public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
hanoi(A.size(), A, B, C);
}
public void hanoi(int n, List<Integer> A, List<Integer> B, List<Integer> C){
if(n == 1){
C.add(A.get(A.size() - 1));
A.remove(A.size() - 1);
}else{
//把A经过辅助C放到B上
hanoi(n - 1, A, C, B);
//把A放到C上
C.add(A.get(A.size() - 1));
A.remove(A.size() - 1);
//把B经过辅助A放到C上
hanoi(n - 1, B, A, C);
}
}
队列
用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
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;
}
}
}
数据流中的中位数 (优先队列)
优先队列 / 堆, 小顶堆,保存较大的一半, 大顶堆,保存较小的一半,堆顶的平均数即为中位数
class MedianFinder {
Queue<Integer> A, B;
public MedianFinder() {
A = new PriorityQueue<>(); // 小顶堆,保存较大的一半
B = new PriorityQueue<>((x, y) -> (y - x)); // 大顶堆,保存较小的一半
}
public void addNum(int num) {
if(A.size() != B.size()) {
A.add(num);
B.add(A.poll());
} else {
B.add(num);
A.add(B.poll());
}
}
public double findMedian() {
return A.size() != B.size() ? A.peek() : (A.peek() + B.peek()) / 2.0;
}
}
滑动窗口的最大值 单调队列
我们维护一个单调的双向队列,窗口在每次滑动的时候,我就从队列头部取当前窗口中的最大值,每次窗口新进来一个元素的时候,我就将它与队列中的元素进行大小比较
如果刚刚进来的元素比队列的尾部元素大,那么先将队列尾部的元素弹出,然后把刚刚进来的元素添到队列的尾部;
如果刚刚进来的元素比队列的尾部元素小,那么把刚刚进来的元素直接添到队列的尾部即可。
方法二:构建双端队列
保持队列大小不超过窗口,以及最大值在出端
读取新数据,入端操作,和之前的进入的数据比较,之前的数据小则剔除,直到比它大则停止,最大值还在出端
循环上面步骤,直到结束
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 j = 0, i = 1 - k; j < nums.length; i++, j++) {
if(i > 0 && 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;
}
队列的最大值
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
方法二:维护一个单调的双端队列
public class MaxQueue {
Queue<Integer> queue;
LinkedList<Integer> max;
public MaxQueue() {
queue = new LinkedList<>();
max = new LinkedList<>();//LinkedList是双端链表
}
public int max_value() {
return max.size()==0?-1:max.getFirst();
}
public void push_back(int value) {
queue.add(value);
while(max.size()!=0&&max.getLast()<value){//注意:这里第二个判断条件不能带等号,即max中对于当前queue中的具有相同值的元素会全部存储,而不是存储最近的那个。只添加递增
max.removeLast();
}
max.add(value);
}
public int pop_front() {
if(max.size()!=0&&queue.peek().equals(max.getFirst()))//Integer类型的值的比较不能直接使用==
max.removeFirst();
return queue.size()==0?-1:queue.poll();
}
}
优先队列的主要操作
优先队列辅助操作
返回优先队列中键值为第k个最小最大的元素
返回优先队列的元素个数
基于键值的优先级将优先队列的元素进行排序
应用
最短路径算法:Dijkstra算法
最小生成树算法:Prim算法
事件驱动仿真:顾客排队算法
选择问题:查找第k个最小元素