栈和队列
Leetcode 232. 用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push
、pop
、peek
、empty
):
实现 MyQueue
类:
-
void push(int x)
将元素 x 推到队列的末尾 -
int pop()
从队列的开头移除并返回元素 -
int peek()
返回队列开头的元素 -
boolean empty()
如果队列为空,返回true
;否则,返回false
-
思路:用两个栈来模拟队列,入栈和入队的操作是一样的。出队时,因为要出的元素是先入栈的元素(在栈底),所以用一个栈翻转过来。
class MyQueue {
Stack<Integer> stackIn;
Stack<Integer> stackOut;
public MyQueue() {
stackIn = new Stack<>();
stackOut = new Stack<>();
}
public void push(int x) {
stackIn.push(x);
}
public int pop() {
if (!stackOut.isEmpty()){
return stackOut.pop();
}
else{
while(!stackIn.isEmpty()){
stackOut.push(stackIn.pop());
}
return stackOut.pop();
}
}
public int peek() {
if (stackOut.isEmpty()){
while(!stackIn.isEmpty()){
stackOut.push(stackIn.pop());
}
}
if(empty()) return -1;
else{
return stackOut.peek();
}
}
public boolean empty() {
if (stackOut.isEmpty() && stackIn.isEmpty()){
return true;
}
return false;
}
}
Leetcode 225. 用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push
、top
、pop
和 empty
)。
实现 MyStack
类:
void push(int x)
将元素 x 压入栈顶。int pop()
移除并返回栈顶元素。int top()
返回栈顶元素。boolean empty()
如果栈是空的,返回true
;否则,返回false
。
class MyStack {
Queue<Integer> que1;
public MyStack() {
que1 = new LinkedList<>();
}
public void push(int x) {
que1.offer(x);
int size = que1.size();
while(size-- > 1){
que1.offer(que1.poll());
}
}
public int pop() {
return que1.poll();
}
public int top() {
return que1.peek();
}
public boolean empty() {
return que1.isEmpty();
}
}
Leetcode 20. 有效的括号
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
- 一开始的思路:
- 想到用栈来解决了,遇到左括号进栈,右括号出栈,但是怎么判断对应左右括号呢?我没有想到
- 思路:遇到
'('
入栈')'
,遇到'{'
入栈'}'
,遇到'['
,入栈']'
),因为只能(){}[]这样匹配,({)}这种情况不是有效的括号哦。所以判断新字符时:- 入栈
- 和栈顶相同,栈顶出栈
- 和栈顶不同,则括号不匹配
- 到最后看栈是否为空,不空则不匹配
class Solution {
public boolean isValid(String s) {
char[] chars = s.toCharArray();
Stack<Character> stack= new Stack<>();
for(int i = 0; i < chars.length; i++){
if(chars[i] == '('){
stack.push(')');
}
else if(chars[i] == '['){
stack.push(']');
}
else if(chars[i] == '{'){
stack.push('}');
}else if (stack.isEmpty() || stack.peek() != chars[i]) {
return false;}
else {//如果是右括号判断是否和栈顶元素匹配
stack.pop();
}
}
return stack.isEmpty();
}
}
Leetcode 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.isEmpty() || stack.peek() != chars[i]){
stack.push(chars[i]);
}
else{
stack.pop();
}
}
String str = "";
//剩余的元素即为不重复的元素
while (!stack.isEmpty()) {
str = stack.pop() + str;
}
return str;
}
}
debug半天不知道哪错了,无语,也不知道为什么就对了。注意出栈后字符串的顺序是反的,需要翻转一下。
Leetcode 150. 逆波兰表达式求值
给你一个字符串数组 tokens
,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
- 有效的算符为
'+'
、'-'
、'*'
和'/'
。 - 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是 向零截断 。
- 表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用 32 位 整数表示。
错误:
- 如何判断字符串相等?可以使用equals方法检测两个字符串是否相等。
字符串1.equals(字符串2);
返回结果为boolean;字符串相等为true,不相等为false。 - 一定不要使用==运算符检测两个字符串是否相等!
这个运算符只能够确定两个字符串是否存放在同一个位置上。 - 如何将字符串String转换为整型Int?使用Integer.valueOf(String)方法。待转换字符串的内容必须为纯数字。 如果待转字符串中出现了除数字以外的其他字符,则程序会抛出异常。Integer.parseInt(String)方法的性能更胜一筹。
- 减或者除需要调换顺序,因为被减数or被除数是后出栈的。
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
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();
}
}
Leetcode 239. 滑动窗口最大值
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
输入: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
一刷理解思路:
- 滑动窗口移动的时候仿佛一个队列,队尾pop队头push。该队列是一个单调队列,队头位置存储的永远是当前滑动窗口中的最大值。
- 入队时比较队列中的值,若是比队尾的值小,则直接入队,若是比队尾的值大,则把队尾poll出来,继续比较,直到队列为空or队尾较大, 这样保证了队首元素永远是滑动窗口中最大的元素
- 出队有两种情况,一种是入队的值较大把前面元素卷出去了,另一种是当队首的值等于滑动窗口滑掉的值时,把该值从队列中pop出去。
class myDeque{
Deque<Integer> deque = new LinkedList<>();
void poll(int val) {
//弹出元素时,要判断队列当前是否为空,当队首的值等于滑动窗口滑掉的值时,把该值从队列中pop出去
if (!deque.isEmpty() && val == deque.peek()) {
deque.poll();
}
}
void push(int val){
//添加元素时,首先判断和队尾的元素大小,如果队尾较大则直接进去;
//如果队尾较小,则把队尾poll出来,继续比较,直到队列为空or队尾较大
// 这样保证了队首元素永远是滑动窗口中最大的元素
while (!deque.isEmpty() && val > deque.getLast()) {
deque.removeLast();
}
deque.add(val);
}
int getMaxValue() {
return deque.peek();
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int len = nums.length - k + 1;
int max[] = new int[len];
int num = 0;
myDeque myque = new myDeque();
for (int i = 0; i < k; i++) {
myque.push(nums[i]);
}
max[num++] = myque.getMaxValue();
for(int i = k; i < nums.length; i++){
myque.poll(nums[i - k]);
myque.push(nums[i]);
max[num++] = myque.getMaxValue();
}
return max;
}
}
Leetcode 347. 前 K 个高频元素
给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
-
大顶堆or小顶堆非常适合在大量数据中求前k个元素。
-
为什么使用小顶堆?堆pop的时候是pop的堆顶元素,如果是大顶堆的话就把堆顶最大的元素pop出去了。如果是小顶堆的话每次pop最小的元素,剩下的就是大的元素。
-
时间复杂度:nlogk。n是遍历nums数组,logk是维护小顶堆。
-
hashmap.getOrDefault(Object key, V defaultValue)
-
getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。key - 键,defaultValue - 当指定的key并不存在映射关系中,则返回的该默认值
-
用优先级队列实现小顶堆
-
入堆时如果堆中个数少于k则直接入堆,若大于k,判断入堆的二元组value与堆顶二元组的value大小,堆顶小则出堆塞进去新入堆的。
-
最后倒序pop到数组中。
class Solution {
public int[] topKFrequent(int[] nums, int k) {
//用hashmap对应数值和出现次数。key-value模式
//key为数组元素值,val为对应出现次数
Map<Integer,Integer> map = new HashMap<>();
for(int num: nums){
//getOrDefault(num,0):寻找num在hashmap中对应的值,若是没有,则返回0,若是有则返回对应value
map.put(num,map.getOrDefault(num,0)+1);
}
//在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
//出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]);
for(Map.Entry<Integer,Integer> entry:map.entrySet()){//小顶堆只需要维持k个元素有序
if(pq.size()<k){//小顶堆元素个数小于k个时直接入堆
pq.add(new int[]{entry.getKey(),entry.getValue()});
}else{
if(entry.getValue()>pq.peek()[1]){//当前元素出现次数大于小顶堆的根结点(这k个元素中出现次数最少的那个)
pq.poll();//弹出队头(小顶堆的根结点),即把堆里出现次数最少的那个删除,留下的就是出现次数多的了
pq.add(new int[]{entry.getKey(),entry.getValue()});
}
}
}
int[] ans = new int[k];
for(int i=k-1;i>=0;i--){//依次弹出小顶堆,先弹出的是堆的根,出现次数少,后面弹出的出现次数多
ans[i] = pq.poll()[0];
}
return ans;
}
}
-
pair的用法
@Test public void TestPair() { Pair<String,String> pair = Pair.of("left","right"); System.out.println("left = " + pair.getLeft()); System.out.println("right = " + pair.getRight()); System.out.println("key = " + pair.getKey()); System.out.println("value = " + pair.getValue()); Pair<String,String> mutablePair = new MutablePair<>("left","right");
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-otmlw34b-1677853809817)(C:\Users\wsco24\AppData\Roaming\Typora\typora-user-images\image-20230303221018528.png)]
上述结果可以看出,pair的左为key,右为value。源码如下:
// 这里的获取key其实就是获取getLeft()方法的值 public final L getKey() { return this.getLeft(); } // 这里的获取value 其实就是获取getRight()方法的值 public R getValue() { return this.getRight(); }
-
map.entrySet()返回映射中包含的映射的 Set 视图。
HashMap<Integer, String> sites = new HashMap<>();
// 往 HashMap 添加一些元素
sites.put(1, "Google");
sites.put(2, "Runoob");
sites.put(3, "Taobao");
System.out.println("sites HashMap: " + sites);
// 返回映射关系中 set view
System.out.println("Set View: " + sites.entrySet());
输出:
sites HashMap: {1=Google, 2=Runoob, 3=Taobao}
Set View: [1=Google, 2=Runoob, 3=Taobao]
entrySet() 方法可以与 for-each 循环一起使用,用来遍历迭代 HashMap 中每一个映射项。
for(Entry<String, Integer> entry: numbers.entrySet())
HashMap<String, Integer> numbers = new HashMap<>();
numbers.put("One", 1);
numbers.put("Two", 2);
numbers.put("Three", 3);
for(Entry<String, Integer> entry: numbers.entrySet()) {
System.out.print(entry);
System.out.print(", ");
}
输出:
Entries: One=1, Two=2, Three=3,
优先级队列PriorityQueue
优先级队列,依旧是 顾名思义,优先级队列会自动按照元素的权值排列,也就是说,优先级队列其实有反 先进先出规则
,但是其对外接口依旧是从队头取元素,队尾添加元素,再无其他存取方式,看起来就是一个队列
优先级队列其实是一个 披着队列外皮的堆。
什么是堆?
堆通常可以被看成一个完全二叉树的数组对象,树中的每个节点都不小于(或不大于)其左右孩子的值
- 如果父节点大于左右孩子的值,那么是大顶堆
- 如果父节点小于左右孩子的值,那么是小顶
优先级队列的特点
- 优先级队列里的元素必须实现内部比较器或者外部比较器
- 优先级队列拥有小顶堆的所有特性, 默认是 小顶堆
- 优先级队列不是线程安全的
- 不允许使用 null元素
- 优先级队列本身是无序的,但是取出的元素所排成的序列才是有序序列