建议代码随想录至少二刷
文章目录
- 栈
- [232. 用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/)
- [225. 用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/)
- [20. 有效的括号](https://leetcode.cn/problems/valid-parentheses/)
- [1047. 删除字符串中的所有相邻重复项](https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/)
- [150. 逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation/) 后缀转中缀(不含括号)
- 队列
栈
232. 用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push
、pop
、peek
、empty
):
实现 MyQueue
类:
void push(int x)
将元素 x 推到队列的末尾int pop()
从队列的开头移除并返回元素int peek()
返回队列开头的元素boolean empty()
如果队列为空,返回true
;否则,返回false
说明:
- 你 只能 使用标准的栈操作 —— 也就是只有
push to top
,peek/pop from top
,size
, 和is empty
操作是合法的。 - 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
代码
在push数据的时候,只要数据放进输入栈就好,但在pop的时候,操作就复杂一些,输出栈如果为空,就把进栈数据全部导入进来(注意是全部导入),再从出栈弹出数据,如果输出栈不为空,则直接从出栈弹出数据就可以了。
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() {
dumpstackIn();
return stackOut.pop();//删除此堆栈顶部的对象,并将该对象作为此函数的值返回。
}
public int peek() {
dumpstackIn();
return stackOut.peek();//查看此堆栈顶部的对象,而不将其从堆栈中删除。
}
public boolean empty() {
return stackIn.isEmpty() && stackOut.isEmpty();
}
private void dumpstackIn() {//注意:pop peek都需要用到这个操作,所以写成单独的函数
if (!stackOut.isEmpty())
return;
while (!stackIn.isEmpty()) {
stackOut.push(stackIn.pop());
}
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
225. 用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push
、top
、pop
和 empty
)。
实现 MyStack
类:
void push(int x)
将元素 x 压入栈顶。int pop()
移除并返回栈顶元素。int top()
返回栈顶元素。boolean empty()
如果栈是空的,返回true
;否则,返回false
。
注意:
- 你只能使用队列的标准操作 —— 也就是
push to back
、peek/pop from front
、size
和is empty
这些操作。 - 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
代码
一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时再去弹出元素就是栈的顺序了。
- 可以只用一个队列模拟栈,但是要两个栈才能模拟一个队列
class MyStack {
Queue<Integer> q;
public MyStack() {
q = new LinkedList<Integer>();
}
//这里的reposition移动处理可以放在pop和top中,但是放在push里面更加方便一些
//放在pop和top中可能会出问题,注意处理好
public void push(int x) {
q.add(x);
int size = q.size();
while (size !=1) {
q.offer(q.poll());
size--;
}
}
public int pop() {
return q.poll();
}
public int top() {
return q.peek();
}
public boolean empty() {
return q.isEmpty();
}
}
/**
* Your MyStack object will be instantiated and called as such:
* MyStack obj = new MyStack();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.top();
* boolean param_4 = obj.empty();
*/
20. 有效的括号
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 1:
**输入:**s = “()”
**输出:**true
示例 2:
**输入:**s = “()[]{}”
**输出:**true
示例 3:
**输入:**s = “(]”
**输出:**false
示例 4:
**输入:**s = “([])”
**输出:**true
代码:栈
该入栈 “(” 的时候直接入栈 “)”,方便后序的比较
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (int i = 0; i < s.length(); i++) {
if(s.charAt(i) == '('){
stack.push(')');
}else if(s.charAt(i) == '['){
stack.push(']');
}else if(s.charAt(i) == '{'){
stack.push('}');
} else if (stack.isEmpty() || stack.peek()!=s.charAt(i)) {
return false;
}else{
stack.pop();
}
}
return stack.isEmpty();
}
}
1047. 删除字符串中的所有相邻重复项
给出由小写字母组成的字符串 s
,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 s
上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
示例:
输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。
代码:栈存放遍历过的元素
在删除相邻重复项的时候,其实就是要知道当前遍历的这个元素,我们在前一位是不是遍历过一样数值的元素,那么如何记录前面遍历过的元素呢?用栈来存放,那么栈的目的,就是存放遍历过的元素,当遍历当前的这个元素的时候,去栈里看一下我们是不是遍历过相同数值的相邻元素。
class Solution {
public String removeDuplicates(String s) {
Stack<Character> stack = new Stack<Character>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (stack.empty() || stack.peek() != c) {
stack.push(c);
}else{
stack.pop();
}
}
StringBuffer sb = new StringBuffer();
while (!stack.empty()) {
sb.append(stack.pop());
}
sb.reverse();//注意栈的输出是倒序
return sb.toString();
}
}
拿字符串直接作为栈,省去了栈还要转为字符串的操作。
class Solution {
public String removeDuplicates(String s) {
StringBuilder res = new StringBuilder();
int top=-1;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if(top==-1 || res.charAt(top) !=c){
res.append(c);
top++;
}else{
res.deleteCharAt(top);
top--;
}
}
return res.toString();
}
}
150. 逆波兰表达式求值 后缀转中缀(不含括号)
给你一个字符串数组 tokens
,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
- 有效的算符为
'+'
、'-'
、'*'
和'/'
。 - 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是 向零截断 。
- 表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用 32 位 整数表示。
示例 1:
输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
代码:栈
逆波兰表达式主要有以下两个优点:
-
去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
-
适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<Integer>();
for (String i : tokens) {
if (i.equals("+")) {
stack.push(stack.pop()+stack.pop());
} else if (i.equals("-")) {//注意操作顺序 从栈里后出来的数在运算符前
stack.push(-stack.pop()+stack.pop());
} else if (i.equals("*")) {
stack.push(stack.pop()*stack.pop());
}else if (i.equals("/")) {
int tmp1 = stack.pop();
int tmp2 = stack.pop();
stack.push(tmp2/tmp1);//注意操作顺序 从栈里后出来的数在运算符前
}else{
stack.push(Integer.parseInt(i));
}
}
return stack.pop();
}
}
队列
- queue
-
boolean
add(E e)
如果可以在不违反容量限制的情况下立即执行此操作,则将指定的元素插入此队列,成功时返回 true
,如果当前没有空间,则抛出IllegalStateException
。E
element()
检索但不删除此队列的头部。 boolean
offer(E e)
如果可以在不违反容量限制的情况下立即执行此操作,则将指定的元素插入此队列。 E
peek()
检索但不删除此队列的头部,如果此队列为空,则返回 null
。E
poll()
检索并删除此队列的头部,如果此队列为空,则返回 null
。E
remove()
检索并删除此队列的头部。
优先队列和单调队列
优先队列具有队列的所有特性,包括基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的
- 队列里的元素一定是要排序的,而且要最大值放在出队口
- 但如果把窗口里的元素都放进队列里,窗口移动的时候,队列需要弹出元素
- 已经排序之后的队列 怎么能把窗口要移除的元素(这个元素可不一定是最大值)弹出呢?
- ------->其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。
- 对于窗口里的元素{2, 3, 5, 1 ,4},单调队列里只维护{5, 4} 就够了,保持单调队列里单调递减,此时队列出口元素就是窗口里最大元素。
239. 滑动窗口最大值
给你一个整数数组 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
暴力解
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
if (n == 0) {
return nums;
}
int result[] = new int[n - k + 1];
for (int i = 0; i < result.length; i++) {
int max = Integer.MIN_VALUE;
for (int j = 0; j < k; j++) {
max = Math.max(max, nums[i + j]);
}
result[i] = max;
}
return result;
}
}
单调队列
//利用双端队列手动实现单调队列
/**
* 用一个单调队列来存储对应的下标,每当窗口滑动的时候,直接取队列的头部指针对应的值放入结果集即可
* 单调队列类似 (tail -->) 3 --> 2 --> 1 --> 0 (--> head) (右边为头结点,元素存的是下标)
*/
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 个高频元素(模板记忆)
给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
代码:哈希统计出现次数,优先队列排序
-
https://leetcode.cn/problems/top-k-frequent-elements/solutions/3049202/347-qian-k-ge-gao-pin-yuan-su-by-timeliu-zdg3/
-
map.merge(num, 1, Integer::sum);
和map.put(num, map.getOrDefault(num, 0) + 1);
的比较:https://www.cnblogs.com/xinyangblog/p/18677672
class Solution {
public int[] topKFrequent(int[] nums, int k) {
//按照出现次数排序
PriorityQueue<int[]> q=new PriorityQueue<>((a,b)->{
if(a[1]!=b[1])return b[1]-a[1];
else return a[0]-b[0];//这里不加也行 直接返回return b[1]-a[1];,字母好像自动按照字典序了
});//自定义 ,后-前 从大到小排序 大根堆 队列最前面是最大的数
//使用map记录每个数字的出现次数
Map<Integer,Integer> map=new HashMap<>();
for (int num : nums) {
map.put(num,map.getOrDefault(num,0)+1);
}
//将数字和出现次数放入优先队列中
for (Integer key : map.keySet()) {
q.add(new int[]{key,map.get(key)});//int a[]{},{}的作用:创建数组的同时直接为数组元素赋值
}
int[] res=new int[k];
//依次推出堆顶元素即可
for (int i = 0; i < k; i++) {
res[i]=q.poll()[0];
}
return res;
}
}
注意:
-
map.put(num,map.getOrDefault(num,0)+1);
-
PriorityQueue<int[]> q=new PriorityQueue<>((a,b)->{
if(a[1]!=b[1])return b[1]-a[1];
else return a[0]-b[0];
}); -
q.add(new int[]{key,map.get(key)});