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

一、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

package Sort;

import java.util.Arrays;

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

    }
}

1.2.2 快排(不推荐使用)

package Sort;

/**
 * @author : lkw
 * @data : 2021/3/11 11:54
 * @description :215. 数组中的第K个最大元素(难度中等)
 * 方法二:快速排序
 **/
/*在程序运行开始前就知道数组的元素(所有的元素都是机前确定,并且不变),可以使用快速选择排序,
// 若是动态数据流的情况,则只能使用堆排序
    //快速选择:即快排中轴值计算的过程,小于轴值的放在左侧,大于的放于右侧
    一轮排序后,通过排序后轴值下标与k之间的关系,选定轴值左侧或右侧,进行下一步操作

  **/
public class findKthLargest_02 {
    public static void main(String[] args) {
        int[] nums = {23,46,20,8,11,18};
        System.out.println(findKthLargest(nums, 2));

    }
    public static int findKthLargest(int[] nums, int k){
        int left= 0;
        int right= nums.length-1;
        int target = nums.length-k;
        while (true){
            int index = partition(nums,left,right);

            if (index==target){
                return nums[index];
            }
            else if(index<target){
                left=index+1;
            }
            else  right=index-1;
        }


    }

    public static int partition(int[] nums,int left,int right){
        int pivot = nums[left];
        int j=left;
        for (int i = left+1; i <= right; i++) {
            if (nums[i]<pivot){
                j++;
                swap(nums,i,j);
            }
        }
        swap(nums,j,left);
    return j;

    }
    public static void swap(int[] nums,int i,int j){
        int temp = nums[i];
        nums[i]= nums[j];
        nums[j] = temp;
    }
}

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大元素。

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

package Sort;

/**
 * @author : lkw
 * @data : 2021/3/11 12:14
 * @description :优先队列(1)
 **/

import java.util.PriorityQueue;
import java.util.Random;

/**
 * 创建一个为len的小顶堆,输出len-k个元素后,堆中剩余k个最大元素,则堆顶是则是第k大元素
 */
public class findKthLargest_03 {
    public static void main(String[] args) {
        int[] nums = {23,20,0,8,11,18};
        System.out.println(findKthLargest(nums, 2));
    }



    public static int findKthLargest(int[] nums, int k) {
        int len = nums.length;
        PriorityQueue<Integer> priorityQueue= new PriorityQueue<>(len,(a,b)->(a-b));
        for (int i = 0; i <len ; i++) {
            priorityQueue.add(nums[i]);

        }
        for (int i = 0; i < len-k; i++) {
         priorityQueue.poll();
        }
        return priorityQueue.peek();
    }
}


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

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

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

可以参考:力扣 

package Sort;

import java.util.PriorityQueue;

/**
 * @author : lkw
 * @data : 2021/3/12 10:11
 * @description :
 **/
public class findKthLargest_03 {
    public static void main(String[] args) {
        //int[] nums = {23,46,20,8,11,18};
        int[] nums = {3,2,1,5,6,4};
        System.out.println(findKthLargest(nums, 2));

    }
    public static int findKthLargest(int[] nums, int k) {
/*优先队列思想:数组长度为len,输出第k大元素,即数组排序后[0...len-k-1][len-k...len-1],后半段k个元素中最小的元素
 后半段k个元素中最小的元素,可以创建一个容量为k的小顶堆,最后输出堆顶元素。
 但是现在数组时乱序:我们可以创建一个k的小顶堆,始终保持有k个
 则先放入k个元素,堆满之后若此时元素若大于堆顶则先弹出在放入

若我们创建一个k+1的小顶堆,先放入k个元素
再将待插入的元素放入小顶堆,此时弹出一个堆顶元素,此时堆内还是k个最小元素
         */
        PriorityQueue<Integer> priorityQueue= new PriorityQueue<>(k+1,(a,b)->(a-b));
        //先放入k个元素
        for (int i = 0; i < k; i++) {
            priorityQueue.add(nums[i]);

        }
        for (int i = k; i < nums.length; i++) {
            priorityQueue.add(nums[i]);
            priorityQueue.poll();

        }
        return priorityQueue.peek();

    }
}

//大顶堆
public static int findKthLargest(int[] nums, int k) {
        int len = nums.length;
        PriorityQueue<Integer>  priorityQueue= new PriorityQueue<>(len,(a,b)->(b-a));//大顶堆
        for (int i = 0; i < len; i++) {
            priorityQueue.add(nums[i]);
        }
        for (int i = 0; i < k-1; i++) {
            priorityQueue.poll();
        }
        return priorityQueue.peek();
    }

以下为桶排序的三个题

二、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

package Sort;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * @author : lkw
 * @data : 2021/3/15 9:06
 * @description :347. 前 K 个高频元素
 **/
public class topKFrequent_01 {
    //思路一:拿一个数组n[k+1]记录0-k的次数,然后比较数组n[k+1]的大小

