当前位置:   article > 正文

LeetCode——排序(215.数组中的第K个最大元素、347.出现频率最多的 k 个元素、451. 根据字符出现频率排序、75. 颜色分类)快排、桶排序_leetcode 215

leetcode 215

一、215. 数组中的第K个最大元素

1.1  题目叙述

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。(难度中等)

示例 1:输入: [3,2,1,5,6,4] 和 k = 2
输出: 5

示例 2:输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4

说明:你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

1.2 代码

1.2.1暴力+API

  1. package Sort;
  2. import java.util.Arrays;
  3. /**
  4. * @author : lkw
  5. * @data : 2021/3/11 10:32
  6. * @description :215. 数组中的第K个最大元素(难度中等)
  7. * 方法一:暴力解法
  8. * 时间复杂度:O(nlogn),此解法时间消耗为排序,jdk默认的是快速排序,
  9. * 空间复杂度:O(1),使用的是原地排序
  10. *
  11. **/
  12. public class findKthLargest_01 {
  13. public int findKthLargest(int[] nums, int k) {
  14. Arrays.sort(nums);
  15. return nums[nums.length-k];
  16. //若有5个元素,第二大,就是nums[5-2]
  17. //若有5个元素,第四大就是nums[5-4],即nums[nums.length-k]
  18. }
  19. }

1.2.2 快排(不推荐使用)

  1. package Sort;
  2. /**
  3. * @author : lkw
  4. * @data : 2021/3/11 11:54
  5. * @description :215. 数组中的第K个最大元素(难度中等)
  6. * 方法二:快速排序
  7. **/
  8. /*在程序运行开始前就知道数组的元素(所有的元素都是机前确定,并且不变),可以使用快速选择排序,
  9. // 若是动态数据流的情况,则只能使用堆排序
  10. //快速选择:即快排中轴值计算的过程,小于轴值的放在左侧,大于的放于右侧
  11. 一轮排序后,通过排序后轴值下标与k之间的关系,选定轴值左侧或右侧,进行下一步操作
  12. **/
  13. public class findKthLargest_02 {
  14. public static void main(String[] args) {
  15. int[] nums = {23,46,20,8,11,18};
  16. System.out.println(findKthLargest(nums, 2));
  17. }
  18. public static int findKthLargest(int[] nums, int k){
  19. int left= 0;
  20. int right= nums.length-1;
  21. int target = nums.length-k;
  22. while (true){
  23. int index = partition(nums,left,right);
  24. if (index==target){
  25. return nums[index];
  26. }
  27. else if(index<target){
  28. left=index+1;
  29. }
  30. else right=index-1;
  31. }
  32. }
  33. public static int partition(int[] nums,int left,int right){
  34. int pivot = nums[left];
  35. int j=left;
  36. for (int i = left+1; i <= right; i++) {
  37. if (nums[i]<pivot){
  38. j++;
  39. swap(nums,i,j);
  40. }
  41. }
  42. swap(nums,j,left);
  43. return j;
  44. }
  45. public static void swap(int[] nums,int i,int j){
  46. int temp = nums[i];
  47. nums[i]= nums[j];
  48. nums[j] = temp;
  49. }
  50. }

1.2.3 优先队列(容量为len,易理解)

补充知识:Lambda表达式

Lambda表达式:接受2个参数(数字),并返回他们的差值 (x, y) -> x – y

Java Lambda 表达式:Java Lambda 表达式 | 菜鸟教程

小顶堆:每个结点的值都小于或等于其左右孩子结点的值

小顶堆的比较器:(a, b) -> a - b

大顶堆的比较器:(a, b) -> b - a

  • poll:将首个元素从队列中弹出,如果队列是空的,就返回null
  • peek:查看首个元素,不会移除首个元素,如果队列是空的就返回null

思路:创建一个为len的小顶堆,输出len-k个最小元素后,堆中剩余k个最大元素,则堆顶是则是第k大元素。

