- topKFrequent 方法:返回出现频率前 k 高的元素
这个方法通过以下步骤实现:
统计频率:
使用 HashMap 统计每个元素的出现频率。
时间复杂度:O(n),其中 n 是数组的长度。
优先级队列(最大堆):
使用优先级队列(PriorityQueue)来存储频率信息,队列按照频率降序排列。
将所有元素及其频率加入优先级队列。
时间复杂度:每次插入操作为 O(logn),总时间复杂度为 O(nlogn)。
提取结果:
从优先级队列中依次提取 k 个元素,存入结果数组。
时间复杂度:每次提取操作为 O(logn),总时间复杂度为 O(klogn)。
代码分析
效率:虽然优先级队列的实现比直接排序的方法效率高,但总体时间复杂度仍然为 O(nlogn)。
优点:代码简洁,利用了 Java 的内置数据结构,易于理解和实现。
改进:如果需要进一步优化,可以考虑使用桶排序,将时间复杂度降低到 O(n)。
//请你返回其中出现频率前 k 高的元素
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
int[] res = new int[k];
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
}
//虽然也可以选择集合实现,但相对来说优先级队列效率更高
PriorityQueue<Map.Entry<Integer, Integer>> pq = new PriorityQueue<>(new Comparator<Map.Entry<Integer, Integer>>() {
@Override
public int compare(Map.Entry<Integer, Integer> o1, Map.Entry<Integer, Integer> o2) {
return o2.getValue() - o1.getValue();
}
});
for (Map.Entry<Integer, Integer> en : map.entrySet()) {
pq.offer(en);
}
for (int i = 0; i < res.length; i++) {
res[i]=pq.poll().getKey();
}
/List<Map.Entry<Integer, Integer>> list = new ArrayList<>(map.entrySet());
list.sort(Map.Entry.comparingByValue(Comparator.reverseOrder()));
for (int i = 0; i < k; i++) {
res[i] = list.get(i).getKey();
}/
return res;
}
-
maxSlidingWindow 方法:滑动窗口最大值
这个方法实现了两种实现方式:
方法 1:使用自定义的单调队列 ProseQueue
单调队列的实现:
使用 ArrayDeque 来维护一个单调递减的队列。
每次插入新元素时,移除队列中所有小于当前元素的值,以保持单调性。
时间复杂度:每次插入操作为 O(1)(平均情况下),总时间复杂度为 O(n)。
滑动窗口的逻辑:
在窗口滑动时,移除超出窗口范围的元素。
记录当前窗口的最大值(队头元素)。
时间复杂度:O(n)。
方法 2:直接使用 ArrayDeque 实现单调队列
直接使用双端队列:
在窗口滑动时,直接在 ArrayDeque 中维护单调递减的队列。
移除超出窗口范围的元素和小于当前元素的值。
时间复杂度:O(n)。
效率对比:
方法 1 使用了自定义的 ProseQueue 类,代码更加模块化,但效率略低(38ms)。
方法 2 直接使用 ArrayDeque,减少了类的封装,效率更高(26ms)。
代码分析
效率:两种方法的时间复杂度均为 O(n),但直接使用 ArrayDeque 的方法效率更高。
优点:直接使用 ArrayDeque 的方法减少了类的封装,代码更加简洁。 -
辅助类 ProseQueue
这个类封装了单调队列的逻辑,提供了以下方法:
offer:插入新元素,保持单调递减。
poll:移除指定值,如果队头元素等于该值。
getMax:获取当前队头元素(最大值)。
代码分析
效率:单调队列的实现非常高效,时间复杂度为 O(n)。
优点:封装了单调队列的逻辑,代码更加模块化,易于复用。//239. 滑动窗口最大值
public int[] maxSlidingWindow(int[] nums, int k) {
//另外定义一个单调队列实现,效率较低38ms
/ProseQueue proseQueue = new ProseQueue();
int[] result = new int[nums.length - k + 1];
for (int i = 0; i < k-1; i++) {
proseQueue.offer(nums[i]);
}
for (int i = k-1,j=0; i < nums.length; i++) {
proseQueue.offer(nums[i]);
result[j]=proseQueue.getMax();
proseQueue.poll(nums[j++]);
}
return result;/
//不用另外定义一个单调队列,直接利用双端队列实现对应思想即可26ms
ArrayDequedeque = new ArrayDeque<>();
int[] result = new int[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
if (i>=k){
if(nums[i-k]deque.peekFirst())deque.pollFirst();
}
while (!deque.isEmpty() && nums[i] > deque.getLast()) {
deque.removeLast();
}
deque.addLast(nums[i]);
if (i>=k-1){
result[i-k+1] = deque.peekFirst();
}
}
return result;
}
//239. 滑动窗口最大值 辅助类 单调队列
class ProseQueue{
Dequedeque = new ArrayDeque<>(); val){
public ProseQueue() {}
public void offer(int x){
while (!deque.isEmpty()&&deque.getLast()<x){
deque.removeLast();
}
deque.offerLast(x);
}
public int poll(int val){
if(!deque.isEmpty()&&deque.peekFirst()
return deque.pollFirst();
}
return val;
}
public int getMax(){
return deque.peekFirst();
}
}