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