小顶堆:每个结点的值都小于或等于其左右孩子结点的值

  1. package Sort;
  2. /**
  3. * @author : lkw
  4. * @data : 2021/3/11 12:14
  5. * @description :优先队列(1)
  6. **/
  7. import java.util.PriorityQueue;
  8. import java.util.Random;
  9. /**
  10. * 创建一个为len的小顶堆,输出len-k个元素后,堆中剩余k个最大元素,则堆顶是则是第k大元素
  11. */
  12. public class findKthLargest_03 {
  13. public static void main(String[] args) {
  14. int[] nums = {23,20,0,8,11,18};
  15. System.out.println(findKthLargest(nums, 2));
  16. }
  17. public static int findKthLargest(int[] nums, int k) {
  18. int len = nums.length;
  19. PriorityQueue<Integer> priorityQueue= new PriorityQueue<>(len,(a,b)->(a-b));
  20. for (int i = 0; i <len ; i++) {
  21. priorityQueue.add(nums[i]);
  22. }
  23. for (int i = 0; i < len-k; i++) {
  24. priorityQueue.poll();
  25. }
  26. return priorityQueue.peek();
  27. }
  28. }

1.2.4 优先队列 (容量为k+1)

若我们创建一个k+1的小顶堆,先放入k个元素

再将待插入的元素放入小顶堆,此时弹出一个堆顶元素,一直保持堆内还是k个最小元素,程序最后的栈顶就是最k大元素。

可以参考:力扣 

  1. package Sort;
  2. import java.util.PriorityQueue;
  3. /**
  4. * @author : lkw
  5. * @data : 2021/3/12 10:11
  6. * @description :
  7. **/
  8. public class findKthLargest_03 {
  9. public static void main(String[] args) {
  10. //int[] nums = {23,46,20,8,11,18};
  11. int[] nums = {3,2,1,5,6,4};
  12. System.out.println(findKthLargest(nums, 2));
  13. }
  14. public static int findKthLargest(int[] nums, int k) {
  15. /*优先队列思想:数组长度为len,输出第k大元素,即数组排序后[0...len-k-1][len-k...len-1],后半段k个元素中最小的元素
  16. 后半段k个元素中最小的元素,可以创建一个容量为k的小顶堆,最后输出堆顶元素。
  17. 但是现在数组时乱序:我们可以创建一个k的小顶堆,始终保持有k个
  18. 则先放入k个元素,堆满之后若此时元素若大于堆顶则先弹出在放入
  19. 若我们创建一个k+1的小顶堆,先放入k个元素
  20. 再将待插入的元素放入小顶堆,此时弹出一个堆顶元素,此时堆内还是k个最小元素
  21. */
  22. PriorityQueue<Integer> priorityQueue= new PriorityQueue<>(k+1,(a,b)->(a-b));
  23. //先放入k个元素
  24. for (int i = 0; i < k; i++) {
  25. priorityQueue.add(nums[i]);
  26. }
  27. for (int i = k; i < nums.length; i++) {
  28. priorityQueue.add(nums[i]);
  29. priorityQueue.poll();
  30. }
  31. return priorityQueue.peek();
  32. }
  33. }

  1. //大顶堆
  2. public static int findKthLargest(int[] nums, int k) {
  3. int len = nums.length;
  4. PriorityQueue<Integer> priorityQueue= new PriorityQueue<>(len,(a,b)->(b-a));//大顶堆
  5. for (int i = 0; i < len; i++) {
  6. priorityQueue.add(nums[i]);
  7. }
  8. for (int i = 0; i < k-1; i++) {
  9. priorityQueue.poll();
  10. }
  11. return priorityQueue.peek();
  12. }

以下为桶排序的三个题

二、347.出现频率最多的 k 个元素

2.1 题目描述

给定一个非空的整数数组,返回其中出现频率前 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 个高频元素的集合是唯一的。
你可以按任意顺序返回答案。

补充:理解List集合add()和addAll()的区别可参考博文:

简单易懂List集合add()和addAll()的区别_IX的博客-CSDN博客

Map.get() 方法返回指定键所映射的值 。

Map.ketSet() 方法将获取 Map 集合的所有键名。

Add方法是将传入的参数作为当前List中的一个item存储,即使你传入一个List也只会令当前的List增加1个元素 。

AddAll是传入一个List,将此List中的所有元素加入到当前List中,也就是当前List会增加的元素个数为传入的List的大小。 

2.2 代码 

2.2.1 桶排序

