347. 前 K 个高频元素

本文介绍了一种高效算法,用于找出数组中出现频率最高的k个元素。通过使用哈希表统计频次,结合快速排序思想优化查找过程,实现优于O(nlogn)的时间复杂度。

快速排序法博客:

https://blog.youkuaiyun.com/u014241071/article/details/81565148

题目

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:

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

示例 2:

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

提示:

  • 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
  • 你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
  • 你可以按任意顺序返回答案。

官方题解

方法一:堆

思路与算法

首先遍历整个数组,并使用哈希表记录每个数字出现的次数,并形成一个「出现次数数组」。找出原数组的前 k 个高频元素,就相当于找出「出现次数数组」的前 k 大的值。

最简单的做法是给「出现次数数组」排序。但由于可能有 O(N) 个不同的出现次数(其中 N 为原数组长度),故总的算法复杂度会达到 O(N log N),不满足题目的要求。

在这里,我们可以利用堆的思想:建立一个小顶堆,然后遍历「出现次数数组」:

  • 如果堆的元素个数小于 k,就可以直接插入堆中。
  • 如果堆的元素个数等于 k,则检查堆顶与当前出现次数的大小。如果堆顶更大,说明至少有 k 个数字的出现次数比当前值大,故舍弃当前值;否则,就弹出堆顶,并将当前值插入堆中。

遍历完成后,堆中的元素就代表了「出现次数数组」中前 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);
        }

        // 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;
    }
}

方法二:基于快速排序

思路与算法

我们可以使用基于快速排序的方法,求出「出现次数数组」的前 k 大的值。

在对数组 arr[l…r] 做快速排序的过程中,我们首先将数组划分为两个部分 arr[i…q−1] 与 arr[q+1…j],并使得 arr[i…q−1] 中的每一个值都不超过 arr[q],且 arr[q+1…j] 中的每一个值都大于 arr[q]。

于是,我们根据 k 与左侧子数组 arr[i…q−1] 的长度(为 q−i)的大小关系:

  • 如果 k≤q−i,则数组 arr[l…r] 前 k 大的值,就等于子数组 arr[i…q−1] 前 kk 大的值。
  • 否则,数组 arr[l…r] 前 k 大的值,就等于左侧子数组全部元素,加上右侧子数组 arr[q+1…j] 中前 k−(q−i) 大的值。
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) {
            qsort(values, start, index - 1, ret, retIndex, k);
        } else {
            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));
            }
        }
    }
}

总结:快速排序的这个方法没看懂。官方的这个思路描述的实在是不像是人话,理解不明白。硬着头皮一段一段思考代码起码还能稍微理解一下,看官方的文字描述是真的折磨。

还有一个很好的思路。

作者:yuan-shan-mei
https://leetcode-cn.com/problems/top-k-frequent-elements/solution/zui-xiao-dui-fa-by-yuan-shan-mei-fnku/

最小堆法

思路:
①借助哈希表来建立数字及其出现频次的映射
②维护一个元素数目为k的最小堆
③每次都将新元素与堆顶元素(堆中频率最小的元素)比较
④若新的元素比堆顶端的元素大,则弹出堆顶元素,将新元素添加进去
⑤最终,堆中k个元素即为前k个高频元素

class Solution{
	public List<Integer> topKFrequent(int[] nums,int k){
		//使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值
		HashMap<Integer,Integer> map=new HashMap();
		for(int num:nums){
			if(map.containsKey(num)){
				//若已存在,值加一
				map.put(num,map.get(num)+1);
			}else{
				//若不存在,创建新的键值对
				map.put(num,1);
			}
		}
	}
	//遍历map,用优先队列最小堆保存频率最大的k个元素
	//建立小顶堆,若想建立大顶堆,则return map.get(b)-map.get(a)
	/*继承Comparator接口,必须重写compare 方法*/
	PriorityQueue<Integer> pq=new PriorityQueue<>(new Comparator<Integer>(){
		@Override
		public int compare(Integer a,Integer b){
			return map.get(a)-map.get(b);
		}
	});
	//保存频率最大的k个元素,peek()方法:查看堆顶元素值
	for(Integer key:map.keySet()){
		if(pq.size()<k){
			pq.add(key);
		}else if(map.get(key)>map.get(pq.peek())){
			pq.remove();
			pq.add(key);
		}
	}
	//取出最小堆的元素
	List<Integer> res=new ArrayList<>();
	while(!pq.isEmpty()){
		res.add(pq.remove);
	}
	return res;
}
03-08
### MathPix工具介绍 Mathpix Snipping Tool 是一款强大的数学公式识别工具,从最初的原型发展至今已经历了多次迭代和功能增强[^1]。该工具不仅能处理简单的数学表达式,还可以应对复杂的数学模型以及多行公式,并且支持手写公式的识别。 这款应用程序通过不断的科技创新与用户反馈改进,在学术研究和技术领域赢得了良好声誉,成为许多科研人员不可或缺的工作伙伴之一。除了基本的功能外,Mathpix 还提供了多种高级特性来满足不同用户的特定需求。 ### 使用方法 #### 安装与启动 为了使用 Mathpix Snipping Tool ,首先需要下载安装程序并按照提示完成设置过程。一旦成功安装后即可随时调用此应用来进行截图操作。 #### 截取屏幕上的公式图像 当遇到想要转换成 LaTeX 或 AsciiMath 的图片时,只需打开软件界面按下快捷键(默认为 `Ctrl+Alt+M`),此时鼠标指针会变为十字形状以便于选取目标区域;框选所需部分之后松开按键即刻上传至云端服务器等待进一步分析处理。 #### 获取LaTeX代码片段 经过短暂几秒钟的时间延迟过后,所截获的内容将以纯文本形式显示出来供复制粘贴到其他编辑器当中继续编写文档或是分享给他人查看交流之用。 ```python import pyperclip # 假设已获取到LaTeX字符串 stored_in_variable named latex_code pyperclip.copy(latex_code) print("LaTeX code has been copied to clipboard.") ``` 对于希望深入探索更多可能性的用户来说,可以考虑查阅官方提供的扩展插件——如用于解析Markdown文件内的公式标记语法的库 **mathpix-markdown-it** [^2] ——这将进一步提升工作效率和个人体验感。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值