Leedcode算法专题训练(排序)
排序
快速排序
用于求解 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 问题。
215. 数组中的第K个最大元素
快速排序选择:
public class Solution {
public int findKthLargest(int[] nums, int k) {
int len = nums.length;
int left = 0;
int right = len - 1;
// 转换一下,第 k 大元素的索引是 len - k
int target = len - k;
while (true) {
int index = partition(nums, left, right);
if (index == target) {
return nums[index];
} else if (index < target) {
left = index + 1;
} else {
right = index - 1;
}
}
}
public int partition(int[] nums, int left, int right) {
int pivot = nums[left];
int j = left;
for (int i = left + 1; i <= right; i++) {
if (nums[i] < pivot) {
// 小于 pivot 的元素都被交换到前面
j++;
swap(nums, j, i);
}
}
// 在之前遍历的过程中,满足 [left + 1, j] < pivot,并且 (j, i] >= pivot
swap(nums, j, left);
// 交换以后 [left, j - 1] < pivot, nums[j] = pivot, [j + 1, right] >= pivot
return j;
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
快速排序法模板背诵:
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (k == 0 || arr.length == 0) {
return new int[0];
}
// 最后一个参数表示我们要找的是下标为k-1的数
return quickSearch(arr, 0, arr.length - 1, k - 1);
}
private int[] quickSearch(int[] nums, int lo, int hi, int k) {
// 每快排切分1次,找到排序后下标为j的元素,如果j恰好等于k就返回j以及j左边所有的数;
int j = partition(nums, lo, hi);
if (j == k) {
return Arrays.copyOf(nums, j + 1);
}
// 否则根据下标j与k的大小关系来决定继续切分左段还是右段。
return j > k? quickSearch(nums, lo, j - 1, k): quickSearch(nums, j + 1, hi, k);
}
// 快排切分,返回下标j,使得比nums[j]小的数都在j的左边,比nums[j]大的数都在j的右边。
private int partition(int[] nums, int lo, int hi) {
int v = nums[lo];
int i = lo, j = hi + 1;
while (true) {
while (++i <= hi && nums[i] < v);
while (--j >= lo && nums[j] > v);
if (i >= j) {
break;
}
int t = nums[j];
nums[j] = nums[i];
nums[i] = t;
}
nums[lo] = nums[j];
nums[j] = v;
return j;
}
}
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;
}
排序 :时间复杂度 O(NlogN),空间复杂度 O(1)
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
return nums[nums.length - k];
}
堆 :时间复杂度 O(NlogK),空间复杂度 O(K)。
(1)min-heap:
PriorityQueue<ListNode> queue = new PriorityQueue<>((x, y) -> x.val - y.val);
(2)max-heap:
PriorityQueue<ListNode> queue = new PriorityQueue<>((x, y) -> y.val - x.val);
A = new PriorityQueue<>(); // 小顶堆,保存较大的一半
B = new PriorityQueue<>((x, y) -> (y - x)); // 大顶堆,保存较小的一半
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();
}
桶排序
1. 出现频率最多的 k 个元素
Top K问题,具体学习看一下这个https://zhuanlan.zhihu.com/p/114699207


