代码随想录-暑假算法第五天(栈和队列篇)
1.用栈实现队列
使用栈实现队列的下列操作:
push(x) – 将一个元素放入队列的尾部。
pop() – 从队列首部移除元素。
peek() – 返回队列首部的元素。
empty() – 返回队列是否为空。
示例:
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // 返回 1
queue.pop(); // 返回 1
queue.empty(); // 返回 false
说明:
- 你只能使用标准的栈操作 – 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
- 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
- 假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)。
#算法公开课
《代码随想录》算法视频公开课 (opens new window):栈的基本操作! | LeetCode:232.用栈实现队列 (opens new window),相信结合视频再看本篇题解,更有助于大家对本题的理解。
#思路
这是一道模拟题,不涉及到具体算法,考察的就是对栈和队列的掌握程度。
使用栈来模式队列的行为,如果仅仅用一个栈,是一定不行的,所以需要两个栈一个输入栈,一个输出栈,这里要注意输入栈和输出栈的关系。
题解
import java.util.ArrayDeque;
class MyQueue {
//使用两个栈模拟队列
//用于交换栈里面的元素
ArrayDeque<Integer> s1 ;
//用于向队列尾部添加元素
ArrayDeque<Integer> s2 ;
public MyQueue() {
s1 = new ArrayDeque<>();
s2 = new ArrayDeque<>();
}
public void push(int x) {
s2.push(x);
}
public int pop() {
//只要s1栈里面不为空 那么栈顶第一个元素就是队列的头元素
if(s1.isEmpty()){
//s1为空 那么把s2里面的元素全部添加到s1里面
while(!s2.isEmpty()){
Integer pop = s2.pop();
s1.push(pop);
}
}
//s1不为空
return s1.pop();
}
public int peek() {
//只要s1栈里面不为空 那么栈顶第一个元素就是队列的头元素
if(s1.isEmpty()){
//s1为空 那么把s2里面的元素全部添加到s1里面
while(!s2.isEmpty()){
Integer pop = s2.pop();
s1.push(pop);
}
}
//s1不为空
return s1.peek();
}
public boolean empty() {
if(s1.isEmpty() && s2.isEmpty()){
return true;
}else{
return false;
}
}
}
/**
* 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();
*/
2. 有效的括号
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 注意空字符串可被认为是有效字符串。
示例 1:
- 输入: “()”
- 输出: true
示例 2:
- 输入: “()[]{}”
- 输出: true
示例 3:
- 输入: “(]”
- 输出: false
示例 4:
- 输入: “([)]”
- 输出: false
示例 5:
- 输入: “{[]}”
- 输出: true
题解
import java.util.ArrayDeque;
class Solution {
public boolean isValid(String s) {
//使用栈进行解题
ArrayDeque<Character> arrayDeque = new ArrayDeque<>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
//左 添加到栈里面
if (c == '(') {
arrayDeque.push(')');
}else if(c == '['){
arrayDeque.push(']');
}else if (c == '{'){
arrayDeque.push('}');
}else {
//右 取出栈顶元素和这个右符号进行判断
if( !arrayDeque.isEmpty() && arrayDeque.peek() == c){
arrayDeque.pop();
}else {
return false;
}
}
}
if(arrayDeque.isEmpty()){
return true;
}else{
return false;
}
}
}
3. 删除字符串中的所有相邻重复项
给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
示例:
- 输入:“abbaca”
- 输出:“ca”
- 解释:例如,在 “abbaca” 中,我们可以删除 “bb” 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 “aaca”,其中又只有 “aa” 可以执行重复项删除操作,所以最后的字符串为 “ca”。
提示:
- 1 <= S.length <= 20000
- S 仅由小写英文字母组成。
题解
import java.util.ArrayDeque;
class Solution {
public String removeDuplicates(String s) {
ArrayDeque<Character> arrayDeque = new ArrayDeque<>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if(!arrayDeque.isEmpty() && arrayDeque.peek()!=null && c == arrayDeque.peek()){
//将栈顶的元素移除
arrayDeque.pop();
continue;
}else{
//添加到栈里面
arrayDeque.push(c);
}
}
//最后遍历拼接即可 (要反着拼接)
String result = "";
while(!arrayDeque.isEmpty()){
result = arrayDeque.pop() + result ;
}
return result;
}
}
4. 逆波兰表达式求值
根据 逆波兰表示法,求表达式的值。
有效的运算符包括 + , - , * , / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
整数除法只保留整数部分。 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例 1:
- 输入: [“2”, “1”, “+”, “3”, " * "]
- 输出: 9
- 解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
- 输入: [“4”, “13”, “5”, “/”, “+”]
- 输出: 6
- 解释: 该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:
-
输入: [“10”, “6”, “9”, “3”, “+”, “-11”, " * ", “/”, " * ", “17”, “+”, “5”, “+”]
-
输出: 22
-
解释:该算式转化为常见的中缀算术表达式为:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5 = ((10 * (6 / (12 * -11))) + 17) + 5 = ((10 * (6 / -132)) + 17) + 5 = ((10 * 0) + 17) + 5 = (0 + 17) + 5 = 17 + 5 = 22
是一种后缀表达式,所谓后缀就是指运算符写在后面。
平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
逆波兰表达式主要有以下两个优点:
-
去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
-
适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。
题解
import java.util.ArrayDeque;
class Solution {
public int evalRPN(String[] tokens) {
//使用栈解决问题
ArrayDeque<Integer> arrayDeque = new ArrayDeque<>();
for (String token : tokens) {
//解题思路: 如果是数字就压入栈 是符号就取出栈顶(不仅要拿,还要移除)的两个元素进行计算,然后把计算的结果在压入栈里面
if(token.equals("+")){
int n1 = arrayDeque.pop();
int n2 = arrayDeque.pop();
int number = n1+n2;
arrayDeque.push(number);
}else if(token.equals("-")){
int n1 = arrayDeque.pop();
int n2 = arrayDeque.pop();
//减号和/号要特殊处理
int number = n2-n1;
arrayDeque.push(number);
}else if(token.equals("*")){
int n1 = arrayDeque.pop();
int n2 = arrayDeque.pop();
int number = n1*n2;
arrayDeque.push(number);
}else if(token.equals("/")){
int n1 = arrayDeque.pop();
int n2 = arrayDeque.pop();
int number = n2/n1;
arrayDeque.push(number);
}else {
//是数字
int i = Integer.parseInt(token);
arrayDeque.push(i);
}
}
//返回结果
return arrayDeque.pop();
}
}
5.前 K 个高频元素
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
- 输入: nums = [1,1,1,2,2,3], k = 2
- 输出: [1,2]
示例 2:
- 输入: nums = [1], k = 1
- 输出: [1]
提示:
- 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
- 你的算法的时间复杂度必须优于 O ( n log n ) O(n \log n) O(nlogn) , n 是数组的大小。
- 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
- 你可以按任意顺序返回答案。
题解
class Solution {
public int[] topKFrequent(int[] nums, int k) {
int[] result = new int[k];
// key : 数字; value :出现到频次
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int curNum = nums[i];
map.put(curNum, map.getOrDefault(curNum, 0) + 1);
}
// 根据 value 进行排序
ArrayList<Map.Entry<Integer, Integer>> list = new ArrayList(map.entrySet());
Collections.sort(list, (a, b) -> {
return b.getValue() - a.getValue();
});
for (int i = 0; i < k; i++) {
result[i] = list.get(i).getKey();
}
// System.out.println(JSONUtil.toJsonStr(result));
return result;
}
}
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;