排序的练习

数组中的第k个元素

给定整数数组 nums 和整数 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

提示:

1 <= k <= nums.length <= 104
-104 <= nums[i] <= 104

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/kth-largest-element-in-an-array

很明显本题可以先进行排序,然后返回下标为nums.length - k的元素即可。
因此本体讲解的解题思路是快速选择

在计算机科学中,快速选择(英语:Quickselect)是一种从无序列表找到第k小元素的选择算法。它从原理上来说与快速排序有关。 同样地,它在实际应用是一种高效的算法,具有很好的平均时间复杂度,然而最坏时间复杂度则不理想。快速选择及其变种是实际应用中最常使用的高效选择算法。
快速选择的总体思路与快速排序一致,选择一个元素作为基准来对元素进行分区,将小于和大于基准的元素分在基准左边和右边的两个区域。不同的是,快速选择并不递归访问双边,而是只递归进入一边的元素中继续寻找。这降低了平均时间复杂度,从O(n log n)至O(n),不过最坏情况仍然是O(n2)。

与快速排序一样,快速选择一般是以原地算法的方式实现,除了选出第k小的元素,数据也得到了部分地排序

怎么实现快速选择不递归访问双边,而是递归进入一边的元素中继续寻找呢?

我们都知道在进行快速排序的时候,需要选出一个中间枢纽,确定这个中心枢纽的位置之后,此时中间枢纽已经将数组分成了两个子数组,然后进行递归进行快速排序,从而实现数组有序。在整个过程中,真正知道一个元素的下标的,只有枢纽这个数字。并且目标数字在排序之后的数组中的下标是nums.length - k,所以需要比较target = nums.length - k以及枢纽的下标mid来进行划分区间,因此基于这个原理,有:

  • target == mid,表示枢纽的下标等于目标数字的下标,因此直接返回nums[mid]就是前k个元素的值
  • 否则,在不相等的情况下,如果target > mid,表示目标数字的下标在枢纽的右边,所以在枢纽的右边进行递归进行快速选择,否则,target < mid,表示目标数字在枢纽的左边,进入枢纽的左边进入快速选择

对应的代码:

class Solution {
    /*
    利用快速选择(类似于快速排序)
    如果数组排好序之后,数组中的第k大元素对应的下标为nums.length - k
    同时,在利用快速排序的时候,可以唯一确定一个下标,这个下标就是中间枢纽
    的下标。所以只要判断最后返回的中间枢纽的下标是否等于nums.length - k这
    个下标即可,如果等于,直接返回这个数,否则如果小于,说明在枢纽的右边,
    此时更新left为mid + 1,否则更新right为mid - 1
    */
    public int findKthLargest(int[] nums, int k) {
           if(nums.length == 1)
              return nums[0];
           int left = 0,right = nums.length - 1,target = nums.length - k;
           while(true){
               int mid = quickSelection(nums,left,right);//获取[left,right]区间中间枢纽的下标
               if(mid == target)//如果mid等于第k大元素的下标,那么直接返回对应的值
                  return nums[mid];
                else if(mid > target)//中间枢纽的下标大于第k大元素的下标,表示目标元素在中间枢纽的左边,更新right,否则更新left
                  right = mid - 1;
                else
                  left = mid + 1;
           }
    }
    //进行快速排序,并将中间枢纽的下标返回
    public int quickSelection(int[] nums,int left,int right){
        if(left == right)//只有一个元素的时候,那么直接返回这个数,表示中间枢纽的下标
           return left;
        /*
        利用三数中值法获取中间枢纽的值,并且假设中间枢纽的下标位于high - 1
        ,所以退出循环时还需要进行交换,将i和high - 1两者进行交换
        */
        int pivot = getPivot(nums,left,right);
        int i = left,j = right - 1;
        while(i < j){
           while(i < j && nums[++i] <= pivot);
           while(i < j && nums[--j] >= pivot);
           if(i >= j)
             break;
           swap(nums,i,j);
        }
        swap(nums,i,right - 1);
        return i;
    }
    public int getPivot(int[] nums,int low,int high){
        int mid = (low + high) / 2;
        if(nums[low] > nums[mid])
          swap(nums,low,mid);
        if(nums[low] > nums[high])
          swap(nums,low,high);
        if(nums[mid] > nums[high])
          swap(nums,mid,high);
        swap(nums,mid,high - 1);
        return nums[high - 1];
    }
    public void swap(int[] nums,int i,int j){
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}

运行结果:
在这里插入图片描述

前k个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

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

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

提示:

1 <= nums.length <= 105
k 的取值范围是 [1, 数组中不相同的元素的个数]
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/top-k-frequent-elements

利用优先队列,从而实现元素按照频率降序列.需要注意优先队列中的泛型,如果只是一个Integer,那么导致在优先队列中仅仅含有次数,却没有与次数形成对应的元素,从而导致错误,所以定义的泛型是一个长度为2的一维数组,其中下标为0表示这个元素出现的次数,下标为1的值表示数组中的一个元素
对应的代码:

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        //获取数组中的最小值,然后减去这个最小值,从而使得所有元素都是非负数
        int min = Integer.MAX_VALUE,i;
        for(int num: nums){
            if(num < min)
              min = num;
        }
        int[] bucket = new int[100001];
        for(i = 0; i < nums.length; ++i){
            //考虑到元素可能含有负数,所以需要找到数组的最小值,然后每一个元素都减去这个最小值,从而保证了bucket的下标是非负数的 
            nums[i] -= min;
            bucket[nums[i]]++;
        }
        PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>(){
            /*
            定义一个优先队列,从而使得它是根据降序进行排序,因此跳出队列是 
            从队列头开始的,其中对应的泛型是一个长度2的一维数组,下标为0的
            值表示数字出现的次数,下标为1的值表示这个数字是数组中的哪一个
            元素
            如果时i1[0] - i2[0]则为升序排序,即按照频率升序进行排序,这时
            候跳出队列需要从队列为开始跳出
            */
            public int compare(int[] i1,int[] i2){
                return i2[0] - i1[0];
            }
        });
        for(i = 0; i < nums.length;++i ){
            if(bucket[nums[i]] != 0){
                /*
                int[]中下标为1的元素为nums[i] + min是因为在一开始的时候考虑元素存在负数的情况,
                如果不做任何措施,那么利用桶的时候导致下标发生越界,因此需要将每一个元素都减去min,从而
                保证bucket[num[i]]不会发生越界
                但是因为-=min,从而导致nums[i]的值发生了改变,所以添加的时候需要加上min恢复到原来的nums[i]值
                */
               queue.add(new int[]{bucket[nums[i]],nums[i] + min});
               bucket[nums[i]] = 0;//存在重复元素的时候,避免nums[i]这个数再次压入到队列中
            }
        }
        int[] res = new int[k];
        int j = 0;
        while(k > 0){
            res[j++] =  queue.poll()[1];
            k--;
        }
        return res;

    }
}

