前言
本文是跟着代码随想录的栈与队列顺序进行刷题并编写的 代码随想录
开学后又开始刷算法了,果然放假就不容易坚持下来了,还是在学校的生活规律一点
首先说明一下:
①栈和队列的特性:栈是先进先出、队列先进后出
②java中的栈使用Deque实现,分为LinkedList和ArrayDeque
这是力扣刷算法的其他文章链接:刷算法Leetcode文章汇总
栈与队列篇
需要实现push、pop、peek、empty四个操作,一般使用两个栈来实现一个队列
①两个栈AB,栈A作为队尾、栈B作为队头,每次push将所有元素转移至栈A,每次pop或peek将所有元素转移至栈B,判空同时判断栈AB
②上面方法可改进为,只用当栈B为空的时候才需要将栈A的所有元素转移过来,不需要每次转移所有元素,其余相同
实现栈的push、top、pop、empty四个操作
①两个队列,队列A用于top或者pop,直接从队头取出;队列B用于辅助入栈,先将元素放入队列B,再将队列A中所有元素转移至队列B,然后将AB互换;保证队列A始终有所有元素,会减少很多判断步骤;判空同时判断AB
②一个队列,思想为每次top或pop时保证理论上的栈顶元素在队头,有两种方式:
a.始终保证栈顶元素在队头:每次入栈时,先将元素置于队尾,再将前面所有元素重新入队
b.只有获取栈顶时才将栈顶元素移至队头:队列元素按照正常入队顺序放置,pop时将n-1个元素重新入队,但top操作还要将第n个元素也重新入栈
①核心思想:栈,左括号入栈,右括号匹配,看是否剩余或类型不符
②改进:Map存储左右括号的对应关系,减少硬编码
class Solution {
public boolean isValid(String s) {
Deque<Character> stack = new ArrayDeque<>();
for (char c : s.toCharArray()){
if(c == '(' || c == '{' || c == '['){
stack.push(c);
}
else if(!stack.isEmpty()){
if((c == ')' && stack.peek() == '(')
|| (c == '}' && stack.peek() == '{')
|| (c == ']' && stack.peek() == '[')) stack.pop();
else return false;
}
else return false;
}
return stack.isEmpty();
}
}
①核心思想:栈将字符逐个入栈,当入栈字符与栈顶字符相同时,同时舍弃这两个字符。最后使用StringBuilder将栈中每个字符拼接
②改进:不使用栈进行判断,直接在构建StringBuilder时,进行拼接字符和StringBuilder的最后一个字符判断,相同则同时舍弃,最后拼接。官方使用StringBuffer,线程更安全但性能稍低
①核心思想:栈,遇到数字就入栈,遇到符号就取出两个栈顶进行运算,将结果重新入栈。注意java中的String判断要使用equals
②改进:int[]数组模拟栈,数组的长度只需要(n+1)/2即可,记录数组当前的下标,遇到数字就存储,遇到符号就将下标对应的连续两个数进行运算,并将结果存储
class Solution {
public int evalRPN(String[] tokens) {
int n = tokens.length;
int[] stack = new int[(n+1)/2];
int index = -1;
for(String s : tokens){
if(s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")){
if(index < 1) return -1;
if(s.equals("+")) stack[--index] += stack[index+1];
else if(s.equals("-")) stack[--index] -= stack[index+1];
else if(s.equals("*")) stack[--index] *= stack[index+1];
else if(s.equals("/")) stack[--index] /= stack[index+1];
}
else stack[++index] = Integer.parseInt(s);
}
return stack[index];
}
}
①优先队列,队列元素为int[],包括value和index,根据元素的value进行降序。窗口滑动后,判断队列的队头下标是否在窗口范围内,不在就poll()
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
int[] res = new int[n-k+1];
PriorityQueue<int[]> maxHeap = new PriorityQueue<>((p1, p2) -> {
return p2[0] == p1[0] ? p2[1]-p1[1] : p2[0]-p1[0];
});
for(int i = 0; i < k; i++) maxHeap.offer(new int[]{nums[i],i});
res[0] = maxHeap.peek()[0];
for(int i = k; i < n; i++){
maxHeap.offer(new int[]{nums[i],i});
while(maxHeap.peek()[1]<=i-k) maxHeap.poll();
res[i-k+1] = maxHeap.peek()[0];
}
return res;
}
}
②单调队列,有单调性的双端队列,队列只记录下标。对于数组下标i<j,若nums[j]>nums[i],且都在滑动窗口内,则nums[i]之后一定不会是滑动窗口的最大值,因此入队时将更小的队尾出队后再入队;在队头获取每次的最大值时,先将无效的下标出队再获取
③分组+最大前后缀,将nums每k个分为一组,预处理计算每个下标在组中的最大前后缀,最大前缀指的是从组头到该值中的最大值,最大后缀为从该值到组尾的最大值;最后滑动窗口的最大值为res[i]=max(suffixMax[i],prefixMax[i+k-1])
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
int[] res = new int[n-k+1];
int[] prefixMax = new int[n];
int[] suffixMax = new int[n];
for(int i = 0, j = n-1; i < n; i++, j--){
if(i % k == 0)
prefixMax[i] = nums[i];
else
prefixMax[i] = Math.max(prefixMax[i-1],nums[i]);
if(j==n-1 || (j+1)%k==0)
suffixMax[j] = nums[j];
else
suffixMax[j] = Math.max(suffixMax[j+1],nums[j]);
}
for(int i = 0; i <= n - k; i++){
res[i] = Math.max(suffixMax[i],prefixMax[i+k-1]);
}
return res;
}
}
①map+优先队列,使用map统计每个元素个数,PriorityQueue元素为value-num,自定义Comparator根据num逆序;入队时,如果队中已经有k个元素,并且新元素的num大于队尾元素,就将队尾出队后新元素入队
②map+List部分快排,使用,ap统计每个元素个数,然后使用List<int[]>记录value-num;该题可以转化为找到第k个高频元素及其前面的元素,但前k-1个的排序并不重要,可以使用部分快排的思想,每次看pivot是否为第k个,并将前面的元素记录至结果数组中
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);
}
// 获取每个数字出现次数
List<int[]> values = new ArrayList<>();
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
int num = entry.getKey(), count = entry.getValue();
values.add(new int[]{num, count});
}
int[] res = new int[k];
quickSortForK(values, 0, values.size() - 1, res, 0, k);
return res;
}
public void quickSortForK(List<int[]> values, int left, int right, int[] ret, int resIndex, int k) {
int picked = (int) (Math.random() * (right - left + 1)) + left;
Collections.swap(values, picked, left);
int pivot = values.get(left)[1];
int index = left;
for (int i = left + 1; i <= right; i++) { // 降序
if (values.get(i)[1] >= pivot) {
Collections.swap(values, index + 1, i);
index++;
}
}
Collections.swap(values, left, index);
if (k <= index - left) {
quickSortForK(values, left, index - 1, ret, resIndex, k);
} else {
for (int i = left; i <= index; i++) {
ret[resIndex++] = values.get(i)[0];
}
if (k > index - left + 1) {
quickSortForK(values, index + 1, right, ret, resIndex, k - (index - left + 1));
}
}
}
}
③map+List排序,map记录每个元素个数,List<int[]>记录value-sum,使用Collections.sort进行排序,再用stream转化为结果数组
import java.util.Map.Entry;
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);
}
List<Map.Entry<Integer, Integer>> values = new ArrayList<>(map.entrySet());
Collections.sort(values, Comparator.comparing(Entry::getValue, Comparator.reverseOrder()));
return values.subList(0, Math.min(k, values.size())).stream().mapToInt(Entry::getKey).toArray();
}
}
写这题时一直在尝试j找到java中map有没有一种根据value排序的方法,编写Comparator自定义比较方式,但是没能成功实现,各位能不能帮忙看看能怎么实现一下:
class Solution {
public int[] topKFrequent(int[] nums, int k) {
TreeMap<Integer,Integer> treeMap = new TreeMap<>(new ValueComparator());
for(int num : nums){
treeMap.put(num,treeMap.getOrDefault(num, 0) + 1);
}
int[] res = new int[k];
int i = 0;
for(Map.Entry<Integer,Integer> entry : treeMap.entrySet()){
if(i < k){
res[i++] = entry.getKey();
}else break;
}
return res;
}
}
class ValueComparator implements Comparator<Integer>{
Map<Integer,Integer> base;
public ValueComparator(){this.base = new TreeMap<>();}
public ValueComparator(Map<Integer,Integer>base){this.base=base;}
@Override
public int compare(Integer key1, Integer key2){
int value = base.getOrDefault(key1, 0).compareTo(base.getOrDefault(key2, 0));
if(value==0)return key1.compareTo(key2);
return value;
}
}
栈与队列总结
①主要特性:栈先进先出、队列先进后出
②栈使用场景:括号匹配(要求字符串的顺序和个数)、字符串去重(匹配问题)、逆波兰式求解(计算结果要重新入栈)
③队列使用场景:优先队列(要求排序、k个高频元素)、双端队列(需要处理队尾元素)、单调队列(有单调性的双端队列、滑动窗口最大值)
④map排序可以转换为List<int[]>,然后用sort排序
⑤栈和队列是一种思想,不一定要使用stack和queue来实现,有时使用int[]也可以