题目:力扣
这道题在前一个月有刷到过,当时花了很久才理解力扣题解上的思路,今天再看到这道题的时候感觉只能想起来采用优先队列维护一个元素为k的大顶堆,每次遍历返回顶端元素 - 也就是最大的那个元素
那么其实还需要考虑的点就是边界问题 - 如何判断数组中的数当前是在滑动窗口当中呢?
这里就有个很巧妙的方法实现 - 在构造优先队列的时候,不光储存元素的值,同时储存元素的index,它的排序方式是
这样在移动滑动窗口的时候,就可以直接判断堆顶的元素是否超出滑动窗口的左边界。
若超出则直接弹出就可以。//注意,这个判断不能只判断一次,需要考虑可能弹出后的堆顶元素仍然超出边界,需要把所有的都弹出 - 因此采用while循环而不是if
另一个需要注意的点是
需要先将新的元素放入然后再进行边界判断 //需要考虑一种极端情况,k=1,那么如果先进行边界判断再将新元素放入,采用while循环判断会导致最后队列中不存在元素,会报空指针错误
整体代码实现:
public int[] maxSlidingWindow(int[] nums, int k) {
//初始化优先队列和结果集
PriorityQueue window = new PriorityQueue<>( (o1, o2) ->{
if(o1[0] != o2[0]){
return o2[0] - o1[0];
}else{
return o1[1] - o2[1];
}
});
int[] result = new int[nums.length-k+1];
//初始化第一个大顶堆和第一个结果
for(int i=0; i
采用优先队列实现的时候,其实并没有严格地维护一个窗口,优先队列中储存的元素会超过滑动窗口中的数量。
其实很多的元素没有必要去维护,只需要去维护最有可能是最大值的元素。
因此可以采用单调队列来实现:
例子 -- 输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
在left为0,right为2的窗口中
index =0,首先将1放入队列;index向右移动
index =1,此时3比1要大,因此3肯定是比1 更可能成为这个窗口的最大值的,把1弹出,放入3
index = 2,-1比3要小,因此将-1放到3的后面[-1的index要比3大,-1有可能是后面的窗口最大值,因此不能删掉]
此时将队首元素存入结果集。
在left为1,right为3的窗口中
index = 3, -3比-1小,因此将-3放入
判断队首元素是否在边界中,如果在则将队首元素存入结果集。
在left为2,right为4的窗口中
index = 5, 5比-3大,删除-3;5比-1大,删除-1; 5比3大,删除3,说明5更有可能成为在窗口[2,4]中的最大值,将5储存。
判断队首元素是否在边界中,如果在则将队首元素存入结果集。
...
整体代码思路:
代码实现:
public int[] maxSlidingWindow(int[] nums, int k) {
LinkedList queue = new LinkedList<>();
int[] result = new int[nums.length-k+1];
for(int right=0; right=0){
result[right-k+1] = nums[queue.peekFirst()];
}
}
return result;
}
参考资料:代码随想录
题目:力扣
这道题同样是可以采用优先级队列 - 求前k个高频元素,也就是维护长度为k的小顶堆
为什么是小顶堆呢?
因为如果采用大顶堆,那么在k之后的也就是频率低的元素并不在堆顶,每次移动更新堆的时候,并不能找到那个在k之后的元素将其弹出;
而用小顶堆的话,在k之后的元素会被挤到堆顶,那么每次在更新的时候就可以很方便的将其弹出,完成小顶堆的更新。
因此队列会是长度为k单调递增的队列。
对于统计频率,非常常用的一个操作是通过map来储存频率。
这里的操作总共分为三步:
具体的代码实现:
public int[] topKFrequent(int[] nums, int k) {
HashMap map = new HashMap<>();
for(int i : nums){
map.put(i, map.getOrDefault(i, 0)+1);
}
PriorityQueue> queue = new PriorityQueue<>((o1, o2) ->{
return o1.getValue() - o2.getValue();
});
for(Map.Entry o : map.entrySet()){
queue.offer(o);
if(queue.size() > k){//使用优先队列的话,只需要判断长度是否超过k,
//因为将新的元素放入队列中会自动排序的,不需要进行value的大小比较
queue.poll();
}
}
int[] result = new int[k];
for(int i= k-1; i >=0; i--){
result[i] = queue.poll().getKey();
}
return result;
}
参考资料:代码随想录
题目:力扣
这道题实在模拟Linux操作系统中的pwd查找路径。
在对字符串用 ‘/’ 进行分割之后,可以发现,有四种不同的字符
1. 对于空字符串和一个点,是不需要进行处理的 - 也就是输出路径中可以忽略的。
2. 对于两个点是需要返回到上一级 - 因此可以采用栈来储存之前的目录,遇到两个点,若栈不为空,则直接弹出栈顶。
3. 对于文本串则是将其放入栈中,说明是下一级目录。
实现代码:
public String simplifyPath(String path) {
String[] pathlist = path.split("/");
LinkedList stack = new LinkedList<>();
for(String s : pathlist){
if(s.equals("") || s.equals(".")){ //条件1
continue;
}else if(s.equals("..")){//条件2
if(!stack.isEmpty()){
stack.poll();
}
}else{//条件3
stack.push(s);
}
}
String result = "";
while(!stack.isEmpty()){
result = "/"+ stack.poll() + result;
}
if(result.equals("")){//输出的时候注意,如果是空字符,那么说明stack是空的,此时是有一个“/”
return "/";
}
return result;
}
参考资料:力扣
栈在系统中的应用是非常广泛的。
递归的实现是栈:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
Java中的栈目前用的最多的是采用DequeArray, Linkedlist这类双端队列用来实现栈的。
栈可以解决:括号匹配问题、字符串去重问题、波兰表达式
在java中PriorityQueue 优先队列是非常有用的队列。
可以用来排序、维护k长度的大顶堆/小顶堆
用于解决:滑动窗口最大值问题、前k个高频元素。