基本排序算法之桶排序(力扣451题、347题)
桶排序是对整数进行排序的高效算法,在进行桶排序的时候我们需要先确定key,即key代表得含义,以及key的取值范围,key的取值范围决定了桶的数量。假设键值的范围是从0到t,那么需要t+1个桶,标记分别为0、1、……、t 。如果元素的键值是i,那么就将该元素放入桶i中,每个桶放的都是键值相同的元素。
一般使用一个ArraysList数组作为一组不同标记的桶。桶中存储的正是键值相同的一系列元素。伪代码如下:
public void bucketsort(E[] list){ ArrayList[] bucket = new ArrayList[t+1]; for (int i = 0; i < list.length; i++) { int key = list[i].getKey(); if (bucket[key] == null){ bucket[key] = new ArrayList<>(); } bucket[key].add(list[i]); } int k = 0; for (int i = 0; i < bucket.length; i++) { if (bucket[i]!=null){ for (int j = 0; j < bucket[i].size(); j++) { list[k++] = bucket[i].get(j); } } } }
桶排序的时间复杂度O(n+t),空间复杂度为O(n+t),n为元素列表的大小,t为桶的大小
练习:
1、力扣第451题
给定一个字符串,请将字符串里的字符按照出现的频率降序排列。
分析思路:
将字符串中出现频率最高的字符放在最前边,字符出现的频率的大小为1-n,所以以字符出现的频数作为桶的标记,将频数出现相同的放在一个桶内,有一个细节就是如果同一个字符,如果出现的频数为i,那么就将它放入桶中i个,这样方便后面求出最终的排列结果。最后,倒序遍历桶,然后依次拼接字符串。
具体的代码如下:
public String frequencySort(String s) { if (s.length() <=2){ return s; } HashMap<Character, Integer> map = new HashMap<>(); for (char c : s.toCharArray()) { map.put(c,map.getOrDefault(c,0)+1); } ArrayList[] bucket = new ArrayList[s.length() + 1]; for (Character c : map.keySet()) { int count = map.get(c); if (bucket[count] == null){ bucket[count] = new ArrayList<>(); } if (count>1){ for (int i = 0; i < count; i++) { bucket[count].add(c); } }else { bucket[count].add(c); } } char[] res = new char[s.length()]; int k = 0; for (int i = bucket.length-1; i >= 0; i--) { if (bucket[i] == null){ continue; }else { for (int j = 0; j < bucket[i].size(); j++) { res[k++] = (Character)bucket[i].get(j); } } } return new String(res); }
2、力扣第347题
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
你可以按任意顺序返回答案。
分析思路:
要求求出出现频率最高的前K个元素,那么说明数组中元素的频数至少为1,至多为n(假设数组一共n个元素),那么我们可以以元素出现的频数作为键值进行桶排序,设t为数组不重复元素的个数,那么需要的桶的标记数量就为t+1,每个桶中存储的元素就是频度相同的元素。最后我们从后向前遍历桶,求出k个不同元素即可。
参考自:https://github.com/CyC2018/CS-Notes/ (膜拜大神)
实现代码如下:
public int[] topKFrequent(int[] nums, int k) { if (nums == null){ return null; } HashMap<Integer, Integer> map = new HashMap<>(); int n = nums.length; for (int num : nums) { if (map.get(num) == null){ map.put(num,1); }else { int count = map.get(num); count ++; map.put(num,count); } } // 用元素出现的频数作为key,key的范围是0-t,所以需要t+1个桶,标记从0-t ArrayList[] bucket = new ArrayList[n + 1]; // 将频度相同的元素添加到相同的水桶中 for (Integer key : map.keySet()) { int count = map.get(key); if (bucket[count] == null){ bucket[count] = new ArrayList<>(); } bucket[count].add(key); } // 进行寻找频率前k高的元素 ArrayList<Integer> res = new ArrayList<>(); for (int i = bucket.length-1; i >=0; i--) { if (bucket[i] == null){ continue; }else { if (bucket[i].size() <= (k-res.size())){ res.addAll(bucket[i]); }else { res.addAll(bucket[i].subList(0,k-res.size())); } } } return res.stream().mapToInt(Integer::valueOf).toArray(); }