具体题解力扣

  • 借助 哈希表 来建立数字和其出现频率的映射,遍历一遍数组统计元素的频率。
  • 频率统计结束后,设置若干个桶,每个桶放出现频率相同的值,即第i个桶,存储出现频率为i的数。
  • 倒序遍历数组获取出现顺序从大到小的排列

补充:关于Map的博文:Map 综述(一):彻头彻尾理解 HashMap_Rico's Blogs-CSDN博客_hashmap 

Map 是 Key-Value 对映射的抽象接口,该映射不包括重复的键,即一个键对应一个值。HashMap 是 Java Collection Framework 的重要成员,也是Map族(如下图所示)中我们最为常用的一种。简单地说,HashMap 是基于哈希表的 Map 接口的实现,以 Key-Value 的形式存在,即存储的对象是 Entry (同时包含了 Key 和 Value) 。

同样地,HashSet 也是 Java Collection Framework 的重要成员,是 Set 接口的常用实现类,但其与 HashMap 有很多相似之处。对于 HashSet 而言,其采用 Hash 算法决定元素在Set中的存储位置,这样可以保证元素的快速存取;

HashMap首先会调用Key的hashCode方法,然后基于此获取Key哈希码,通过哈希码快速找到某个桶,这个位置可以被称之为 bucketIndex。如果两个对象的hashCode不同,那么equals一定为 false;否则,如果其hashCode相同,equals也不一定为 true。所以,理论上,hashCode 可能存在碰撞的情况。

hashCode : Object 的 native方法 , 获取对象的哈希值,用于确定该对象在哈希表中的索引位置,它实际上是一个int型整数。 

List、ArrayList和LinkedList

  • ArrayList是Array(动态数组)的数据结构,LinkedList是Link(链表)的数据结构。
  • List是一个接口,而ArrayList是List的实现类。因此List不能作为定义一个实例对象,只能作为引用。

当数列范围太大,或者不是整数时,我们可以使用桶排序,类似于计数排序。

所有的桶保存在ArrayList集合当中,每一个桶被定义成一个ArrayList。

桶排序的博文:漫画:什么是桶排序?_程序人生的博客-CSDN博客 

for (int num:nums)中的num为nums中元素的值。

String reverse = new StringBuffer(a).reverse().toString();   //翻转字符a

  1. package Sort;
  2. import java.util.ArrayList;
  3. import java.util.HashMap;
  4. import java.util.List;
  5. /**
  6. * @author : lkw
  7. * @data : 2021/3/15 9:06
  8. * @description :347. 前 K 个高频元素
  9. **/
  10. public class topKFrequent_01 {
  11. //思路一:拿一个数组n[k+1]记录0-k的次数,然后比较数组n[k+1]的大小
  12. /*
  13. Map.get() 方法返回指定键所映射的值
  14. Map.keySet() 方法将获取 Map 集合的所有键名
  15. Add方法是将传入的参数作为当前List中的一个item存储,即使你传入一个List也只会令当前的List增加1个元素
  16. AddAll是传入一个List,将此List中的所有元素加入到当前List中,也就是当前List会增加的元素个数为传入的List的大小
  17. */
  18. //1.先统计频率
  19. public static void main(String[] args) {
  20. int[] nums = {1,1,1,2,2,3};
  21. System.out.println(topKFrequent(nums, 2));
  22. }
  23. public static int[] topKFrequent(int[] nums, int k) {
  24. List<Integer> reslist = new ArrayList();
  25. //使用map存入的是键值对,统计每个元素出现的频率,元素为键,频率为值
  26. HashMap<Integer,Integer> map = new HashMap();
  27. for (int num:nums){
  28. if (map.containsKey(num)) {
  29. map.put((Integer)num,map.get(num) + 1);
  30. }
  31. else
  32. map.put((Integer)num,1);
  33. }
  34. //桶排序,频率作为数组下标,对于出现频率不同的元素,存入对于的数组下标
  35. //有n+1个桶,因为元素出现的频率可能有0次,1次,2次.....n次 , 一共有n+1种可能
  36. //
  37. List<Integer>[] list = new List[nums.length+1];//创建了一个元素都为List型的数组
  38. for (int key: map.keySet()){
  39. //i为出现的频率
  40. int i = map.get(key);
  41. if (list[i]==null){
  42. //防止空指针,防止i第一次进来的时候报错
  43. //因为频率相同也可放入,因为array[i]也是一个数组列表
  44. list[i]=new ArrayList<>();
  45. }
  46. //创建好数组列表后,再将数值放入
  47. list[i].add((Integer)key);//注意:这里不是else
  48. }
  49. //倒序遍历数组,获得频率由大到小的次序
  50. for (int i = list.length-1;i>=0 && reslist.size()<k;i--){
  51. if (list[i]==null) continue;
  52. reslist.addAll(list[i]);//为啥不是else?
  53. }
  54. System.out.println(reslist);
  55. int[] resArray = new int[reslist.size()];
  56. // int[] res = new int[k];
  57. for (int i = 0; i < k; i++) {
  58. resArray[i] = reslist.get(i);
  59. }
  60. return resArray;
  61. }
  62. }

 时间复杂度:遍历数组,统计频率的时复为O(n),桶排序的时复为O(n+1),list转数组时复为O(n),因此最后的时间复杂度为O(n).

