代码随想录-Day13

239. 滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
在这里插入图片描述

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
            public int compare(int[] pair1, int[] pair2) {
                return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
            }
        });
        for (int i = 0; i < k; ++i) {
            pq.offer(new int[]{nums[i], i});
        }
        int[] ans = new int[n - k + 1];
        ans[0] = pq.peek()[0];
        for (int i = k; i < n; ++i) {
            pq.offer(new int[]{nums[i], i});
            while (pq.peek()[1] <= i - k) {
                pq.poll();
            }
            ans[i - k + 1] = pq.peek()[0];
        }
        return ans;
    }
}

这段代码定义了一个名为 Solution 的类,其中包含一个方法 maxSlidingWindow,用于求解数组中大小为 k 的滑动窗口里的最大值。该方法利用优先队列(具体实现为 PriorityQueue)来高效地维护窗口内的最大值,下面是代码的详细解析:

方法签名

public int[] maxSlidingWindow(int[] nums, int k)
  • 输入:
    • nums: 整型数组,表示输入序列。
    • k: 窗口大小。
  • 输出:
    • 返回一个数组,包含每个大小为 k 的滑动窗口的最大值。

代码解析

  1. 初始化:

    • 计算输入数组长度 n
    • 初始化一个优先队列 pq,用于存放窗口内的元素及其在原数组中的索引。这里的优先队列采用自定义比较器,使得队列顶部元素始终为当前窗口内最大值(值最大,且最早出现的排前面)。
  2. 预处理前 k 个元素:

    • 先将前 k 个元素加入优先队列 pq
  3. 构建结果数组:

    • 初始化结果数组 ans,大小为 n - k + 1
    • 记录下当前窗口(即前 k 个元素)的最大值,作为结果数组的第一个元素。
    • 遍历数组剩余部分,每次迭代时:
      • 向队列添加新进入窗口的元素(当前元素)。
      • 若队首元素已不在当前窗口内(索引小于 i - k),则将其移出队列,确保队列始终代表当前窗口。
      • 记录下队首元素作为当前窗口的最大值,加入结果数组。
  4. 返回结果:

    • 完成遍历后,返回结果数组 ans

注意

  • 代码中存在一个语法错误,创建 pq.offer 时应为 pq.offer(new int[]{nums[i], i}),原代码缺少括号闭合。
  • 自定义比较器实现了降序排列,首先按值降序,值相同时按索引降序,确保了最早的元素被优先淘汰,满足滑动窗口逻辑。

修正后的代码如下:

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
            public int compare(int[] pair1, int[] pair2) {
                return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
            }
        });
        for (int i = 0; i < k; ++i) {
            pq.offer(new int[]{nums[i], i});
        }
        int[] ans = new int[n - k + 1];
        ans[0] = pq.peek()[0];
        for (int i = k; i < n; ++i) {
            pq.offer(new int[]{nums[i], i});
            while (pq.peek()[1] <= i - k) {
                pq.poll();
            }
            ans[i - k + 1] = pq.peek()[0];
        }
        return ans;
    }
}

请根据上述解析,修复语法错误后,这段代码将能正确实现滑动窗口最大值的求解逻辑。

347. 前 K 个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

方法一:堆

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> occurrences = new HashMap<Integer, Integer>();
        for (int num : nums) {
            occurrences.put(num, occurrences.getOrDefault(num, 0) + 1);
        }

        // int[] 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
        PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>() {
            public int compare(int[] m, int[] n) {
                return m[1] - n[1];
            }
        });
        for (Map.Entry<Integer, Integer> entry : occurrences.entrySet()) {
            int num = entry.getKey(), count = entry.getValue();
            if (queue.size() == k) {
                if (queue.peek()[1] < count) {
                    queue.poll();
                    queue.offer(new int[]{num, count});
                }
            } else {
                queue.offer(new int[]{num, count});
            }
        }
        int[] ret = new int[k];
        for (int i = 0; i < k; ++i) {
            ret[i] = queue.poll()[0];
        }
        return ret;
    }
}

这段代码定义了一个名为 Solution 的类,其中包含一个方法 topKFrequent 用于找到输入数组 nums 中出现频率最高的 k 个元素。该方法使用了哈希映射(HashMap)记录每个元素出现的次数,并使用优先队列(PriorityQueue)进行排序,最终提取前 k 个高频元素。下面是代码的详细解析:

  1. 记录元素出现次数:

    • 遍历输入数组 nums,使用 HashMap occurrences 记录每个元素及其出现的次数,使用 getOrDefault 方法简化计数逻辑。
  2. 初始化优先队列:

    • 定义一个优先队列 queue,其中每个元素为 int[] 类型数组,数组的第一个元素存数值,第二个元素存该数值出现的频次。
    • 使用自定义比较器 Comparator,使得队列按频次从小到大排序,以方便之后的处理。
  3. 构建优先队列:

    • 遍历 occurrences 的条目,将数值和频次加入队列 queue 中。
    • 当队列大小达到 k 后,检查新元素的频次是否大于队头元素频次,若是则移除队头元素并加入新元素,确保队列始终存储的是当前最大的 k 个频次的元素。
  4. 提取结果:

    • 初始化一个大小为 k 的数组 ret 用于存放结果。
    • 弹出队列中的元素,提取数值部分存入 ret,直到队列为空。
    • 最后返回数组 ret,即为频率最高的 k 个元素。

