LeetCode日记——【算法】排序专题

  题1:数组中的第k个最大元素(Kth Largest Element in an Array)

Leetcode题号:215

难度:Medium

链接:https://leetcode-cn.com/problems/kth-largest-element-in-an-array/

题目描述:

在未排序的数组中找到第 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 class Solution {
2     public int findKthLargest(int[] nums, int k) {
3         Arrays.sort(nums);
4         return nums[nums.length-k];
5     }
6 }

方法二:堆排序

 1 class Solution {
 2     public int findKthLargest(int[] nums, int k) {
 3         //创建一个小顶堆
 4         PriorityQueue <Integer> pq = new PriorityQueue<>();
 5         //对nums数组中的每个元素进行遍历
 6         for(int val:nums){
 7             //把新元素加入到小顶堆中
 8             pq.add(val);
 9             //保持小顶堆大小为k
10             if(pq.size()>k){
11                 //排序后,删除堆顶元素(堆中最小的数),保证剩下的是遍历到的数中最大的k个数
12                 pq.poll();
13             }
14         }
15         //返回堆顶元素(即最大的k个数中最小的那个,也就是第k大的数)
16         return pq.peek();
17     }
18 }

 方法二:快速选择

 1 class Solution {
 2     //比较j与处理后的k(正序),分割知道j==k
 3     public int findKthLargest(int[] nums, int k) {
 4     k = nums.length - k;
 5     int l = 0, h = nums.length - 1;
 6     while (l < h) {
 7         int j = partition(nums, l, h);
 8         if (j == k) {
 9             break;
10         } else if (j < k) {
11             l = j + 1;
12         } else {
13             h = j - 1;
14         }
15     }
16     return nums[k];
17     }
18     //在l~h范围内分割,找到位置为j的数
19     private int partition(int[] a, int l, int h) {
20         int i = l, j = h + 1;
21         while (true) {
22             while (a[++i] < a[l] && i < h) ;
23             while (a[--j] > a[l] && j > l) ;
24             if (i >= j) {
25                 break;
26             }
27         swap(a, i, j);
28         }
29     swap(a, l, j);
30     return j;
31     } 
32     //交换a[i]与a[j]
33     private void swap(int[] a, int i, int j) {
34         int t = a[i];
35         a[i] = a[j];
36         a[j] = t;
37     }
38 }

分析:

堆排序:https://www.bilibili.com/video/BV1K4411X7fq

概念:大顶堆:根节点永远比左右子节点大。小顶堆:根节点永远比左右子节点小

总体思想:(以大顶堆为例)将需要比较的所有数构造一个大顶堆,输出并删除堆顶数字(最大值)。将剩下的数重新构造一个大顶堆,输出并删除堆顶数字(倒数第2大值).重复以上操作,直到取完堆中的数字。

具体排序方法:

1.对一个无序的树,从倒数第2层最右边的节点开始排序。

2.查看该节点是否比其左右子节点都要大,若是,保持不变。若左子节点比它大,则该节点与左子节点交换,右边亦然。若左右子节点都比它大,则该节点与左右子节点中较大的那个交换。这样完成了一组交换。

3.从右到左,从下到上一层一层地进行步骤2中的交换。直到最大元素出现在根节点处。

4.从左到右,从上到下一层一层检查根节点以下的部分是否满足大顶堆的条件,对不满足的部分重新进行交换。

5.输出并删除根节点(即为最大的数)。将最右下角的节点放置到根节点的位置,重复步骤2~4,直到当前树为null。

快速选择:https://www.bilibili.com/video/BV1at411T75o

总体思想:选择一个数作为中心轴,然后将大于该中心轴的数放在它右边,将小于该中心轴的数放在它左边。分别对其左右子序列重复以上操作,直到子序列只剩一个数。

具体排序方法:

1.将作为中心轴的数取出。

2.设置left指针,指向最左边的数的左边一位。设置right指针,指向最右边的数。跳转到步骤3.

3.比较right指向的数与中心轴的大小。若right指向的数较小,则将right指向的数放到left指针指向的位置,然后left指针向右边移动一位,跳转到步骤4。

   如果right指向的数较大,则将right指针向左边移动一位,然后重新执行步骤3.

4.比较left指向的数与中心轴的大小。若left指向的数较大,则将left指向的数放到right指针指向的位置,然后right指针向左边移动一位,跳转到步骤3。

   如果left指向的数较小,则将left指针向右边移动一位,然后重新执行步骤4.

5.若left与right指针重合,则将中心轴放到该重合位置,结束本次分割。

6.分别对分割后的左右子序列进行1~5的操作,直到子序列只剩下一个数。

 

  题2:出现频率最多的k个元素(Top K Frequent Elements)

Leetcode题号:347

难度:Medium

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

题目描述:

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