空间复杂度:O(n), map,list,relist,reArray所占用的内存。

三、451. 根据字符出现频率排序

3.1 题目描述

给定一个字符串,请将字符串里的字符按照出现的频率降序排列。(中等难度)

示例 1:

输入:"tree"

输出:"eert"

解释:  'e'出现两次,'r'和't'都只出现一次。

因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。

3.2 代码

3.2.1 桶排序

此题和上一题347题为一个思路

先统计频率,在桶排序,在组合字符串。

  1. package Sort;
  2. import java.util.ArrayList;
  3. import java.util.HashMap;
  4. /**
  5. * @author : lkw
  6. * @data : 2021/3/21 17:20
  7. * @description :451. 根据字符出现频率排序(中等)
  8. **/
  9. public class frequencySort_01 {
  10. public static void main(String[] args) {
  11. String s = "cccaaaabcdw";
  12. frequencySort(s);
  13. }
  14. public static String frequencySort(String s) {
  15. char array[] = s.toCharArray();
  16. //键为:char,值为频率
  17. HashMap<Character, Integer> map = new HashMap();
  18. for (int i = 0; i < array.length; i++) {
  19. char key = array[i];
  20. if (map.containsKey(key)) {
  21. map.put(key, map.get(key) + 1);
  22. } else
  23. map.put(key, 1);
  24. }
  25. //System.out.println(map);//{r=1, t=1, e=2}
  26. //创建桶
  27. //有n+1个桶,因为元素出现的频率可能有0次,1次,2次.....n次 , 一共有n+1种可能
  28. //第i个桶存出现频率为i的字符
  29. ArrayList<Character>[] list = new ArrayList[array.length + 1];
  30. for (char key : map.keySet()) {
  31. int index = map.get(key);
  32. if (list[index] == null) {
  33. list[index] = new ArrayList();
  34. }
  35. list[index].add(key);
  36. }
  37. System.out.println(list[3]);
  38. //组合字符
  39. //建议使用StringBuilder,执行效率高
  40. // String a = "";
  41. //倒序扫描桶,因为频率高的在最后面
  42. StringBuilder res = new StringBuilder();
  43. for (int i=list.length-1;i>=0;i--) {
  44. if (list[i] != null) {
  45. for (char ch:list[i]){
  46. for (int j = 0; j < i; j++) {
  47. //a=a+ch;
  48. res.append(ch);
  49. }
  50. }
  51. }
  52. }
  53. //System.out.println(a);
  54. //return a;
  55. System.out.println(res.toString());
  56. return res.toString();
  57. }
  58. }

时间复杂度:O(n).   统计频率需要 O(n);创建桶遍历 HashMap 需要 O(n);遍历桶的集合需要 O(n) *两个常数,依然是 O(n)。

空间复杂度:O(n).   建立 HashMap 需要 O(n);建立 n+1个桶需要 O(n)。

最后组合字符串用的string:

最后输出组合字符串用的StringBuilder:

补充:StringBuffer和StringBuilder的运行效率和内存占用几乎一致,这是因为二者都是对对象本身进行操作,不会生成新的字符串对象。二者的区别主要是StringBuffer是线程安全的,而StringBuilder是线程不安全的。

String是不可变的对象,每次对String进行操作的时候,都会生成一个新的对象,这会对系统的性能造成影响。

