LeetCode有代表性的题解---排序问题(二)

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

在未排序的数组中找到第 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 ≤ 数组的长度。

法一:冒泡排序,从大到小排序定位第K-1个

class Solution {
    public int findKthLargest(int[] nums, int k) {
        int tempNum;
        //冒泡排序从大到小排序
        for(int i=0;i<nums.length;i++){
            for(int j=0;j<nums.length-i-1;j++){
                if(nums[j]<nums[j+1]){
                    //交换
                    tempNum=nums[j];
                    nums[j]=nums[j+1];
                    nums[j+1]=tempNum;
                }
            }
        }
        return(nums[k-1]);
    }
}

冒泡排序复杂度:时间复杂度 O(N*N),空间复杂度 O(1),稳定

法二:快速排序,从大到小排序定位第K-1个

//2.快速排序
    public static void quickSort(int[] nums,int low,int high){
        if (low<high){
           int index=quickSort_findIndex(nums,low,high);
            quickSort(nums,0,index-1);
            quickSort(nums,index+1,high);
        }
    }
    public static int quickSort_findIndex(int[] nums, int low, int high) {
        int tenmpNum=nums[low];
        while (low<high){
            while (low<high&&nums[high]<=tenmpNum)//注意此处必须加等于,否则出现相同值时可能无限循环
                high--;
            nums[low]=nums[high];
            while (low<high&&nums[low]>=tenmpNum)//注意此处必须加等于,否则出现相同值时可能无限循环
                low++;
            nums[high]=nums[low];
        }
        nums[low]=tenmpNum;
        return low;
    }

快速排序复杂度:时间复杂度 O(N*logN),空间复杂度 O(1),不稳定

法三:快速选择排序

复杂度:时间复杂度 O(N),空间复杂度 O(1)

public int findKthLargest(int[] nums, int k) {
    k = nums.length - k;
    int l = 0, h = nums.length - 1;
    while (l < h) {
        int j = quickSort_findIndex(nums, l, h);
        if (j == k) {//如果相等则找到,当前位置的值即为第K大的值
            break;
        } else if (j < k) {//在右部分找
            l = j + 1;
        } else {//在左部分找
            h = j - 1;
        }
    }
    return nums[k];
}
public static int quickSort_findIndex(int[] nums, int low, int high) {
        int tenmpNum=nums[low];
        while (low<high){
            while (low<high&&nums[high]>=tenmpNum)//注意此处必须加等于,否则出现相同值时可能无限循环
                high--;
            nums[low]=nums[high];
            while (low<high&&nums[low]<=tenmpNum)//注意此处必须加等于,否则出现相同值时可能无限循环
                low++;
            nums[high]=nums[low];
        }
        nums[low]=tenmpNum;
        return low;
}

347. 前 K 个高频元素

给定一个非空的整数数组,返回其中出现频率前 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 是数组的大小。(此处说明无法使用排序实现,因为排序最优也得O(n log n))

方法说明:

        题目最终需要返回的是前 k个频率最大的元素,可以想到借助堆这种数据结构,对于 k频率之后的元素不用再去处理,进一步优化时间复杂度。

具体操作为:

1.借助 哈希表 来建立数字和其出现次数的映射,遍历一遍数组统计元素的频率
2.维护一个元素数目为 k 的最小堆(小顶堆)
3.每次都将新的元素与堆顶元素(堆中频率最小的元素)进行比较
        如果新的元素的频率比堆顶端的元素大,则弹出堆顶端的元素,将新的元素添加进堆中
4.最终,堆中的 k 个元素即为前 k 个高频元素

Java代码如下:

class Solution {
    public List<Integer> topKFrequent(int[] nums, int k) {
        //1.先创建hashmap存储每个值及其对应的频率
        Map<Integer, Integer> hash_map = new HashMap();
        for (int num : nums) {
            hash_map.put(num, hash_map.getOrDefault(num, 0) + 1);
        }
        //2.创建小顶堆
        PriorityQueue<Integer> pq = new PriorityQueue(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return hash_map.get(o1)-hash_map.get(o2);//返回值小于0交换数a和数b的位置,否则相反;数a数又边的数,数b是左边的数
            }
        });

        //3.建堆
        for(int key:hash_map.keySet()){
            if (pq.size()<k){//如果小顶堆中的个数小于K,直接添加
                pq.add(key);
            }else if(hash_map.get(key)>hash_map.get(pq.peek())){//如果小顶堆中已有k个数值,但是当前数值比堆顶的数值大则删除堆顶的数值,将当前数值添加到堆中
                pq.poll();//返回并且删除堆顶元素——队列为空的时候返回null
                pq.add(key);
            }
        }

        //4.用一个List存放堆中的k个值
        List<Integer> list=new ArrayList<>();
        while (!pq.isEmpty())
            list.add(pq.poll());
        return  list;
        
    }
}