运行结果:
在这里插入图片描述

根据字符出现频率排序

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

示例 1:
输入:
“tree”
输出:
“eert”

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

示例 2:
输入:
“cccaaa”
输出:
“cccaaa”

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

示例 3:
输入:
“Aabb”
输出:
“bbAa”

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sort-characters-by-frequency

道理同上面一题,对应的代码:

class Solution {
    public String frequencySort(String s) {
         char[] chars = s.toCharArray();
         HashMap<Character,Integer> map = new HashMap<Character,Integer>();
         Integer count;
         //统计每一个字符出现得次数
         for(char ch: chars){
            count = map.get(ch);
            if(count == null){
                map.put(ch,1);
            }else{
                map.put(ch,count + 1);
            }
         }
         PriorityQueue<Object[]> queue = new PriorityQueue<Object[]>(new Comparator<Object[]>(){
              public int compare(Object[] o1,Object[] o2){
                  //按照那个降序进行排序,其中下标为0的元素是字符,下标为1的值表示字符出现的次数(由于两个下标对应的元素不同,所以泛型是Object[]数组)
                  return (int)o2[1] - (int)o1[1];
              }
         });
         //遍历哈希表,从而将哈希表中得元素添加到队列中
         for(Character ch: map.keySet()){
             queue.add(new Object[]{ch,map.get(ch)});
         }
         StringBuilder sb = new StringBuilder();
         Object[] tmp;
         int k;
         while(!queue.isEmpty()){
            tmp = queue.poll();
            k = (int)tmp[1];
            while(k > 0){
                sb.append(tmp[0]);
                k--;
            }
         }
         return sb.toString();
    }
}

运行结果:
在这里插入图片描述

在C++中,结构体排序练习通常涉及到如何使用标准库中的算法对包含自定义数据类型的结构体数组或容器进行排序。这里我们可以举一个简单的例子,假设有一个名为`Student`的结构体,包含`name`和`age`两个成员: ```cpp struct Student { std::string name; int age; }; ``` 你可以用以下几种方法对`Student`结构体数组进行排序: 1. **直接排序:**如果年龄是排序的主要依据,你可以定义一个比较函数(`compare`),然后使用`std::sort`函数: ```cpp bool compareStudents(const Student& s1, const Student& s2) { return s1.age < s2.age; } int main() { Student students[] = {{"Alice", 20}, {"Bob", 18}, {"Charlie", 22}}; std::sort(students, students + sizeof(students) / sizeof(students), compareStudents); // 现在students数组按年龄升序排列 } ``` 2. **使用STL算法:**如果你的结构体已经实现了`<`运算符,那么可以直接使用`std::stable_sort`: ```cpp bool studentLess(const Student& s1, const Student& s2) { return s1.age < s2.age; } int main() { std::vector<Student> students = {{"Alice", 20}, {"Bob", 18}, {"Charlie", 22}}; std::stable_sort(students.begin(), students.end(), studentLess); } ``` 3. **自定义比较器(C++11及以上):**也可以使用lambda表达式来创建一个可传递的比较器: ```cpp int main() { std::vector<Student> students = {{"Alice", 20}, {"Bob", 18}, {"Charlie", 22}}; std::sort(students.begin(), students.end(), [](const Student& s1, const Student& s2) { return s1.age < s2.age; }); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值