执行效率竟然如此之大,因此以后建议使用StringBuilder。 

四、75. 颜色分类

4.1 题目描述

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。(难度中等)

示例 1:输入:nums = [2,0,2,1,1,0]
              输出:[0,0,1,1,2,2]

示例 2:输入:nums = [2,0,1]
              输出:[0,1,2]

示例 3:输入:nums = [0]
              输出:[0]

示例 4:输入:nums = [1]
              输出:[1]

4.2 代码

4.2.1 桶排序

  1. package Sort;
  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.HashMap;
  5. /**
  6. * @author : lkw
  7. * @data : 2021/3/21 20:36
  8. * @description :75. 颜色分类
  9. * 桶排序
  10. **/
  11. public class sortColors_01 {
  12. public static void main(String[] args) {
  13. int nums[] = {1,1,0,2,0,1,2,2,2};
  14. sortColors(nums);
  15. }
  16. public static void sortColors(int[] nums) {
  17. //统计频率,hashmap,key:数值,value:频率
  18. HashMap<Integer,Integer> map = new HashMap();
  19. for (int num:nums){
  20. if (map.containsKey(num)){
  21. map.put(num,map.get(num)+1);
  22. }
  23. else
  24. map.put(num,1);
  25. }
  26. //System.out.println(map);
  27. int t=0;
  28. for (int key: map.keySet()){
  29. for (int i =0;i<map.get(key);i++){
  30. nums[t++]=key;
  31. }
  32. }
  33. //System.out.println(Arrays.toString(nums));
  34. }
  35. }

时间复杂度:O(n), 遍历数组nums时构造HashMap为O(n),最后构建nums,遍历map用时O(n),因此为O(n)。

空间复杂度:O(n),  map的大小为O(n)

 

4.2.2 数组+指针

此代码中: 0 的优先级> 1 的优先级 >2 的优先级

即时2先被填入,但是若最后值不为2,会被覆盖掉。

  1. package Sort;
  2. import java.util.Arrays;
  3. /**
  4. * @author : lkw
  5. * @data : 2021/3/21 21:28
  6. * @description :数组
  7. **/
  8. public class sortColors_02 {
  9. public static void main(String[] args) {
  10. int nums[] = {1, 1, 0, 2, 0, 1, 2, 2, 2};
  11. sortColors(nums);
  12. }
  13. public static void sortColors(int[] nums) {
  14. //zero表示数字0的右侧边界,one、two分别表示1 和 2 的右侧边界
  15. int zero = 0;
  16. int one = 0;
  17. int two = 0;
  18. int length = nums.length;
  19. for(int i = 0; i < length; i++)
  20. {
  21. //记录下待处理的值
  22. int temp = nums[i];
  23. //不管怎样,我先给你填个2
  24. nums[two] = 2;
  25. two ++;
  26. // <=1的话 1的右侧边界one要向右挪一格
  27. if(temp <= 1)
  28. {
  29. nums[one] = 1;
  30. one ++;
  31. }
  32. //为0的话 0的右侧边界zero要向右挪一格
  33. if(temp == 0)
  34. {
  35. nums[zero] = 0;
  36. zero ++;
  37. }
  38. }
  39. //System.out.println(Arrays.toString(nums));
  40. }
  41. }

时间复杂度:O(n)

空间复杂度: O(1)

化简以上代码:

  1. package Sort;
  2. import java.util.Arrays;
  3. /**
  4. * @author : lkw
  5. * @data : 2021/3/22 9:21
  6. * @description :
  7. **/
  8. public class sortColors_03 {
  9. public static void main(String[] args) {
  10. int nums[] = {1, 1, 0, 2, 0, 1, 2, 2, 2};
  11. sortColors(nums);
  12. }
  13. public static void sortColors(int[] nums) {
  14. int zeao=0,one=0;
  15. for (int i=0;i< nums.length;i++){
  16. int tem=nums[i];
  17. nums[i]=2;
  18. if (tem<=1){
  19. nums[one++]=1;
  20. }
  21. if (tem==0){
  22. nums[zeao++]=0;
  23. }
  24. }
  25. //System.out.println(Arrays.toString(nums));
  26. }
  27. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/72818?site
推荐阅读
相关标签
  

闽ICP备14008679号