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/
题目描述:
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
代码:
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/
题目描述:
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
代码:
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右移一位。
排序专题完结撒花~