栈和队列
基本实现
关于java中栈和队列的实现,下面链接有讲到如何选择接口实现与方法。
java中栈和队列的实现
232.用栈实现队列
双栈
class MyQueue {
Stack<Integer> s1;//Vector下的Stack尽量不用
Stack<Integer> s2;
//Deque<Integer> s1;
public MyQueue() {
s1 = new Stack<Integer>();
s2 = new Stack<Integer>();
//s1 = new ArrayDeque();
}
public void push(int x) {
s1.push(x);
}
public int pop() {
if(s2.empty()){
in2out();
}
return s2.pop();
}
public int peek() {
if(s2.empty()){
in2out();
}
return s2.peek();
}
public boolean empty() {
return s2.empty()&& s1.empty();
}
private void in2out() {
while (!s1.isEmpty()) {
s2.push(s1.pop( ));
}
}
}
225. 用队列实现栈
双栈
class MyStack {
Deque<Integer> queue1;
Deque<Integer> queue2;
public MyStack() {
queue1 = new LinkedList();
queue2 = new LinkedList();
}
public void push(int x) {
queue1.offer(x);
}
public int pop() {
int length = queue1.size();
for(int i = 0 ; i< length-1 ; i++){
int t = queue1.pop();
queue2.offer(t);
}
int t = queue1.pop();
Deque<Integer> temp = queue1;
queue1 = queue2;
queue2 = temp;
return t;
}
public int top() {
int t = 0;
int length = queue1.size();
for(int i = 0 ; i< length ; i++){
t = queue1.pop();
queue2.offer(t);
}
Deque<Integer> temp = queue1;
queue1 = queue2;
queue2 = temp;
return t;
}
public boolean empty() {
return queue1.isEmpty();
}
}
单栈实现
class MyStack {
Deque<Integer> queue1;
public MyStack() {
queue1 = new LinkedList();
}
public void push(int x) {
queue1.offer(x);
}
public int pop() {
for(int i =0 ; i< queue1.size()-1; i++){
int t = queue1.pop();
queue1.offer(t);
}
return queue1.pop();
}
public int top() {
int result = 0;
for(int i =0 ; i< queue1.size(); i++){
result = queue1.pop();
queue1.offer(result);
}
return result;
}
public boolean empty() {
return queue1.isEmpty();
}
}
20. 有效的括号
放入括号,条件判断
class Solution {
public boolean isValid(String s) {
char[] c = s.toCharArray();
Deque<Character> stack = new ArrayDeque();
for(int i = 0 ; i < c.length; i++){
if(c[i] == '(' || c[i] == '{' || c[i] == '[' ){
stack.push(c[i]);
}else if( stack.isEmpty() ||
!( (c[i] == ')' && stack.poll() == '(') ||
( c[i] == '}' && stack.poll() == '{' ) ||
( c[i] == ']' && stack.poll() == '[' ) ))
{
return false;
}
}
return stack.isEmpty();
}
}
优化代码易懂
class Solution {
public boolean isValid(String s) {
Deque<Character> deque = new LinkedList<>();
char ch;
for (int i = 0; i < s.length(); i++) {
ch = s.charAt(i);
//碰到左括号,就把相应的右括号入栈
if (ch == '(') {
deque.push(')');
}else if (ch == '{') {
deque.push('}');
}else if (ch == '[') {
deque.push(']');
} else if (deque.isEmpty() || deque.peek() != ch) {
return false;
}else {//如果是右括号判断是否和栈顶元素匹配
deque.pop();
}
}
//最后判断栈中元素是否匹配
return deque.isEmpty();
}
}
1047. 删除字符串中的所有相邻重复项
利用栈(19ms)
class Solution {
public String removeDuplicates(String s) {
char[] c = s.toCharArray();
Deque<Character> stack = new ArrayDeque();
for(int i = 0; i< c.length; i++){
if(stack.isEmpty() || c[i] != stack.peek()){
stack.push(c[i]);
}else{
stack.pop();
}
}
StringBuilder result = new StringBuilder();
for(Character t : stack){
result.append(t);
}
return result.reverse().toString();
}
}
双指针(5ms)
class Solution {
public String removeDuplicates(String s) {
char[] ch = s.toCharArray();
int fast = 0;
int slow = 0;
while(fast < s.length()){
// 直接用fast指针覆盖slow指针的值
ch[slow] = ch[fast];
// 遇到前后相同值的,就跳过,即slow指针后退一步,下次循环就可以直接被覆盖掉了
if(slow > 0 && ch[slow] == ch[slow - 1]){
slow--;//两个元素都消失,slow将在下轮循环被fast替代
}else{
slow++;
}
fast++;
}
return new String(ch,0,slow);
}
}
150. 逆波兰表达式求值
利用栈
class Solution {
public int evalRPN(String[] tokens) {
Deque<Integer> stack = new ArrayDeque();
int result = 0;
for(int i = 0; i< tokens.length; i++){
if(tokens[i].equals("+")){
int num1 = stack.poll();
int num2 = stack.poll();
stack.push(num2+num1);
}else if(tokens[i].equals("-")){
int num1 = stack.poll();
int num2 = stack.poll();
stack.push(num2-num1);
}else if(tokens[i].equals("*") ){
int num1 = stack.poll();
int num2 = stack.poll();
stack.push(num2*num1);
}else if(tokens[i].equals("/")){
int num1 = stack.poll();
int num2 = stack.poll();
stack.push(num2/num1);
}else{
stack.push(Integer.parseInt(tokens[i]));
}
}
return stack.poll();
}
}
优化
class Solution {
public int evalRPN(String[] tokens) {
Deque<Integer> stack = new LinkedList();
for (String s : tokens) {
if ("+".equals(s)) {
stack.push(stack.pop() + stack.pop());
} else if ("-".equals(s)) {
stack.push(-stack.pop() + stack.pop());
} else if ("*".equals(s)) {
stack.push(stack.pop() * stack.pop());
} else if ("/".equals(s)) {
int temp1 = stack.pop();
int temp2 = stack.pop();
stack.push(temp2 / temp1);
} else {
stack.push(Integer.valueOf(s));
}
}
return stack.pop();
}
}
239. 滑动窗口最大值(Hard)
利用单调队列
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
int[] result = new int[n-k+1];
Deque<Integer> que = new ArrayDeque();
for(int i = 0; i< n; i++){
//每轮移走最前面的一个
//如果要移除的数字,是队列头也就是最大值,则移走
if( i >= k && nums[i-k] == que.peek()){
que.poll();
}
//添加新元素,从后面遍历,确保队列单调
while(!que.isEmpty() && nums[i]>que.getLast()){
que.removeLast();
}
que.offer(nums[i]);
//如果前k个已完成
if(i >= k-1){
result[i-k+1] = que.peek();
}
}
return result;
}
}
单调队列
输出过程
自己定义队列
分两个循环走,一样
//解法一
//自定义数组
class MyQueue {
Deque<Integer> deque = new LinkedList<>();
//弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
//同时判断队列当前是否为空
void poll(int val) {
if (!deque.isEmpty() && val == deque.peek()) {
deque.poll();
}
}
//添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出
//保证队列元素单调递减
//比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2
void add(int val) {
while (!deque.isEmpty() && val > deque.getLast()) {
deque.removeLast();
}
deque.add(val);
}
//队列队顶元素始终为最大值
int peek() {
return deque.peek();
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums.length == 1) {
return nums;
}
int len = nums.length - k + 1;
//存放结果元素的数组
int[] res = new int[len];
int num = 0;
//自定义队列
MyQueue myQueue = new MyQueue();
//先将前k的元素放入队列
for (int i = 0; i < k; i++) {
myQueue.add(nums[i]);
}
res[num++] = myQueue.peek();
for (int i = k; i < nums.length; i++) {
//滑动窗口移除最前面的元素,移除是判断该元素是否放入队列
myQueue.poll(nums[i - k]);
//滑动窗口加入最后面的元素
myQueue.add(nums[i]);
//记录对应的最大值
res[num++] = myQueue.peek();
}
return res;
}
}
不存数值存下表
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
ArrayDeque<Integer> deque = new ArrayDeque<>();
int n = nums.length;
int[] res = new int[n - k + 1];
int idx = 0;
for(int i = 0; i < n; i++) {
// 根据题意,i为nums下标,是要在[i - k + 1, i] 中选到最大值,只需要保证两点
// 1.队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出
while(!deque.isEmpty() && deque.peek() < i - k + 1){
deque.poll();
}
// 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offer(i);
// 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了
if(i >= k - 1){
res[idx++] = nums[deque.peek()];
}
}
return res;
}
}
347.前 K 个高频元素
PriorityQueue
- 基于优先级堆的无界优先级queue 。 优先级队列的元素根据其natural ordering或队列构造时提供的
Comparator
进行排序 ,具体取决于使用的构造函数。 优先级队列不允许null
元素。 依赖于自然排序的优先级队列也不允许插入不可比较的对象(这样做可能导致ClassCastException
)。
PriorityQueue(Comparator<? super E> comparator) 创建具有默认初始容量的 PriorityQueue ,其元素根据指定的比较器进行排序。
Comparator接口
/*Comparator接口说明:
* 返回负数,形参中第一个参数排在前面;返回正数,形参中第二个参数排在前面
* 对于队列:排在前面意味着往队头靠
* 对于堆(使用PriorityQueue实现):从队头到队尾按从小到大排就是最小堆(小顶堆),
* 从队头到队尾按从大到小排就是最大堆(大顶堆)--->队头元素相当于堆的根节点
* */
普通实现
//根据出现次数排序
Collections.sort(newlist, new Comparator<Map.Entry<Integer, Integer>>() {
@Override
public int compare(Map.Entry<Integer, Integer> o1, Map.Entry<Integer, Integer> o2) {
//降序
return (int) o2.getValue() - o1.getValue();
}
});
函数式编程
//函数式编程comparator,降序
Collections.sort(newlist, (o1,o2)-> o2.getValue() - o1.getValue());
1.运用HashMap和List
HashMap存数值和其出现的次数,然后将键值对放到List中根据次数排序,输出前k个数值
时间复杂度O(N*logN),排序的复杂度,12ms-15ms(11% - 85%)
当数组长度小于7时,只走插入排序O(n^2),否则执行归并排序O(nlogn)。
class Solution {
public int[] topKFrequent(int[] nums, int k) {
//value,times
Map<Integer,Integer> map = new HashMap<>();
//getOrDefault替代判断
for(int i = 0; i< nums.length;i++){
map.put(nums[i],map.getOrDefault(nums[i],0)+1);
}
//Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
//所有键值对放在list里
List<Map.Entry<Integer, Integer>> newlist = new ArrayList<>(map.entrySet());
//函数式编程comparator
Collections.sort(newlist, (o1,o2)-> o2.getValue() - o1.getValue());
int[] ans = new int[k];
//输出前k个
for(int i =0 ;i<k; i++){
ans[i] = newlist.get(i).getKey();
}
return ans;
}
}
2.运用HashMap和优先队列(堆)
大顶堆
维护所有元素,最后从堆顶便利,类似List, 12ms
//解法1:基于大顶堆实现
public int[] topKFrequent(int[] nums, int k) {
Map<Integer,Integer> map = new HashMap<>();//key为数组元素值,val为对应出现次数
for(int num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
//在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
//出现次数按从队头到队尾的顺序是从大到小排,出现次数最多的在队头(相当于大顶堆)
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2)->pair2[1]-pair1[1]);
for(Map.Entry<Integer,Integer> entry:map.entrySet()){//大顶堆需要对所有元素进行排序
pq.add(new int[]{entry.getKey(),entry.getValue()});
}
int[] ans = new int[k];
for(int i=0;i<k;i++){//依次从队头弹出k个,就是出现频率前k高的元素
ans[i] = pq.poll()[0];
}
return ans;
}
小顶堆
因为要统计最大前k个元素,维护K个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。 12ms,三种算法基本时间复杂度一致。
class Solution {
public int[] topKFrequent(int[] nums, int k) {
//value,times
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0; i< nums.length;i++){
map.put(nums[i],map.getOrDefault(nums[i],0)+1);
}
//在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
//出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
PriorityQueue<int[]> que = new PriorityQueue<>((o1,o2)->o1[1]-o2[1]);
for(Map.Entry<Integer, Integer> t : map.entrySet()){//小顶堆只需要维持k个元素有序
if(que.size()<k){//小顶堆元素个数小于k个时直接加
//que.add() is the same
que.offer(new int[]{t.getKey(),t.getValue()});
}else if(t.getValue() > que.peek()[1]){
{//当前元素出现次数大于小顶堆的根结点(这k个元素中出现次数最少的那个)
//弹出队头(小顶堆的根结点),即把堆里出现次数最少的那个删除,留下的就是出现次数多的了
que.poll();
que.offer(new int[]{t.getKey(),t.getValue()});
}
}
int[] ans = new int[k];
//依次弹出小顶堆,先弹出的是堆的根,出现次数少,后面弹出的出现次数多
for(int i = k-1 ; i >= 0 ; i--){
ans[i] = que.poll()[0];
}
return ans;
}
}