day12 347.前 K 个高频元素&&239. 滑动窗口最大值

  1. 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;
}

  1. 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 的方法减少了类的封装,代码更加简洁。

  2. 辅助类 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
    ArrayDeque deque = 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{
    Deque deque = new ArrayDeque<>();
    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()
    val){
    return deque.pollFirst();
    }
    return val;
    }
    public int getMax(){
    return deque.peekFirst();
    }
    }

posted @ 2025-01-26 02:37  123木头人-10086  阅读(71)  评论(0)    收藏  举报