排序算法:
常用的排序算法有:快排、归并、堆排序。
该中题型只要是利用排序来解题。
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;
}