java栈和队列语法基础
java中的栈和队列的实现用双端队列Deque
Deque deque = new LinkedList<type>()//此种方式创建取出的元素是object类型要强制转换为int等
Deque<type> deque =new LinkedList<>()//此种方式创建不需要强制类型转化。
#用作队列
deque.addLast(e);
deque.removeFirst(e);
deque.getFirst(e);
#用作栈
deque.addFirst(e);
deque.removeFirst(e);
deque.getFirst(e);]
#deque的栈方法
deque.push(e); //addFirst
deque.pop(); //removeFirst
deque.peek();//getFirst
q.size();
q.isEmpty();
你只能使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
进阶:
你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n) ,即使其中一个操作可能花费较长时间。
不管是用栈实现队列还是用队列是实现栈,都可以用双栈/双队列的方式。每个元素最多进站出站两次,所以均摊时间复杂度为O(1);
每次 pop 或peek 时,若输出栈为空则将输入栈的全部数据依次弹出并压入输出栈,这样输出栈从栈顶往栈底的顺序就是队列从队首往队尾的顺序
class MyQueue {
private Deque in;
private Deque out;
/** Initialize your data structure here. */
public MyQueue() {
in=new LinkedList<Integer>();
out=new LinkedList<Integer>();
}
/** Push element x to the back of queue. */
public void push(int x) {
in.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
if(out.isEmpty())
{
while(!in.isEmpty())
{
out.push(in.pop());
}
}
return (int)out.pop();
}
/** Get the front element. */
public int peek() {
if(out.isEmpty())
{
while(!in.isEmpty())
{
out.push(in.pop());
}
}
return (int)out.peek();
}
/** Returns whether the queue is empty. */
public boolean empty() {
return in.isEmpty()&&out.isEmpty();
}
}
队列是先进先出的规则,把一个队列中的数据导入另一个队列中,数据的顺序并没有变,并有变成先进后出的顺序,,所以用栈实现队列, 和用队列实现栈的思路还是不一样的.思路有两种,一种是每次push时放到队头,一种是每次pop时先把队伍前面部分转移到备份队列,把队尾弹出,再从备份队列转移回来。
class MyStack {
private Deque que1;
private Deque que2; //备份队列
/** Initialize your data structure here. */
public MyStack() {
que1=new LinkedList<Integer>();
que2=new LinkedList<Integer>();
}
/** Push element x onto stack. */
public void push(int x) { //将放到队尾改为放到队首
que2.addLast(x); //刚push进来的元素放备份队列到队首
while(!que1.isEmpty()) //原本的元素放到备份队列的队首后面
{
que2.addLast(que1.removeFirst());
}
Deque tmp=que1; //交换que1和备份队列,备份队列为空,que1队首是刚刚进来的元素,出队出的是刚刚进来的元素。
que1=que1;
que2=tmp;
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {
return (int)que1.removeFirst();
}
/** Get the top element. */
public int top() {
return (int)que1.getFirst();
}
/** Returns whether the stack is empty. */
public boolean empty() {
return que1.isEmpty();
}
}
匹配问题都是栈的强项
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
1.遇到右括号但栈为空或栈顶括号不匹配
2.已经没有右括号但栈内剩余左括号
class Solution {
public boolean isValid(String s) {
Deque stack= new LinkedList<Character>();
for(int i=0;i<s.length();i++)
{
Character c=s.charAt(i);
if(c=='{'||c=='['||c=='(')
{
stack.push(c);
}
else if(c=='}')
{
if(stack.isEmpty()||(char)stack.pop()!='{')return false;
}
else if(c==']')
{
if(stack.isEmpty()||(char)stack.pop()!='[')return false;
}
else if(c==')')
{
if(stack.isEmpty()||(char)stack.pop()!='(')return false;
}
}
if(!stack.isEmpty())return false;
return true;
}
}
输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。"对对碰“
class Solution {
public String removeDuplicates(String S) {
Deque stack=new LinkedList<Character>();
for(int i=0;i<S.length();i++)
{
if(!stack.isEmpty()&&S.charAt(i)==(char)stack.peek())
{
stack.pop();
}
else
stack.push(S.charAt(i));
}
StringBuilder sb=new StringBuilder();
while(!stack.isEmpty())
{
sb.append(stack.removeLast());
}
return sb.toString();
}
}
输入: ["2", "1", "+", "3", "*"]
输出: 9
解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
思路:遇到数字入栈,遇到操作符弹出栈顶的两个操作数计算结果后再将结果入栈,最终栈中剩下的一个数字是结果。
class Solution {
public int evalRPN(String[] tokens) {
Deque numbers=new LinkedList<Integer>();
for(String token:tokens)
{
if(token.equals("+")||token.equals("-")||token.equals("*")||token.equals("/"))
{
int b=(int)numbers.pop(),a=(int)numbers.pop();
int result=cal(a,b,token);
numbers.push(result);
}
else
{
numbers.push(Integer.valueOf(token));
}
}
return (int)numbers.pop();
}
public int cal(int a,int b,String opt)
{
switch(opt)
{
case "+":
return a+b;
case "-":
return a-b;
case "*":
return a*b;
case "/":
return a/b;
default:
return 0;
}
}
}
单调栈
单调栈主要用来解决找到比当前元素更大/小的前一个/后一个元素的问题
单调栈优化了for循环暴力求解的时间效率,因为每个元素只会进栈出栈一次,所以时间复杂度是O(n)。
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
对每一个柱子找到其左边和右边第一个比它低的,此柱子高度*左右下标范围=此高度下矩形的最大面积。
class Solution {
public int largestRectangleArea(int[] heights) {
//对每一个柱子找到其左边和右边第一个比它低的,此柱子高度*左右下标范围=此高度下矩形的最大面积。
//添加哨兵,在heights最后补0,强迫最后栈内元素全部出栈,在最前补0,不用额外判断栈pop了一次之后为空的情况(说明栈顶柱子的高度是所有柱子里最低的)。
int[] new_heights = new int[heights.length + 2];
for (int i = 1; i < heights.length + 1; i++) {
new_heights[i] = heights[i - 1];
}
int maxArea= 0;
Deque<Integer> stack=new LinkedList<>();
for(int i=0;i<new_heights.length;i++)
{
while(!stack.isEmpty()&&new_heights[stack.peek()]>new_heights[i])
{
int cur=stack.pop();
int left=stack.peek()+1;//矩形左边界,当前栈顶就是左边第一个比出栈元素低的柱子
int right=i-1;//矩形右边界,i是右边第一个比出栈元素低的柱子的下标
int width=right-left+1;
maxArea=Math.max(maxArea,new_heights[cur]*width);
}
stack.push(i);
}
return maxArea;
}
}
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
找到每个元素左右第一个比它高的柱子,水坑的高度就是左右两边更低的一边减去底部,宽度是在左右中间
class Solution {
public int trap(int[] height) {
int result=0;
if(height.length<=2)return result;
Deque<Integer> stack= new LinkedList<>();
for(int i=0;i<height.length;i++)
{
while(!stack.isEmpty()&&height[stack.peek()]<=height[i]) //注意栈内放的是下标!!!!
{
int cur=stack.pop();
if(stack.isEmpty())
{
break;
}
int left=stack.peek()+1;
int right =i-1;
int width=right-left+1;
int h=Math.min(height[stack.peek()],height[i])-height[cur];
result+=width*h;
}
stack.push(i);
}
return result;
}
}
请根据每日 气温
列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0
来代替。
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73]
,你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]
。
找到每日右边第一个大于该日气温的日子,注意栈内存的是下标,要用T[stack.peek()]
class Solution {
public int[] dailyTemperatures(int[] T) {
int[] result =new int[T.length];
Deque<Integer> stack=new LinkedList<>();
for(int i=0;i<T.length;i++)
{
while(!stack.isEmpty()&&T[stack.peek()]<T[i])
{
int left=stack.pop();
result[left]=i-left;
}
stack.push(i);
}
return result;
}
}
单调队列
一个序列只有头尾的数据有变动,需要求该序列的最大值或最小值,可以尝试使用单调队列,常见题型是滑动窗口类
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
示例 1:
输入: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
本题需要借助单调队列,单调队列是单调递增或单调递减的队列,它不是一种固有的数据结构,需要我们自己维护。
- 本题是单调递减队列,这样队首元素就是最大值。要维护单调递减队列,若队尾元素<=当前元素就出队,然后再把当前元素入队,这样就保证了单调递减。
- 单调队列中只放窗口内部的元素,因此每次移动窗口后都要判断队头元素的下标还在不在窗口范围内了,不在就移出队列,在就是当前窗口最大值。
- 单调队列的出现可以简化问题,队首元素便是最大(小)值,这样,选取最大(小)值的复杂度便为o(1),由于队列的性质,每个元素入队一次,出队一次,维护队列的复杂度均摊下来便是o(1)。排序自然想到用优先队列,本题当然可以用优先队列,但优先队列的时间复杂度更高,每个元素插入优先队列复杂度为o(logn);
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
Deque deque=new LinkedList<Integer>();
int n=nums.length;
int ans[]=new int[n-k+1];
int j=0; //ans 的下标
for(int i=0;i<k;i++)
{
while(!deque.isEmpty()&&nums[(int)deque.getLast()]<=nums[i])
{
deque.removeLast();
}
deque.addLast(i);
}
ans[j++]=nums[(int)deque.getFirst()];
for(int i=k;i<n;i++)
{
// 保证从大到小 如果前面数小则需要依次弹出,直至满足要求
while(!deque.isEmpty()&&nums[(int)deque.getLast()]<=nums[i])
{
deque.removeLast();
}
deque.addLast(i);
// 判断当前队列中队首的值是否有效
while((int)deque.getFirst()<=i-k)
deque.removeFirst();
ans[j++]=nums[(int)deque.getFirst()];
}
return ans;
}
}
优先队列
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
你可以按任意顺序返回答案。
class Solution {
public int[] topKFrequent(int[] nums, int k) {
//统计每个元素的频率
Map<Integer,Integer> hashMap=new HashMap<>();
for(int i=0;i<nums.length;i++)
{
if(hashMap.containsKey(nums[i]))
hashMap.put(nums[i],hashMap.get(nums[i])+1);
else
hashMap.put(nums[i],1);
}
//最小堆 队首是最小的队尾最大
//重写Comparator开始顺序是a,b 若返回值>0则交换ab否则不交换,因此升序是a-b,降序是b-a
PriorityQueue<Integer> queue=new PriorityQueue<>(new Comparator<Integer>(){
@Override
public int compare(Integer a,Integer b)
{
return hashMap.get(a)-hashMap.get(b);
}
});
//把优先队列大小维持在k,大于k了就移除一个队首频率最小的元素
//复杂度Onlogk ,插入大小为k的堆复杂度Ologk
for(Integer key:hashMap.keySet())
{
if(queue.size()<k)
{
queue.add(key);
}
else
{
if(hashMap.get(queue.peek())<hashMap.get(key))
{
queue.remove();
queue.add(key);
}
}
}
List<Integer> res=new ArrayList<>();
int result[]=new int[k];
int j=0;
while(!queue.isEmpty())
{
result[j++]=queue.remove();
}
return result;
}
}
方法二 利用快速排序中的分治之的分,找到第n-k大,其后面所有元素就是前k大。
不明白partition时为什么一定要while l<=r ,swap(left,r)??????a
class Solution {
Random random=new Random();
Map<Integer,Integer> hashMap= new HashMap<>();
public int[] topKFrequent(int[] nums, int k) {
for(int num:nums)
{
if(hashMap.containsKey(num))
hashMap.put(num, hashMap.get(num)+1);
else
hashMap.put(num,1);
}
int [] elements=new int[hashMap.keySet().size()];
int i=0;
for(Integer a:hashMap.keySet())
{
elements[i++]=a;
}
int l=0,r=elements.length-1,target=elements.length-k;
while(true)
{
int index=partition(elements, l, r);
if(index==target)
{
return Arrays.copyOfRange(elements, index, elements.length);
}
else if(index <target)l=index+1;
else r=index-1;
}
}
public int partition(int []nums,int left,int right)
{
int rand=random.nextInt(right-left+1)+left;
swap(nums,left,rand);
int l=left+1,r=right,pivot=hashMap.get(nums[left]);
while(l<=r)
{
if(hashMap.get(nums[l])<=pivot){
l++;
continue;
}
if(hashMap.get(nums[r])>=pivot){
r--;
continue;
}
swap(nums,l,r);
l++;
r--;
}
swap(nums,left,r);
return r;
}
public void swap(int [] nums,int a,int b)
{
int temp=nums[a];
nums[a]=nums[b];
nums[b]=temp;
}
}
class Solution {
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> minHeap=new PriorityQueue<>(new Comparator<Integer>(){
@Override
public int compare(Integer a,Integer b)
{
return a-b;
}
});
for(int i=0;i<nums.length;i++)
{
if(minHeap.size()<k)
{
minHeap.add(nums[i]);
}
else
{
if(minHeap.peek()<nums[i])
{
minHeap.remove();
minHeap.add(nums[i]);
}
}
}
return minHeap.peek();
}
}