复杂度分析
时间复杂度:O(nlogk),n 表示数组的长度。首先,遍历一遍数组统计元素的频率,这一系列操作的时间复杂度是O(n);接着,遍历用于存储元素频率的 map,如果元素的频率大于最小堆中顶部的元素,则将顶部的元素删除并将该元素加入堆中,这里维护堆的数目是 k,所以这一系列操作的时间复杂度是 O(nlogk) 的;因此,总的时间复杂度是O(nlog⁡k)。
空间复杂度:O(n),最坏情况下(每个元素都不同),map 需要存储 n 个键值对,优先队列需要存储 k个元素,因此,空间复杂度是 O(n).

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

给定一个字符串,请将字符串里的字符按照出现的频率降序排列。

示例 1:

输入:
"tree"

输出:
"eert"

解释:
'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。
示例 2:

输入:
"cccaaa"

输出:
"cccaaa"

解释:
'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。
示例 3:

输入:
"Aabb"

输出:
"bbAa"

解释:
此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意'A'和'a'被认为是两种不同的字符。

解题思想:本题和leetcode347. 前k个高频元素实际上是一个类型的,都可以利用桶排序来高效解决。步骤如下:

1.遍历字符数组,将每个字符的和它对应的频次存入哈希表中。
2.新建一个桶,将每个字符存入索引为它的频次的那个桶中。由于桶的索引本身就是自增的,因此这样就直接利用桶完成了对每个字符按照它的出现次数进行了从大到小的排序。
3.倒着遍历桶,将每个桶里的元素取出来,并按照它的频次存入要返回的结果中

JAVA代码如下:

class Solution {
    public String frequencySort(String s) {
        char[] chs = s.toCharArray();
        //1.用一个HashMap统计每个字符出现的频率
        Map<Character,Integer> frequencyMap=new HashMap<>();
        for(char c:s.toCharArray()){
            //统计每个字符出现的频率
            frequencyMap.put(c,frequencyMap.getOrDefault(c,0)+1);
        }
        //2.利用桶存储字符和字符出现的频率,频率即为桶的下标
        List<Character>[] frequencyBucket= new ArrayList[s.length()+1];
        for(char c:frequencyMap.keySet()){
            //得到当前字符对应的频率
            int frequencyValue=frequencyMap.get(c);
            if (frequencyBucket[frequencyValue]==null) {
                frequencyBucket[frequencyValue] = new ArrayList<>();
            }
                frequencyBucket[frequencyValue].add(c);
        }
        //3.倒序取出桶中的字符以及其对应的下标(字符出现的频率)
        List<Character> list=new ArrayList<>();
        int p=0;
        for (int i=frequencyBucket.length-1;i>0;i--){
            if(frequencyBucket[i]==null)
                continue;
            for (char c:frequencyBucket[i]){
                for (int j=0;j<i;j++) chs[p++]=c;
            }
        }
        return new String(chs) ;
    }
}

75. 颜色分类

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

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

注意:
不能使用代码库中的排序函数来解决这道题。

示例:

输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
进阶:

一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。
你能想出一个仅使用常数空间的一趟扫描算法吗?

分析:本问题被称为 荷兰国旗问题,最初由 Edsger W. Dijkstra提出。其主要思想是给每个数字设定一种颜色,并按照荷兰国旗颜色的顺序进行调整。

算法:

1.初始化0的最右边界:p0 = 0。在整个算法执行过程中 nums[idx < p0] = 0.

2.初始化2的最左边界 :p2 = n - 1。在整个算法执行过程中 nums[idx > p2] = 2.

3.初始化当前考虑的元素序号 :curr = 0.

4.While curr <= p2 :

      若 nums[curr] = 0 :交换第 curr个 和 第p0个 元素,并将指针都向右移

      若 nums[curr] = 2 :交换第 curr个和第 p2个元素,并将 p2指针左移

      若 nums[curr] = 1 :将指针curr右移

JAVA代码如下:

class Solution {
    public void sortColors(int[] nums) {
        int p1=0;
        int p2=nums.length-1;
        int cur=0;
        int tempNum;
        while(cur<=p2){//注意点1:cur要小于等于p2,否则已经换好位置的2可能再经遍历又被换回去
            if(nums[cur]==2){
                tempNum=nums[p2];
                nums[p2]=nums[cur];
                nums[cur]=tempNum;
                p2--;
            }
            else if(nums[cur]==0){
                tempNum=nums[p1];
                nums[p1]=nums[cur];
                nums[cur]=tempNum;
                p1++;
                cur++;//注意点2:此处必须cur++
            }else
                cur++;

        }
        
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章