排序算法:
常用的排序算法有:快排、归并、堆排序。
该中题型只要是利用排序来解题。
Kth Element问题,也就是第K个元素的问题。
快速选择
用于求解 Kth Element 问题,也就是第 K 个元素的问题。
可以使用快速排序的 partition() 进行实现。需要先打乱数组,否则最坏情况下时间复杂度为 O(N2)。
堆
用于求解 TopK Elements 问题,也就是 K 个最小元素的问题。可以维护一个大小为 K 的最小堆,最小堆中的元素就是最小元素。最小堆需要使用大顶堆来实现,大顶堆表示堆顶元素是堆中最大元素。这是因为我们要得到 k 个最小的元素,因此当遍历到一个新的元素时,需要知道这个新元素是否比堆中最大的元素更小,更小的话就把堆中最大元素去除,并将新元素添加到堆中。所以我们需要很容易得到最大元素并移除最大元素,大顶堆就能很好满足这个要求。
堆也可以用于求解 Kth Element 问题,得到了大小为 k 的最小堆之后,因为使用了大顶堆来实现,因此堆顶元素就是第 k 大的元素。
快速选择也可以求解 TopK Elements 问题,因为找到 Kth Element 之后,再遍历一次数组,所有小于等于 Kth Element 的元素都是 TopK Elements。
可以看到,快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题。
1.Kth Element
215. Kth Largest Element in an Array (Medium)
Input: [3,2,1,5,6,4] and k = 2
Output: 5
题目描述:找到倒数第 k 个的元素。
(1)排序 :时间复杂度 O(NlogN),空间复杂度 O(1)
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
return nums[nums.length - k];
}
(2)堆 :时间复杂度 O(NlogK),空间复杂度 O(K)。
思路:
小顶堆
使用小顶堆存放元素,堆顶是所有元素中最小元素,那么当堆中超过了K个元素,则将堆顶元素删除,一直循环到所有元素遍历一遍,且保持小顶堆元素个数始终为K个元素,则堆顶元素即为所求元素。
大顶堆
使用大顶堆存放元素,堆定是序列中最大元素,那么当堆内元素超过了n-k个元素,则将堆定删除,一直循环到遍历完所有元素,且保持大顶堆中元素个数始终为n-k个,则堆顶元素即为所求元素。
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<>(); // 小顶堆
for (int val : nums) {
pq.add(val);
if (pq.size() > k) // 维护堆的大小为 K
pq.poll();
}
return pq.peek();
}
(3)快速选择 :时间复杂度 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 = partition(nums, l, h);
if (j == k) {
break;
} else if (j < k) {
l = j + 1;
} else {
h = j - 1;
}
}
return nums[k];
}
private int partition(int[] a, int l, int h) {
int i = l, j = h + 1;
while (true) {
while (a[++i] < a[l] && i < h) ;
while (a[--j] > a[l] && j > l) ;
if (i >= j) {
break;
}
swap(a, i, j);
}
swap(a, l, j);
return j;
}
private void swap(int[] a, int i, int j) {
int t = a[i];
a[i] = a[j];
a[j] = t;
}
桶排序
1. 出现频率最多的 k 个元素
347. Top K Frequent Elements (Medium)
Given [1,1,1,2,2,3] and k = 2, return [1,2].
思路:
使用map存放,元素和元素对应个数,使用小顶堆:
小顶堆中存放的是map的键,但是在建立小顶堆的时,使用键对应map的值进行建堆。还可以这样玩儿 骚操作。
这就是所谓的桶排序,即根据map中的值来对键进行排序,本题使用相对简单的方法,常见的map根据值排序,相对复杂。
代码实现:
public List<Integer> topKFrequent(int[] nums, int k) {
Map<Integer,Integer> map=new HashMap<>();
for (int num : nums) {
map.put(num,map.getOrDefault(num,0)+1);
}
PriorityQueue<Integer> heap =
new PriorityQueue<Integer>((n1, n2) -> map.get(n1) - map.get(n2));
//进小顶堆,并保持小顶堆始终含有k个元素,若超过k个元素,则删除队首元素,则这k个元素是map的键中前k大的元素
for (Integer integer : map.keySet()) {
heap.add(integer);
if(heap.size()>k){
heap.remove();
}
}
List<Integer> list=new ArrayList<>();
//当小顶堆不空时候,将数据输出到list
while (!heap.isEmpty()){
list.add(heap.remove());
}
Collections.reverse(list);
return list;
}
2.按照字符出现次数对字符串排序
451. Sort Characters By Frequency (Medium)
Input:
"tree"
Output:
"eert"
Explanation:
'e' appears twice while 'r' and 't' both appear once.
So 'e' must appear before both 'r' and 't'. Therefore "eetr" is also a valid answer.
思路:
使用map统计出字符和对应出现频次,key为字符,value为值,
使用优先队列来根据value的值来对key进行构建大顶堆,
大顶堆出队,并根据出现的频次来加入StringBuilder,
最后返回sb.toString()
代码实现:
public static String frequencySort2(String s) {
Map<Character, Integer> map = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
map.put( c, map.getOrDefault(c, 0) + 1);
}
PriorityQueue<Character> pq = new PriorityQueue(((o1, o2) -> map.get(o2) - map.get(o1)));
for (Character character : map.keySet()) {
pq.add(character);
}
//使用StringBuilder来进行添加字符,更加高效
StringBuilder sb=new StringBuilder();
while (!pq.isEmpty()) {
Character key = pq.remove();
for (Integer i = 0; i < map.get(key); i++) {
sb.append(key);
}
}
return sb.toString();
}
荷兰国旗问题
荷兰国旗包含三种颜色:红、白、蓝。
有三种颜色的球,算法的目标是将这三种球按颜色顺序正确地排列。它其实是三向切分快速排序的一种变种,在三向切分快速排序中,每次切分都将数组分成三个区间:小于切分元素、等于切分元素、大于切分元素,而该算法是将数组分成三个区间:等于红色、等于白色、等于蓝色。
1.按颜色进行排序
思路:
本问题被称为 荷兰国旗问题
,最初由 Edsger W. Dijkstra提出。
其主要思想是给每个数字设定一种颜色,并按照荷兰国旗颜色的顺序进行调整。
我们用三个指针(p0, p2 和curr)来分别追踪0的最右边界,2的最左边界和当前考虑的元素。
本解法的思路是沿着数组移动 curr 指针,若nums[curr] = 0,则将其与 nums[p0]互换;若 nums[curr] = 2 ,则与 nums[p2]互换。
流程:
初始化0的最优边界: p0=0
初始化2的最左边界: p2=n-1
初始化当前考虑的元素符号: cuur=0
while cuur <= p2
若 nums[cuur]=0 :交换第 curr个和第 p0个元素,并将指针都向右移。
若 nums[cuur]=2 :交换第 curr个和第p2个元素,并肩p2指针左移。
若 nums[cree]=1 :将指针 curr右移
代码实现:
/**
* 荷兰国旗问题
* @param nums
*/
public void sortColors(int[] nums) {
int curr=0,p0=0,p2=nums.length-1;
while (curr<=p2){
if (nums[curr]==0){
//此处必须是curr++ p0++ ,curr>=p0 ,
//将两坐标位置元素交换后,此时curr指向的位置元素不需排序,
//因为是已经排过序的,即对于p0都是已经排过序的,
//可以画图模拟
swap(nums,curr++,p0++);
}else if (nums[curr]==2){
//此处必须是curr不变,p2--,curr<=p0,
//将两坐标元素交换后此时,此时curr指向的元素是未曾被排序的,
//所以还需要继续排序,可以画图模拟
swap(nums,curr,p2--);
}else {
curr++;
}
}
}
/**
* 交换位置
* @param nums
* @param i
* @param j
*/
void swap(int nums[],int i,int j){
int tem=nums[i];
nums[i]=nums[j];
nums[j]=tem;
}