代码:

 1 class Solution {
 2     public List<Integer> topKFrequent(int[] nums, int k) {
 3         //存放 数字-数字对应的频数
 4         Map<Integer,Integer> f = new HashMap<>();
 5         for (int num : nums) {
 6             f.put(num, f.getOrDefault(num, 0) + 1);
 7         }
 8         //桶:桶的索引表示频数,每个索引位置放入对应频数的数字
 9         List<Integer>[] buckets = new ArrayList[nums.length + 1];
10         for (int key : f.keySet()) {
11             int frequency = f.get(key);
12             if (buckets[frequency] == null) {
13                 //因为可能存在频数相同的数字,因此每个索引位置都是一个数组
14                 buckets[frequency] = new ArrayList<>();
15             }
16             //根据桶的索引(频数),放入对应的数字(key)
17         buckets[frequency].add(key);
18         }
19         List <Integer> topK = new ArrayList<>();
20         //从桶的最后一个元素开始遍历,往topK数组里面放频率最大的数,放满k个停止
21         for (int i = buckets.length - 1; i >= 0 && topK.size() < k; i--) {
22             //如果频率i没有对应的数,就跳过找下一个频率
23             if (buckets[i] == null) {
24             continue;
25             }
26             //当前频率的数的个数小于k,就将这些数全部放入topK
27             if (buckets[i].size() <= (k - topK.size())) {
28             topK.addAll(buckets[i]);
29             }  else {
30             //topK还剩几个位子,就放几个数
31             topK.addAll(buckets[i].subList(0, k - topK.size()));
32             }
33         }
34     return topK;
35     }
36 }

分析:

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

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

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

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

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

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

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

 

  题3:按照字符出现次数对字符串排序(Sort Characters By Frequency)

Leetcode题号:451

难度:Medium

链接:https://leetcode-cn.com/problems/sort-characters-by-frequency/description/

题目描述:

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

代码:

 1 class Solution {
 2     public String frequencySort(String s) {
 3         //按 字母--出现频率的格式放入HashMap中
 4         Map<Character, Integer> frequencyForNum = new HashMap<>();
 5         for (char c : s.toCharArray())
 6         frequencyForNum.put(c, frequencyForNum.getOrDefault(c, 0) + 1);
 7         //创建桶组
 8         List<Character>[] frequencyBucket = new ArrayList[s.length() + 1];
 9         //把字母放入桶中,桶的索引代表字母出现的频数
10         for (char c : frequencyForNum.keySet()) {
11             int f = frequencyForNum.get(c);
12             if (frequencyBucket[f] == null) {
13                 frequencyBucket[f] = new ArrayList<>();
14             }
15         frequencyBucket[f].add(c);
16         }
17         //从桶中索引最大的元素开始,依次取出字母放入str中
18         StringBuilder str = new StringBuilder();
19         for (int i = frequencyBucket.length - 1; i >= 0; i--) {
20             if (frequencyBucket[i] == null) {
21                 continue;
22             }
23             for (char c : frequencyBucket[i]) {
24                 for (int j = 0; j < i; j++) {
25                     str.append(c);
26                 }
27             } 
28         }   
29         return str.toString();
30     }
31 }

分析:

总体思路与上一道题相同,注意一些字符专用的方法。

将字符串转换为数组:字符串名称.toCharArray( );

StringBuider是一个字符序列可变的字符串(该复习基础了!)

往字符串后面添加一个元素:字符串名称.append();

返回该对象的字符串表示:toString()

 

  题4:按颜色进行排序(Sort Colors)

Leetcode题号:75

难度:Medium

链接:https://leetcode-cn.com/problems/sort-colors/description/

题目描述:

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

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

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

示例:

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

代码:

 1 class Solution {
 2     public void sortColors(int[] nums) {
 3         int zero = -1; 
 4         int two = nums.length;
 5         int curr = 0;
 6         while(curr<two){
 7             if(nums[curr]==0){
 8                 swap(nums,++zero,curr++);
 9             }else if(nums[curr]==2){
10                 swap(nums,--two,curr);
11             }else{
12                 ++curr;
13             }   
14         }
15     } 
16     private void swap(int[] a,int i,int j){
17         int t = a[i];
18         a[i] = a[j];
19         a[j] = t;  
20     }
21 }

分析:

这道题我是通过把实际例子代入代码来理解的。用文字描述为:

一开始把zero指针放在第一个元素的左边,把two指针放在最后一个元素的右边,curr放在第一个元素上表示当前位置。

当curr在two指针左边,就进入循环体:

1.若curr==0,zero指针右移,交换zero与curr位置的数,最后将curr右移。这样可以保证zero与zero左边的数字均为0;

2.若curr==2,two指针左移,交换two与curr位置的数。这样可以保证two与two右边边的数字均为2;

3.若curr==1,将curr右移一位。

排序专题完结撒花~

 

posted @ 2020-05-20 11:19  菅兮徽音  阅读(180)  评论(0编辑  收藏  举报