    /*
    Map.get() 方法返回指定键所映射的值
    Map.keySet() 方法将获取 Map 集合的所有键名
    Add方法是将传入的参数作为当前List中的一个item存储,即使你传入一个List也只会令当前的List增加1个元素
    AddAll是传入一个List,将此List中的所有元素加入到当前List中,也就是当前List会增加的元素个数为传入的List的大小

     */
//1.先统计频率
    public static void main(String[] args) {
        int[] nums = {1,1,1,2,2,3};
        System.out.println(topKFrequent(nums, 2));


    }
    public static int[] topKFrequent(int[] nums, int k) {
        List<Integer> reslist = new ArrayList();
        //使用map存入的是键值对,统计每个元素出现的频率,元素为键,频率为值
        HashMap<Integer,Integer> map = new HashMap();
        for (int num:nums){
            if (map.containsKey(num)) {
                map.put((Integer)num,map.get(num) + 1);
            }
            else
                map.put((Integer)num,1);
            }
    //桶排序,频率作为数组下标,对于出现频率不同的元素,存入对于的数组下标
        //有n+1个桶,因为元素出现的频率可能有0次,1次,2次.....n次 , 一共有n+1种可能
        //
        List<Integer>[] list = new List[nums.length+1];//创建了一个元素都为List型的数组
        for (int key: map.keySet()){
            //i为出现的频率
            int i = map.get(key);
            if (list[i]==null){
                //防止空指针,防止i第一次进来的时候报错
                //因为频率相同也可放入,因为array[i]也是一个数组列表
                list[i]=new ArrayList<>();
            }
            //创建好数组列表后,再将数值放入
            list[i].add((Integer)key);//注意:这里不是else
        }
//倒序遍历数组,获得频率由大到小的次序
        for (int i = list.length-1;i>=0 && reslist.size()<k;i--){
            if (list[i]==null) continue;
            reslist.addAll(list[i]);//为啥不是else?
        }
        System.out.println(reslist);
            int[] resArray = new int[reslist.size()];
       // int[] res = new int[k];
        for (int i = 0; i < k; i++) {
            resArray[i] = reslist.get(i);
        }

        return resArray;

    }

}

 时间复杂度:遍历数组,统计频率的时复为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题为一个思路

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

package Sort;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * @author : lkw
 * @data : 2021/3/21 17:20
 * @description :451. 根据字符出现频率排序(中等)
 **/
public class frequencySort_01 {
    public static void main(String[] args) {
        String s = "cccaaaabcdw";
        frequencySort(s);
    }
    public static String frequencySort(String s) {
        char array[] = s.toCharArray();
        //键为:char,值为频率
        HashMap<Character, Integer> map = new HashMap();
        for (int i = 0; i < array.length; i++) {
            char key = array[i];
            if (map.containsKey(key)) {
                map.put(key, map.get(key) + 1);
            } else
                map.put(key, 1);
        }

        //System.out.println(map);//{r=1, t=1, e=2}

        //创建桶
        //有n+1个桶,因为元素出现的频率可能有0次,1次,2次.....n次 , 一共有n+1种可能
        //第i个桶存出现频率为i的字符
        ArrayList<Character>[] list = new ArrayList[array.length + 1];
        for (char key : map.keySet()) {
            int index = map.get(key);
            if (list[index] == null) {
                list[index] = new ArrayList();
            }
            list[index].add(key);
        }
        System.out.println(list[3]);

        //组合字符
        //建议使用StringBuilder,执行效率高
//        String a = "";
        //倒序扫描桶,因为频率高的在最后面
        StringBuilder res = new StringBuilder();
        for (int i=list.length-1;i>=0;i--) {
            if (list[i] != null) {
                for (char ch:list[i]){
                    for (int j = 0; j < i; j++) {
                        //a=a+ch;
                        res.append(ch);
                    }
                }
            }

        }

        //System.out.println(a);
        //return a;
        System.out.println(res.toString());
       return res.toString();
    }

}


时间复杂度: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 桶排序

package Sort;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

/**
 * @author : lkw
 * @data : 2021/3/21 20:36
 * @description :75. 颜色分类
 * 桶排序
 **/
public class sortColors_01 {
    public static void main(String[] args) {
        int nums[] = {1,1,0,2,0,1,2,2,2};
        sortColors(nums);
    }

    public static void sortColors(int[] nums) {
        //统计频率,hashmap,key:数值,value:频率
        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);

        }
        //System.out.println(map);
        
        
        int t=0;
        for (int key: map.keySet()){
            for (int i =0;i<map.get(key);i++){
                nums[t++]=key;
            }
        }
        //System.out.println(Arrays.toString(nums));

    }
}

时间复杂度: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,会被覆盖掉。

package Sort;

import java.util.Arrays;

/**
 * @author : lkw
 * @data : 2021/3/21 21:28
 * @description :数组
 **/
public class sortColors_02 {
    public static void main(String[] args) {
        int nums[] = {1, 1, 0, 2, 0, 1, 2, 2, 2};
        sortColors(nums);
    }

    public static void sortColors(int[] nums) {
        //zero表示数字0的右侧边界,one、two分别表示1 和 2 的右侧边界
        int zero = 0;
        int one = 0;
        int two = 0;


        int length = nums.length;

        for(int i = 0; i < length; i++)
        {
            //记录下待处理的值
            int temp = nums[i];

            //不管怎样,我先给你填个2
            nums[two] = 2;
            two ++;

            // <=1的话 1的右侧边界one要向右挪一格
            if(temp <= 1)
            {
                nums[one] = 1;
                one ++;
            }

            //为0的话 0的右侧边界zero要向右挪一格
            if(temp == 0)
            {
                nums[zero] = 0;
                zero ++;
            }
        }
        //System.out.println(Arrays.toString(nums));
    }
}

时间复杂度:O(n)

空间复杂度: O(1)

化简以上代码:

package Sort;

import java.util.Arrays;

/**
 * @author : lkw
 * @data : 2021/3/22 9:21
 * @description :
 **/
public class sortColors_03 {
    public static void main(String[] args) {
        int nums[] = {1, 1, 0, 2, 0, 1, 2, 2, 2};
        sortColors(nums);
    }

    public static void sortColors(int[] nums) {
        int zeao=0,one=0;
        for (int i=0;i< nums.length;i++){
            int tem=nums[i];
            nums[i]=2;

            if (tem<=1){
                nums[one++]=1;
            }
            if (tem==0){
                nums[zeao++]=0;
            }

        }
        //System.out.println(Arrays.toString(nums));

    }

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值