2020.06.21
1、两个栈来实现一个队列
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
思路:
1、入栈只入push栈,出栈只出pop栈
2、若pop栈不为空,push栈不能进入
3、push栈的元素如果可以加入到pop栈,则需要全部一起到pop栈
public class Stack2Queue {
Stack<Integer> pushStack = new Stack<Integer>();
Stack<Integer> popStack = new Stack<Integer>();
/**
* 入队只入一个栈
* @param node
*/
public void push(int node) {
pushStack.push(node);
}
/**
* 出队只从一个栈中出
* @return
*/
public int pop() {
if(popStack.empty()){
while (!pushStack.empty()){
popStack.push(pushStack.pop());
}
}
return popStack.pop();
}
}
扩展:两个队列实现栈
思路:队列实现栈:腾空一下位置,将最后一个返回
出栈和入栈都是在data队列,但是需要一个辅助队列实现。
入栈只入数据队列
出栈是需要返回数据队列最后进入的一个元素,所以将队列的最后一个元素按照要求返回,然后将data队列剩下的放到辅助队列,最后需要换引用。
如果不换引用,将data队列剩下的放到辅助队列后,data队列就空了
public class Queue2Stack {
public static void main(String[] args) {
Queue2Stack stack=new Queue2Stack();
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
System.out.println(stack.pop()); //4
System.out.println(stack.pop()); //3
System.out.println("top"+stack.top()); //2
System.out.println(stack.pop()); //2
System.out.println(stack.pop()); //1
}
private ArrayDeque<Integer> data= new ArrayDeque<>();
private ArrayDeque<Integer> help= new ArrayDeque<>();
/**
* 模拟入栈操作:入栈只入数据栈
* @param node
*/
public void push(Integer node){
data.offer(node);
}
/**
* 模拟出栈:需要借助辅助队列,将数据队列的前面的数据都移动
* @return
*/
public int pop(){
if (data.isEmpty()){
return Integer.MIN_VALUE;
}
//全部移动到help
while (data.size()>1){
help.offer(data.poll());
}
int res=data.poll();
swap();
return res;
}
/**
* 获取栈顶元素
* @return
*/
public int top(){
if (data.isEmpty()){
return Integer.MIN_VALUE;
}
while (data.size()>1){
help.offer(data.poll());
}
int res=data.poll();
//和pop不同的就多了这一步
help.offer(res);
swap();
return res;
}
/**
* 交换两个队列的引用
*/
private void swap() {
ArrayDeque<Integer> temp=data;
data=help;
help=temp;
}
}
2、最小栈
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法。
思路:
1、使用辅助栈min,入栈只进data栈,min栈同步记录最小值,如果进入的数num大于min栈的栈顶,则栈顶继续压栈,否则min栈入栈num
2、出栈的时候data栈和min栈同步出栈
public class MinStack {
Stack<Integer> data = new Stack<>();
Stack<Integer> min = new Stack<>();
public static void main(String[] args) {
MinStack minStack = new MinStack();
minStack.push(3);
System.out.println(minStack.min());
minStack.push(4);
System.out.println(minStack.min());
minStack.push(2);
System.out.println(minStack.min());
minStack.push(3);
System.out.println(minStack.min());
minStack.pop();
System.out.println(minStack.min());
minStack.pop();
System.out.println(minStack.min());
minStack.push(0);
System.out.println(minStack.min());
}
public void push(int node) {
data.push(node);
//关键:min栈进栈的时候需要判断
if (min.empty()) {
min.push(node);
} else {
if (min.peek() <= node) {
min.push(min.peek());
} else {
min.push(node);
}
}
}
public void pop() {
if (!data.empty()) {
data.pop();
}
if (!min.empty()) {
min.pop();
}
}
public int top() {
if (!data.empty()) {
return data.peek();
}
return -1;
}
public int min() {
if (!min.empty()) {
return min.peek();
}
return -1;
}
}
更好的思路:使用min变量来分割截至目前的栈中的最小值,如果出现比min更小的,说明min需要被保存,表示这个更小的值之前的最小值是min,同时更新min
出栈时候,需要判断出栈的元素res是不是min,如果是,说明出栈的元素是更小值(因为这个min其实是更新后的min),res下一个才是旧的最小值,需要再pop一次。
public class MinStackBetter {
private Stack<Integer> data = new Stack<>();
int min = Integer.MAX_VALUE;
/**
* 数据入栈是需要处理
*
* @param node
*/
public void push(int node) {
//1、min保存截至目前入栈的元素中的最小值
//遇到更小需要更新
if (min >= node) {
data.push(min);
//min更新变得更小
min = node;
}
//先min入栈再node入栈就是保证min是表示下面的元素中的最小值
//正常插入新的数据
data.push(node);
}
/**
* 出栈的时候也需要特殊处理:
*/
public void pop() {
//正常数据只需要pop一次
int res = data.pop();
//说明还需要再pop一下
if (res == min) {
min = data.pop();
}
}
public int top() {
return data.peek();
}
public int min() {
return min;
}
}
3、判断栈的压入和弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
思路:使用辅助栈,出栈的序列顺序决定压栈序列是否压栈
(1)遍历入栈序列,加入辅助栈,同时使用popindex遍历出栈队列,如果下一个弹出的数刚好是栈顶元素,则说明入栈序列和出栈序列合格,需要循环弹出。
(2)如果不等,则继续循环,也就是入栈序列继续下一个入栈
(3)最后判断栈是否为空,如果栈为空,说明出栈序列符合规定。
public class IsPopOrder {
public boolean IsPopOrder(int[] pushA, int[] popA) {
if (pushA == null || pushA.length == 0
|| popA == null || popA.length == 0||popA.length!=pushA.length) {
return false;
}
ArrayDeque<Integer> stack=new ArrayDeque<>();
//需要遍历入栈序列
int len=pushA.length,popIndex=0;
for (int i=0;i<len;i++){
stack.push(pushA[i]);
//如果栈顶和出栈序列相等,则全部直接出栈
while (!stack.isEmpty()&&stack.peek()==popA[popIndex]){
stack.pop();
popIndex++;
}
//如果不等,则继续循环,也就是入栈序列一直入栈
}
//如果最后栈空,说明刚好完全出栈
return stack.isEmpty();
}```
4、数据流中的中位数(堆)
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
思路:大根堆存小数(小数的最大)+小根堆存大数(大数的最小)+个数差别不超过1(使用判断大小实现)
1、大根堆存放的是较小的n/2个数,小根堆存放的是较大的n/2个数,判断大小是通过大根堆的堆顶,第一个元素(通过大根堆是否为空判断第一个)因为堆都是空,第一个元素先入大于大根堆
2、大于大根堆堆顶就放到小根堆,小于等于就放到大根堆
3、如果两个堆的元素个数(通过大小获取)差值超过1,则多的那个弹出堆顶给少的那个堆(确保两个堆的元素个数差值不超过1),最后大根堆和小根堆的堆顶一定可以找到中位数(有可能不是传入的数,而是计算而得的小数)
4、使用count记录元素个数,如果元素个数是奇数,则哪个堆的数量多就是哪个堆顶;若为偶数,则中位数是两个堆顶的平均数。
注意:顺序是小根堆,逆序是大根堆
public class GetMedianFromDataStream {
public static void main(String[] args) {
GetMedianFromDataStream stream=new GetMedianFromDataStream();
stream.Insert(5);
Double median1 = stream.GetMedian();
System.out.println(median1);
stream.Insert(2);
Double median2 = stream.GetMedian();
System.out.println(median2);
stream.Insert(3);
Double median3 = stream.GetMedian();
System.out.println(median3);
stream.Insert(4);
Double median4 = stream.GetMedian();
System.out.println(median4);
stream.Insert(1);
Double median5 = stream.GetMedian();
System.out.println(median5);
stream.Insert(6);
Double median6 = stream.GetMedian();
System.out.println(median6);
stream.Insert(7);
Double median7 = stream.GetMedian();
System.out.println(median7);
stream.Insert(0);
Double median8 = stream.GetMedian();
System.out.println(median8);
stream.Insert(8);
Double median9 = stream.GetMedian();
System.out.println(median9);
}
PriorityQueue<Integer> minHeap=new PriorityQueue<Integer>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});
PriorityQueue<Integer> maxHeap=new PriorityQueue<Integer>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
int count=0;
public void Insert(Integer num) {
//注意不能放在后面
count++;
//1、一开始先插入大根堆
if (maxHeap.isEmpty()){
maxHeap.offer(num);
return;
}else {
if (maxHeap.peek()>=num){
//比大根堆的max小,则放入大根堆
maxHeap.offer(num);
}else {
//比大根堆的max大,则放入小根堆
minHeap.offer(num);
}
}
//每次加入新的数以后都要进行堆的调整,保证个数相差不超过1
modifyTwoHeapsSize();
}
private void modifyTwoHeapsSize() {
if (maxHeap.size()+2==minHeap.size()){
maxHeap.offer(minHeap.poll());
}else if (minHeap.size()+2==maxHeap.size()){
minHeap.offer(maxHeap.poll());
}
}
public Double GetMedian() {
if (count==0){
return null;
}
if ((count&1)==1){
//说明是奇数,哪个堆大就返回哪个堆的堆顶
if (maxHeap.size()>minHeap.size()){
//注意不是poll
return Double.valueOf(maxHeap.peek());
}else {
return Double.valueOf(minHeap.peek());
}
}else {
//说明是偶数,返回两个堆堆顶的平均数
return (maxHeap.peek()+minHeap.peek())/2.0;
}
}
}