题解均为java
在写栈和队列的题之前,先介绍一下Deque这个接口 ,该接口需要双向链表数据接口来实现,在它的源码注释中,写明了该双向链表的常用的一些方法。
这里面的所有添加,移除,和获取的方法大家可以看到都是双份的,为什么?
这就涉及到操作失败的问题,一旦操作失败,第一列和第三列的方法会抛出异常,而第二列和第四列的方法会返回特殊值,这就是不同。
offerFirst(e) 在链表头插入元素e
offerLast(e) 在链表尾插入元素e
pollFirst 移除链表头首元素e,并返回
pollLast 移除链表尾首元素e,并返回
peekFirst 返回链表头首元素e(不移除)
peekLast 返回链表尾首元素e(不移除)
使用实现了这个接口的实现类,在逻辑上构建栈和队列。
LInkedList实现了该接口(所以linkedlist是双向链表这下知道了吧)
leetcode232. 用栈实现队列
使用栈实现队列的下列操作:
push(x) – 将一个元素放入队列的尾部。
pop() – 从队列首部移除元素。
peek() – 返回队列首部的元素。
empty() – 返回队列是否为空。
思路:
这种肯定是需要多个栈进行配合的,在这里只需要两个栈,一个入栈和一个出栈
入栈负责入,所以进入队列的元素,push到入栈内
出栈负责出,如果出栈为空,那么把入栈使用pop清空,pop的元素全部都push到出栈中来,然后出栈出栈顶元素,如果出栈不为空,直接pop栈顶元素即可。
上面是push(x),pop()和peek()的思路,
如何检查empty?如果入栈和出栈都为空,那么就是队列就是空的,否则队列不为空
这个我偷懒了没写,就先写个思路吧hhhhhhh
225. 用队列实现栈
实现栈也需要两个队列,第一个队列是存储队列,第二个队列是备份队列
思路:
1.入栈操作:将要入栈的元素加入到存储队列队尾
2.empty操作:判断存储队列是否为空
3.出栈操作:将最后一个元素之前的所有元素出队,并按顺序入队到备份队列中,然后将最后一个元素出队,然后将备份队列与存储队列互换
4.top操作:同上,只不过是在获取到栈顶元素(最后一个元素)之后,再将其加入到备份队列中
20. 有效的括号
这个例题在严蔚敏数据结构那本书上栈那一章
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
思路:依次读取字符串将所有左括号入栈,如果读取到右括号,将栈顶元素出栈,如果是该右括号匹配的左括号,继续,如果不是返回false;在遍历完字符串之后,如果栈中还有剩余元素,返回false,否则返回true
class Solution {
public boolean isValid(String s) {
if(s=="") return true;
int length = s.length();
char[] stack = new char[length];
int index = 0;
for(int i=0;i<length;i++){
char c = s.charAt(i);
if(c=='('||c=='{'||c=='['){
stack[index] = c;
index++;
}else if((c==')'&&index>0&&stack[index-1]=='(')||
(c==']'&&index>0&&stack[index-1]=='[')||
(c=='}'&&index>0&&stack[index-1]=='{')){
index--;
}else{
return false;
}
}
return index==0?true:false;
}
}
1047. 删除字符串中的所有相邻重复项
给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
示例:
输入:“abbaca”
输出:“ca”
解释:
例如,在 “abbaca” 中,我们可以删除 “bb” 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 “aaca”,其中又只有 “aa” 可以执行重复项删除操作,所以最后的字符串为 “ca”。
这个思路跟上个题相同,只不过是每个字母入栈前都要检查一遍栈顶元素是否与要入栈的元素相同
class Solution {
public String removeDuplicates(String S) {
Stack<Character> stack = new Stack();
char[] chars = S.toCharArray();
for (int i=0;i<chars.length;i++){
if (stack.size()!=0&&chars[i]==stack.peek()){
stack.pop();
}else {
stack.push(chars[i]);
}
}
char[] newChars = new char[stack.size()];
for (int i=stack.size()-1;i>=0;i--){
newChars[i] = stack.pop();
}
return String.valueOf(newChars);
}
}
150. 逆波兰表达式求值
经典问题
逆波兰表达式:是一种后缀表达式,所谓后缀就是指算符写在后面。适合用栈去操作
思路:如果读取到的是数,那么直接入栈,如果运算符,栈顶元素出栈作为int2,栈顶元素再出栈作为int1,计算int1 计算符 int2,然后将计算结果入栈
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack();
for (int i=0;i<tokens.length;i++){
switch (tokens[i]){
case "+":
stack.push(stack.pop()+stack.pop());
break;
case "-":
int a = stack.pop();
stack.push(stack.pop()-a);
break;
case "*":
stack.push(stack.pop()*stack.pop());
break;
case "/":
int b = stack.pop();
stack.push(stack.pop()/b);
break;
default:
stack.push(Integer.valueOf(tokens[i]));
break;
}
}
return stack.pop();
}
}
239. 滑动窗口最大值
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
进阶:你能在线性时间复杂度内解决此题吗?
单调队列,即单调递减或单调递增的队列。在这个题中,我们需要维护一个单调最大队列
在这个单调最大队列中,为了符合我们的题目需求,我要规定:
入队列:在元素进入队列的时候,要从队尾到队首检查元素,只要是比这个元素小的(值相等的不要移除),全部移除出队列
出队列:判断队首元素是否等于要出队的元素,如果等于,移除队首元素
因为对队首和队尾都要操作,实际上并不是一个队列hhh,使用deque实现这个数据结构
思路:每一次滑动窗口:首先对进入的第i个元素进行入队列操作,然后对第i-k个元素进行出队列操作,然后获取队首元素作为这次滑动窗口的结果。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int[] result = new int[nums.length-k+1];
Deque<Integer> queue = new LinkedList<Integer>();
for (int i=0;i<k;i++){
while(queue.size()!=0&&queue.peekLast()<nums[i]){
queue.pollLast();
}
queue.offerLast(nums[i]);
}
result[0] = queue.peekFirst();
for (int i=k;i<nums.length;i++){
if (nums[i-k]==queue.peekFirst()){
queue.pollFirst();
}
while(queue.size()!=0&&queue.peekLast()<nums[i]){
queue.pollLast();
}
queue.offerLast(nums[i]);
result[i-k+1] = queue.peekFirst();
}
return result;
}
}
347.前 K 个高频元素
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
思路:
这道题,可以用堆去做,维护一个大小为k的小顶堆,然后再有一个map key为nums中元素的值,value是出现的次数。先计算这个map,然后让map中的数据依次进入该小顶堆,如果堆大小已经为k,然后此时map中数据想要进来,先和小顶堆顶部的元素的出现次数进行比较,如果比堆顶元素小,那继续遍历map,如果比堆顶元素大,堆顶元素出堆,该元素入堆
在java中,PriorityQueue实际上就是一个堆的实现类,然后创造大顶堆小顶堆的方法就是comparable函数里面具体怎么写
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer,Integer> map = new HashMap<>();
for (int num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return map.get(a) - map.get(b);
}
});
for (Integer key:map.keySet()){
if (queue.size()<k){
queue.offer(key);
}else{
if (map.get(key)>map.get(queue.peek())){
queue.poll();
queue.offer(key);
}
}
}
int[] res = new int[k];
int i=0;
while(queue.size()!=0){
res[i] =queue.poll();
i++;
}
return res;
}
}