注意点

  • 代码中存在一处逻辑错误,应将条件判断语句块正确嵌套。修正后的代码应为:
if (queue.size() == k) {
    if (queue.peek()[1] < count) {
        queue.poll();
        queue.offer(new int[]{num, count});
    }
} else {
    queue.offer(new int[]{num, count});
}
  • 修正后的代码片段将正确实现原意,即当队列满时比较并可能替换队首元素,否则直接加入新元素。

整体而言,此方法通过哈希映射高效统计频次,利用优先队列进行排序并限制大小,最终提取前 k 高频元素,是处理此类问题的一种典型且高效的算法实现。

方法二:基于快速排序

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> occurrences = new HashMap<Integer, Integer>();
        for (int num : nums) {
            occurrences.put(num, occurrences.getOrDefault(num, 0) + 1);
        }
        // 获取每个数字出现次数
        List<int[]> values = new ArrayList<int[]>();
        for (Map.Entry<Integer, Integer> entry : occurrences.entrySet()) {
            int num = entry.getKey(), count = entry.getValue();
            values.add(new int[]{num, count});
        }
        int[] ret = new int[k];
        qsort(values, 0, values.size() - 1, ret, 0, k);
        return ret;
    }

    public void qsort(List<int[]> values, int start, int end, int[] ret, int retIndex, int k) {
        int picked = (int) (Math.random() * (end - start + 1)) + start;
        Collections.swap(values, picked, start);
        
        int pivot = values.get(start)[1];
        int index = start;
        for (int i = start + 1; i <= end; i++) {
            // 使用双指针把不小于基准值的元素放到左边,
            // 小于基准值的元素放到右边
            if (values.get(i)[1] >= pivot) {
                Collections.swap(values, index + 1, i);
                index++;
            }
        }
        Collections.swap(values, start, index);

        if (k <= index - start) {
            // 前 k 大的值在左侧的子数组里
            qsort(values, start, index - 1, ret, retIndex, k);
        } else {
            // 前 k 大的值等于左侧的子数组全部元素
            // 加上右侧子数组中前 k - (index - start + 1) 大的值
            for (int i = start; i <= index; i++) {
                ret[retIndex++] = values.get(i)[0];
            }
            if (k > index - start + 1) {
                qsort(values, index + 1, end, ret, retIndex, k - (index - start + 1));
            }
        }
    }
}

这段代码实现了一个名为 Solution 的类,其中包含一个方法 topKFrequent 用于找出数组 nums 中出现频率最高的 k 个元素。代码采用了哈希映射(HashMap)记录每个元素出现的频率,并使用了快速排序(qsort)算法的一个变体来找出这些高频元素。下面是代码的详细解析:

记录元素频率

  1. 使用 HashMap occurrences 记录下每个元素及其出现的次数。
  2. HashMap 的内容转换为 List<int[]> 形式的 values,每个数组包含元素值及其出现次数。

快速排序变体

  1. 定义 qsort 方法,用于对 values 列表进行快速排序,同时填充结果数组 ret
    • 选择一个随机索引 picked 作为基准值,与起始元素交换,以实现随机化,提高平均性能。
    • 通过双指针法将元素分为小于和大于等于基准值的两部分,完成一次划分。
    • 递归地在合适的子数组中继续进行快速排序,直到完成排序。
    • 当前 k 大的元素完全位于某侧子数组中时,直接处理并更新结果数组 ret,否则递归处理另一侧子数组。

返回结果

  • 最终,topKFrequent 方法返回数组 ret,包含频率最高的 k 个元素。

注意点

  • 该实现在最坏情况下的时间复杂度可能退化为 O(N^2),因为快速排序的选择和交换操作是在基于列表的环境中进行的,这比直接在数组上的操作更耗时。
  • 代码中使用的快速排序变体特别针对寻找 top-K 问题进行了优化,减少了不必要的排序操作,直接在过程中收集结果。

整体而言,这段代码通过哈希映射快速统计元素频率,然后利用快速排序的一个变体高效找到前 k 大的高频元素,是处理这类问题的一种可行方案,尽管在效率上可能不是最优。

  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值