最近在刷LeetCode的算法题,今天学习了排序的高效方法:最小堆和桶排序法。
题目描述:给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2 输出: [1,2]
1.最小堆法
思路:①借助哈希表来建立数字及其出现频次的映射
②维护一个元素数目为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; }
tips:
PriorityQueue(优先级队列):按关键词有序排列,插入新数据会自动插入合适位置。默认小顶堆。重写可以改变其排列方式。
方法1:
PriorityQueue<Integer> pq=new PriorityQueue<>(new Comparator<Integer>(){ @Override public int compare(Integer a,Integer b){ return map.get(b)-map.get(a); } )};
方法2:调用Comparator,reverseOrder()方法
PriorityQueue<Integer> pq=new PriorityQueue<>(Comparator.reverseOrder());
方法3:Lambda表达式:允许把函数作为一个方法的参数传递进方法。
PriorityQueue<Integer> pq=new PriorityQueue<>((a,b)->b-a);
时间复杂度:O(nlogk),n表示数组的长度,遍历一遍复杂度为o(n);接着,遍历用于存储元素频率的map,若元素频率大于最小堆中顶部的元素,则将顶部的元素删除并将该元素 加入堆中,这里维护堆的数目为k,因此时间复杂度为O(nlogk)。
2.桶排序法
//基于桶排序求解「前 K 个高频元素」 class Solution { public List<Integer> topKFrequent(int[] nums, int k) { List<Integer> res = new ArrayList(); // 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值 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); } } //桶排序 //将频率作为数组下标,对于出现频率不同的数字集合,存入对应的数组下标 List<Integer>[] list = new List[nums.length+1]; for(int key : map.keySet()){ // 获取出现的次数作为下标 int i = map.get(key); if(list[i] == null){ list[i] = new ArrayList(); } list[i].add(key); } // 倒序遍历数组获取出现顺序从大到小的排列 for(int i = list.length - 1;i >= 0 && res.size() < k;i--){ if(list[i] == null) continue; res.addAll(list[i]); } return res; } }
LeetCode-75按颜色排序
题目:
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
学习来的思路:1.设置两个指针r1,r2,分别代表当前r1记录左边小于1的当前位置,r2记录左边小于2 的当前位置。
2.如果左边比2小,那么就r2++,且交换数组的顺序(r2和i),若仍然小于1,r1++,继续交换(r2,r1)
(这个思路太大神了orz)
class Solution { public void sortColors(int[] nums) { int r1=-1,r2=-1; for(int i=0;i<nums.length;i++){ if(nums[i]<2){ r2++; swap(nums,i,r2); if(nums[r2]<1){ r1++; swap(nums,r1,r2); } } } } void swap(int[] nums,int i,int j){ int temp=nums[i]; nums[i]=nums[j]; nums[j]=temp; } }