class Solution {
public int[] topKFrequent(int[] nums, int k) {
// 统计每个数字出现的次数
// Map<Integer, Integer> counterMap = IntStream.of(nums).boxed().collect(Collectors.toMap(e -> e, e -> 1, Integer::sum));
Map<Integer,Integer> counterMap=new HashMap<>();
for(int num:nums){
counterMap.put(num,counterMap.getOrDefault(num,0)+1);
}
// 一个数字最多出现 nums.length 次,因此定义一个长度为 nums.length + 1 的数组,freqList[i] 中存储出现次数为 i 的所有数字。
List<Integer>[] freqList = new List[nums.length + 1];
for (int i = 0; i < freqList.length; i++) {
freqList[i] = new ArrayList<>();
}
counterMap.forEach((num, freq) -> {
freqList[freq].add(num);
});
// 按照出现频次,从大到小遍历频次数组,构造返回结果。
int[] res = new int[k];
int idx = 0;
for (int freq = freqList.length - 1; freq > 0; freq--) {
for (int num: freqList[freq]) {
res[idx++] = num;
if (idx == k) {
return res;
}
}
}
return res;
}
}
2. 按照字符出现次数对字符串排序
class Solution {
public String frequencySort(String s) {
HashMap<Character,Integer> str_map=new HashMap<>();
char[] nums=s.toCharArray();
for(char c: nums){
str_map.put(c, str_map.getOrDefault(c,0)+1);
}
LinkedList<Character>[] freq_list = new LinkedList[s.length()+1];
for(int i=0;i<freq_list.length;i++){
freq_list[i]=new LinkedList<>();
}
str_map.forEach((num,freq)->{
freq_list[freq].add(num);
});
StringBuilder str=new StringBuilder();
for(int i=freq_list.length-1; i>=0; i--){
for(Character ch:freq_list[i]){
for(int j=0;j<i;j++){
str.append(ch);
}
}
}
return str.toString();
}
}
class Solution {
public String frequencySort(String s) {
Map<Character,Integer> frequencyForNum=new HashMap<>();
for(char c: s.toCharArray()){
frequencyForNum.put(c,frequencyForNum.getOrDefault(c,0)+1);
}
List<Character>[] frequencyBucket=new ArrayList[s.length()+1];
for(char c:frequencyForNum.keySet()){
int f=frequencyForNum.get(c);
if(frequencyBucket[f]==null){
frequencyBucket[f]=new ArrayList<>();
}
frequencyBucket[f].add(c);
}
StringBuilder str=new StringBuilder();
for(int i=frequencyBucket.length-1;i>=0;i--){
if(frequencyBucket[i]==null){
continue;
}
for(char c:frequencyBucket[i]){
for(int j=0;j<i;j++){
str.append(c);
}
}
}
return str.toString();
}
}
荷兰国旗问题
荷兰国旗包含三种颜色:红、白、蓝。
有三种颜色的球,算法的目标是将这三种球按颜色顺序正确地排列。它其实是三向切分快速排序的一种变种,在三向切分快速排序中,每次切分都将数组分成三个区间:小于切分元素、等于切分元素、大于切分元素,而该算法是将数组分成三个区间:等于红色、等于白色、等于蓝色。

