zoukankan      html  css  js  c++  java
  • LeetCode排序专题【算法】

    快速选择

    用于求解 Kth Element 问题,也就是第 K 个元素的问题。

    可以使用快速排序的 partition() 分治进行实现。需要先打乱数组,否则最坏情况下时间复杂度为 O(N2)。

    关于快速排序:

    https://blog.csdn.net/nrsc272420199/article/details/82587933(例子和总结很好,是评论区说代码有问题0改成low,但是换成low我反而不理解了。。。嗷,理解了,后半段可能还需要分两段,后半段分两段后的前半段就不是从0开始了

    https://blog.csdn.net/morewindows/article/details/6684558 (这个也好理解,挖坑填数最后分治)

    1个temp存第一个元素的数据作为基准数,排他!!!一个low指针的数据,还有一个high指针从尾部开始。

    首先从后面hign指针开始,如果nums[high] 大于temp(后面没有值对应的变量名,用索引代替),则high-1左移。若nums[high]小于temp则把nums[high]赋值给nums[low],即nums[low]=nums[high] ,赋值的话high不动不用左移!!     是赋值给nums[low]不是temp!!!!temp是不变的一直是第一个元素的值!!!

    前边nums[low]要是被后面小的nums[high]赋值以后的话,就要low指针右移1位,开始从前往后判断。如果此时low指针指向的元素小于nums[high],则low指针继续右移。若nums[low]大于nums[high],则把nums[low]赋给nums[high],即nuns[high]=nums[low],赋值操作low不用右移。

    然后再开始从前往后遍历,直到low=high结束循环,此时low或high的下标就是基准数据23在该数组中的正确索引位置.

    这样一遍走下来,可以很清楚的知道,其实快速排序的本质就是把基准数大的都放在基准数的右边,把比基准数小的放在基准数的左边,这样就找到了该数据在数组中的正确位置.
      以后采用递归的方式分别对前半部分和后半部分排序,当前半部分和后半部分均有序时该数组就自然有序了。

    用于求解 TopK Elements 问题,也就是 K 个最小元素的问题。可以维护一个大小为 K 的最小堆,最小堆中的元素就是最小元素。最小堆需要使用大顶堆来实现,大顶堆表示堆顶元素是堆中最大元素。这是因为我们要得到 k 个最小的元素,因此当遍历到一个新的元素时,需要知道这个新元素是否比堆中最大的元素更小,更小的话就把堆中最大元素去除,并将新元素添加到堆中。所以我们需要很容易得到最大元素并移除最大元素,大顶堆就能很好满足这个要求。

    堆也可以用于求解 Kth Element 问题,得到了大小为 k 的最小堆之后,因为使用了大顶堆来实现,因此堆顶元素就是第 k 大的元素

    快速选择也可以求解 TopK Elements 问题,因为找到 Kth Element 之后,再遍历一次数组,所有小于等于 Kth Element 的元素都是 TopK Elements。

    可以看到,快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题。

    https://mp.weixin.qq.com/s?__biz=MzIxMjE5MTE1Nw==&mid=2653195207&idx=2&sn=12689c6c1a92e7ec3cce4d423019ec2a&chksm=8c99f91dbbee700b8e760d06b27582037ab0713295dacf2b5a7a7f954c0032fe860aa0bf8b74&scene=21#wechat_redirect什么是二叉堆?

    https://mp.weixin.qq.com/s?__biz=MzIxMjE5MTE1Nw==&mid=2653195208&idx=1&sn=e3d6559402148458f0a4993b47d8bc6f&chksm=8c99f912bbee7004625a0b204acc8484acbdf4f1b18953e7ff5acbea958ec002d8c8ea072792&scene=21#wechat_redirect什么是堆排序?

    https://mp.weixin.qq.com/s?__biz=MzIxMjE5MTE1Nw==&mid=2653195301&idx=1&sn=9d380bbf3c507ab684ff51596673268f&chksm=8c99feffbbee77e97fe047bdfd4afa8084f09d28df8516a6436207c13fed2c8c5366a8c5677e&scene=21#wechat_redirect (什么是优先队列?

    做过的一个 优先队列题378和一个我以为用优先队列但不是的题(?)。。。(复习一下)

    1. Kth Element

    215. Kth Largest Element in an Array (Medium)

    Leetcode / 力扣

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

    Input: [3,2,1,5,6,4] and k = 2
    Output: 5
    

    题目描述:找到倒数第 k 个的元素。

    378题是第k小,这里是找第k大。。也可以用最大优先队列自动排序

    方法一:堆排序

    class Solution {
        public int findKthLargest(int[] nums, int k) {
            PriorityQueue<Integer> MaxPQ = new PriorityQueue<>(Collections.reverseOrder());
            for(int num:nums){
                MaxPQ.add(num);//先全给他加进去会自动排序 最大的在外面
                  }
    
            for(int i = 1;i<k;i++){//把第k个前面的全出了
                MaxPQ.remove();
            }
            return MaxPQ.remove();//第k个就找到了
        }
    }

    这里用最大优先队列,把比k位小的们都放在队列里,大的出掉了。

    若改成用最小优先队列(最小堆),则应该是放入的是比k位大的们,则堆顶是最小的k位了。

    看答案也太。。。Arrays.sort()用一下就出来。。。(JDK默认使用快速排序

    方法二:排序

        public int findKthLargest(int[] nums, int k) {
            int len = nums.length;
            Arrays.sort(nums);
            return nums[len - k];
        }

    方法三:自己用代码实现快速排序的分治

    借助 partition 操作定位到最终排定以后索引为 len - k 的那个元素(特别注意:随机化切分元素)

    以下的描述基于 “快速排序” 算法知识的学习,如果忘记的朋友们可以翻一翻自己的《数据结构与算法》教材,复习一下,partition 过程、分治思想和 “快速排序” 算法的优化。

    分析:我们在学习 “快速排序” 的时候,接触的第 1 个操作就是 partition(切分),简单介绍如下:

    其中 partition 分区函数会任意选择一个元素作为 pivot(分区点),将数组中小于 pivot 的点都放置到其左边,将大于pivot的点都放置在其右边,最终 partition 函数返回 pivot 的下标 i

    partition(切分)操作,使得:

    对于某个索引 j,nums[j] 已经排定,即 nums[j] 经过 partition(切分)操作以后会放置在它 “最终应该放置的地方”
    nums[left] 到 nums[j - 1] 中的所有元素都不大于 nums[j];
    nums[j + 1] 到 nums[right] 中的所有元素都不小于 nums[j]。

    partition(切分)操作总能排定一个元素,还能够知道这个元素它最终所在的位置,这样每经过一次 partition(切分)操作就能缩小搜索的范围,这样的思想叫做 “减而治之”(是 “分而治之” 思想的特例)。

    切分过程可以不借助额外的数组空间,仅通过交换数组元素实现。

    顺便回忆一下”快排“的实现

    quick_sort(int[] arr, int low, int high) {
    if (low >= high) return;
    int i = partition(arr, low, high);//返回pivot的正确索引值
    quick_sort(arr, low, i-1);
    quick_sort(arr, i+1, high);
    }

    该题答案:

    class Solution {
        //比较j与处理后的k(正序),分割知道j==k
        public int findKthLargest(int[] nums, int k) {
        k = nums.length - k;
        int l = 0, h = nums.length - 1;
        while (l < h) {
            int j = partition(nums, l, h);//得到pivot的正确索引值
            if (j == k) {
                break;
            } else if (j < k) {
                l = j + 1;//选后半段
            } else {
                h = j - 1;//选前半段
            }
        }
        return nums[k];
        }
        //在l~h范围内分割,找到位置为j的数  有点没看懂。。。
        private int partition(int[] a, int l, int h) {
            int i = l, j = h + 1;
            while (true) {
                while (a[++i] < a[l] && i < h) ;//这里是啥?????交换还是just指针动?
                while (a[--j] > a[l] && j > l) ;
                if (i >= j) {
                    break;
                }
            swap(a, i, j);//???
        }
        swap(a, l, j);//????
        return j;
        }
        //交换a[i]与a[j]
        private void swap(int[] a, int i, int j) {
            int t = a[i];
            a[i] = a[j];
            a[j] = t;
        }
    }

    没看懂。。。。???

    桶排序

    1. 出现频率最多的 k 个元素

    Leetcode题号:347 难度:Medium

    链接:https://leetcode-cn.com/problems/top-k-frequent-elements/description/

    题目描述:

    给定一个非空的整数数组,返回其中出现频率前 高的元素。

    写半天结果还是错的。。。就悟出了一个数组中加元素的道理

            int[] arr = new int[map.size()];
            for(int x :map.keySet()){
                for(int i = 0;i<map.size();i++){
                    arr[i] = map.get(x);
                }
            }
    数组的初始化
    你可以在声明数组的同时进行初始化(静态初始化),
    也可以在声明以后进行初始化(动态初始化)。例如:
     
    // 静态初始化
    // 静态初始化的同时就为数组元素分配空间并赋值
    int intArray[] = {1,2,3,4};
    String stringArray[] = {"微学苑", "http://www.weixueyuan.net", "一切编程语言都是纸老虎"};
    // 动态初始化
    float floatArray[] = new float[3];
    floatArray[0] = 1.0f;
    floatArray[1] = 132.63f;
    floatArray[2] = 100F;

    题目答案:

    整道题思路可以分为3步:

    1.将数组中的数按照 数字--出现频数 的格式存放在一个HashMap中。

    2.将该HashMap中的内容放入一组桶中,其中,这组桶代表一个大ArrayList(第一次见这操作!)。这组桶的每一个元素都是一个桶(每个桶都是一个ArrayList),它的下标(索引)表示频数。每个桶都存放着对应该频数的数字。若同一频数的数字有多个,那么对应的桶中就会有多个数。

    3.最后,从索引最大的桶开始,取出频数最大的k个数字即可。

    上面是map 下面是桶,桶都是几到几一组的,比如这里是频率1到5的桶都有

    注意其中用到的一些方法:

    HashMap的getOrDefault(key,default)表示如果存在key对应的value,就返回该value,若不存在该key,就返回default位置的值。

    ArrayList的addAll()方法表示括号里的所有元素都添加到ArrayList中。

    class Solution {
        public List<Integer> topKFrequent(int[] nums, int k) {
            //存放 数字-数字对应的频数
            Map<Integer,Integer> f = new HashMap<>();
            for (int num : nums) {
                f.put(num, f.getOrDefault(num, 0) + 1);
            }
            //桶:桶的索引表示频数,每个索引位置放入对应频数的数字
            List<Integer>[] buckets = new ArrayList[nums.length + 1];//因为下标设置成了从1开始?可是长度为啥加1??
            for (int key : f.keySet()) {
                int frequency = f.get(key);//把map的计数值们都取出赋给了桶的索引值
                if (buckets[frequency] == null) {
                    //因为可能存在频数相同的数字,因此每个索引位置都是一个数组
                    buckets[frequency] = new ArrayList<>();//桶的索引对应的值们存在arraylist里面
                }
                //根据桶的索引(频数),放入对应的数字(key)
            buckets[frequency].add(key);//这个arraylist里面是map的key即输入数组元素值
            }
            List <Integer> topK = new ArrayList<>();
            //从桶的最后一个元素开始遍历,往topK数组里面放频率最大的数,放满k个停止
            for (int i = buckets.length - 1; i >= 0 && topK.size() < k; i--) {
                //如果频率i没有对应的数,就跳过找下一个频率
                if (buckets[i] == null) {
                continue;
                }
                //当前频率的数的个数小于k,就将这些数全部放入topK
                if (buckets[i].size() <= (k - topK.size())) {
                topK.addAll(buckets[i]);
                }  else {
                //topK还剩几个位子,就放几个数
                topK.addAll(buckets[i].subList(0, k - topK.size()));
                }
            }
        return topK;
        }
    }

    可是leetcode上返回的是int,需要转换一下,就是按索引遍历给数组一个一个加元素。

        public int[] topKFrequent(int[] nums, int k) {
            Map<Integer,Integer> map = new HashMap<>();
            for(int num:nums){
                map.put(num,map.getOrDefault(num,0)+1);
            }
    
             //桶排序
            //将频率作为数组下标,对于出现频率不同的数字集合,存入对应的数组下标
            List<Integer>[] list = new List[nums.length+1];//new一组桶List 
            for(int key : map.keySet()){
                // 获取出现的次数作为下标
                int i = map.get(key);
                if(list[i] == null){
                   list[i] = new ArrayList();//每个索引对应的还是list
                } 
                list[i].add(key);
            }
            
         // 倒序遍历数组获取出现顺序从大到小的排列
            List<Integer> res = new ArrayList();
            for(int i = list.length - 1;i >= 0 && res.size() < k;i--){
                if(list[i] == null) continue;
                res.addAll(list[i]);
            }
    
    
             int[] listt = new int[res.size()];
            int idx=0;
            for(int num:res){
                listt[idx++]= num;
            }
            return listt;
    
        }

    2. 按照字符出现次数对字符串排序

    451. Sort Characters By Frequency (Medium)

    Leetcode / 力扣

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

    示例 1:

    输入:
    "tree"

    输出:
    "eert"

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

    class Solution {
        public String frequencySort(String s) {
            Map<Character,Integer> map = new HashMap<>();
            //遍历String
            for (char c : s.toCharArray()){
            map.put(c, map.getOrDefault(c, 0) + 1);
             }
             List<Character>[] Bucket = new List[s.length()+1];//又是长度要加1 是个List们的数组们
             for(char c:map.keySet()){
                 int f = map.get(c);//索引放入计数
                 if(Bucket[f] == null){
                     Bucket[f] = new ArrayList<>();
                 }
                 Bucket[f].add(c);
             }
            //从桶中索引最大的元素开始,依次取出字母放入可变字符串str中
             StringBuilder str = new StringBuilder();
             for (int i = Bucket.length - 1; i >= 0; i--) {
               if (Bucket[i] == null) {
                     continue;
                 }
                for (char c : Bucket[i]) {
                    for (int j = 0; j < i; j++) {
                         str.append(c);
                         }             
                     } 
                }   
             return str.toString();
    
        }
    }

    荷兰国旗问题

    荷兰国旗包含三种颜色:红、白、蓝。

    有三种颜色的球,算法的目标是将这三种球按颜色顺序正确地排列。它其实是三向切分快速排序的一种变种,在三向切分快速排序中,每次切分都将数组分成三个区间:小于切分元素、等于切分元素、大于切分元素而该算法是将数组分成三个区间:等于红色、等于白色、等于蓝色

    1. 按颜色进行排序

    75. Sort Colors (Medium)

    Leetcode / 力扣

    Input: [2,0,2,1,1,0]
    Output: [0,0,1,1,2,2]

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

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

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

    示例:

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

    答案:

    1、len 是数组的长度;
    2、变量 zero 是前两个子区间的分界点,一个是闭区间,另一个就必须是开区间;
    3、变量 i 是循环变量,一般设置为开区间,表示 i 之前的元素是遍历过的
    4、two 是另一个分界线,我设计成闭区间。

    public class Solution {
    
        public void sortColors(int[] nums) {
            int len = nums.length;
            if (len < 2) {
                return;
            }
            // all in [0, zero) = 0
            // all in [zero, i) = 1
            // all in [two, len - 1] = 2
            
            // 循环终止条件是 i == two,那么循环可以继续的条件是 i < two
            // 为了保证初始化的时候 [0, zero) 为空,设置 zero = 0,
            // 所以下面遍历到 0 的时候,先交换,再加
            int zero = 0;//占着最前面
            // 为了保证初始化的时候 [two, len - 1] 为空,设置 two = len
            // 所以下面遍历到 2 的时候,先减,再交换
            int two = len-1;//占着最后面
            int i = 0;
            // 当 i == two 上面的三个子区间正好覆盖了全部数组
            // 因此,循环可以继续的条件是 i <= two
            while (i < =two) {//有点分治那意思,小的换前面去,大的换后面去
                if (nums[i] == 0) {
                    swap(nums, i, zero);// i遇到0 把0换前面去。值交换,索引没有换,向右推1步
                    zero++;
                    i++;
                } else if (nums[i] == 1) {
                    i++;//不管
                } else {//i遇到2 把2换后面去                swap(nums, i, two);
             two--;
    } } }
    private void swap(int[] nums, int index1, int index2) {//传入的索引指针 int temp = nums[index1]; nums[index1] = nums[index2]; nums[index2] = temp; } } 作者:liweiwei1419 链接:https://leetcode-cn.com/problems/sort-colors/solution/kuai-su-pai-xu-partition-guo-cheng-she-ji-xun-huan/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。



  • 相关阅读:
    Python爬虫入门教程 45-100 Charles抓取兔儿故事-下载小猪佩奇故事-手机APP爬虫部分
    Python爬虫入门教程 44-100 Charles的安装与使用-手机APP爬虫部分
    Python爬虫入门教程 42-100 爬取儿歌多多APP数据-手机APP爬虫部分
    Python爬虫入门教程 43-100 百思不得姐APP数据-手机APP爬虫部分
    Python爬虫入门教程 41-100 Fiddler+夜神模拟器+雷电模拟器配置手机APP爬虫部分
    Python爬虫入门教程 40-100 博客园Python相关40W博客抓取 scrapy
    Python爬虫入门教程 39-100 天津市科技计划项目成果库数据抓取 scrapy
    Python爬虫入门教程 38-100 教育部高校名单数据爬虫 scrapy
    Python爬虫入门教程 37-100 云沃客项目外包网数据爬虫 scrapy
    Python爬虫入门教程 36-100 酷安网全站应用爬虫 scrapy
  • 原文地址:https://www.cnblogs.com/gezi1007/p/13058399.html
Copyright © 2011-2022 走看看