循环不变量:
1、[0,p0)区间内元素全是0
2、 [p0,curr)区间内元素全是1
3、 (p2,len-1]区间内元素全是2
4、[curr,p2]区间元素待遍历
代码中的元素交换、指针增增减减都是为了保证以上循环不变量的性质。
###########################################################################################
对于nums[curr] == 0时为什么curr++的问题,分两种情况讨论即可:
curr != p0 则[p0,curr)左闭右开区间的元素全是1,两者交换后nums[curr]一定是1,所以直接curr++
curr == p0,满足循环不变量性质,直接curr++
class Solution {
public void sortColors(int[] nums) {
int zero = -1, one = 0, two = nums.length;
while (one < two) {
if (nums[one] == 0) {
// 如果是2 0 1 2 的话 左边交换完 nums[cur]的值是1, 我理解是因为左边交换完要么是0,要么是1, 都不用再判断, 因此cur后移, 而右边交换完 可能是2 需要再次判断, 因此cur不用后移
swap(nums, ++zero, one++);
} else if (nums[one] == 2) {
swap(nums, --two, one);
} else {
++one;
}
}
}
private void swap(int[] nums, int i, int j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
}
Leedcode算法专题训练(排序)的更多相关文章
- Leedcode算法专题训练(搜索)
BFS 广度优先搜索一层一层地进行遍历,每层遍历都是以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点.需要注意的是,遍历过的节点不能再次被遍历. 第一层: 0 -> {6,2,1,5} ...
- Leedcode算法专题训练(分治法)
归并排序就是一个用分治法的经典例子,这里我用它来举例描述一下上面的步骤: 1.归并排序首先把原问题拆分成2个规模更小的子问题. 2.递归地求解子问题,当子问题规模足够小时,可以一下子解决它.在这个例子 ...
- Leedcode算法专题训练(二分查找)
二分查找实现 非常详细的解释,简单但是细节很重要 https://www.cnblogs.com/kyoner/p/11080078.html 正常实现 Input : [1,2,3,4,5] key ...
- Leedcode算法专题训练(贪心)
1. 分配饼干 455. 分发饼干 题目描述:每个孩子都有一个满足度 grid,每个饼干都有一个大小 size,只有饼干的大小大于等于一个孩子的满足度,该孩子才会获得满足.求解最多可以获得满足的孩子数 ...
- Leedcode算法专题训练(双指针)
算法思想 双指针 167. 两数之和 II - 输入有序数组 双指针的典型用法 如果两个指针指向元素的和 sum == target,那么得到要求的结果: 如果 sum > target,移动较 ...
- Leedcode算法专题训练(位运算)
https://www.cnblogs.com/findbetterme/p/10787118.html 看这个就完事了 1. 统计两个数的二进制表示有多少位不同 461. Hamming Dista ...
- Leedcode算法专题训练(数组与矩阵)
1. 把数组中的 0 移到末尾 283. Move Zeroes (Easy) Leetcode / 力扣 class Solution { public void moveZeroes(int[] ...
- Leedcode算法专题训练(数学)
204. 计数质数 难度简单523 统计所有小于非负整数 n 的质数的数量. class Solution { public int countPrimes(int n) { boolean[] is ...
- Leedcode算法专题训练(字符串)
4. 两个字符串包含的字符是否完全相同 242. Valid Anagram (Easy) Leetcode / 力扣 可以用 HashMap 来映射字符与出现次数,然后比较两个字符串出现的字符数量是 ...
随机推荐
- 翻译:《实用的Python编程》01_07_Functions
目录 | 上一节 (1.6 文件) | 下一节 (2.0 处理数据) 1.7 函数 随着程序开始变大,我们会想要有条理地组织这些程序.本节简要介绍函数.库模块以及带有异常的错误处理. 自定义函数 对你 ...
- [转]Linux 线程实现机制分析 Linux 线程实现机制分析 Linux 线程模型的比较:LinuxThreads 和 NPTL
转载地址:https://www.cnblogs.com/MYSQLZOUQI/p/4233630.html 自从多线程编程的概念出现在 Linux 中以来,Linux 多线应用的发展总是与两个问题脱 ...
- ToolBar 用法
xml中的设置: <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_wi ...
- 第35天学习打卡(输入框 TextField监听 简易计算器,组合+内部类回顾复习 画笔 鼠标监听 窗口监听 键盘监听)
1.输入框 TextField监听 package com.kuang.lesson02; import java.awt.*; import java.awt.event.ActionEven ...
- Docker 一键安装及Docker管理面板Portainer中文汉化
前言 Docker接触了一段时间了,批量操作过程中感觉太繁琐,所以找到了好评率比较高的Portainer面板,使用后感觉的确不错所以准备拿出来精力来做个汉化版,过程中发现词条非常多,所以暂时先汉 ...
- linux 关闭对端口的监听
netstat -anp | grep [端口号] [root@test-01 ~]# netstat -anp | grep 6665 tcp 0 0 0.0.0.0:6665 0.0.0.0:* ...
- pytorch(07)数据模型的读取
DataLoader与Dataset pytorch中的数据读取机制 graph TB DataLoader --> DataLoaderIter DataLoaderIter --> S ...
- mysql内一些可以布尔盲注的查询语句
一.left() 首先需要 use security; 这个数据库,然后进入之后再使用查询语句: 此时再使用: select left(database(),1)='s'; ...
- NIO三大组件之Buffer
什么是Buffer Buffer(这里并不是特指Buffer类)是一个存储数据的容器,与数组类似(其实底层依旧是用数组的结构来存储数据),但不同的是,Buffer对象提供了一组更有效的方法去进行写入和 ...
- Go语言学习笔记——Go语言的指针
Go具有指针.指针保存了变量的内存地址. 类型*T是指向类型T的值得指针.其零值是nil var p *int &符号会生成一个指向其作用对象的指针 i:=42 P=&i *符号表示指 ...