排序

快速排序

用于求解 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 个元素

347. 前 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. 按照字符出现次数对字符串排序

451. 根据字符出现频率排序

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(); }
}

荷兰国旗问题

荷兰国旗包含三种颜色:红、白、蓝。

有三种颜色的球,算法的目标是将这三种球按颜色顺序正确地排列。它其实是三向切分快速排序的一种变种,在三向切分快速排序中,每次切分都将数组分成三个区间:小于切分元素、等于切分元素、大于切分元素,而该算法是将数组分成三个区间:等于红色、等于白色、等于蓝色。

75. 颜色分类

循环不变量:

1、[0,p0)区间内元素全是0

2、 [p0,curr)区间内元素全是1

3、 (p2,len-1]区间内元素全是2

4、[curr,p2]区间元素待遍历

代码中的元素交换、指针增增减减都是为了保证以上循环不变量的性质。

###########################################################################################

对于nums[curr] == 0时为什么curr++的问题,分两种情况讨论即可:

  1. curr != p0 则[p0,curr)左闭右开区间的元素全是1,两者交换后nums[curr]一定是1,所以直接curr++

  2. 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算法专题训练(排序)的更多相关文章

  1. Leedcode算法专题训练(搜索)

    BFS 广度优先搜索一层一层地进行遍历,每层遍历都是以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点.需要注意的是,遍历过的节点不能再次被遍历. 第一层: 0 -> {6,2,1,5} ...

  2. Leedcode算法专题训练(分治法)

    归并排序就是一个用分治法的经典例子,这里我用它来举例描述一下上面的步骤: 1.归并排序首先把原问题拆分成2个规模更小的子问题. 2.递归地求解子问题,当子问题规模足够小时,可以一下子解决它.在这个例子 ...

  3. Leedcode算法专题训练(二分查找)

    二分查找实现 非常详细的解释,简单但是细节很重要 https://www.cnblogs.com/kyoner/p/11080078.html 正常实现 Input : [1,2,3,4,5] key ...

  4. Leedcode算法专题训练(贪心)

    1. 分配饼干 455. 分发饼干 题目描述:每个孩子都有一个满足度 grid,每个饼干都有一个大小 size,只有饼干的大小大于等于一个孩子的满足度,该孩子才会获得满足.求解最多可以获得满足的孩子数 ...

  5. Leedcode算法专题训练(双指针)

    算法思想 双指针 167. 两数之和 II - 输入有序数组 双指针的典型用法 如果两个指针指向元素的和 sum == target,那么得到要求的结果: 如果 sum > target,移动较 ...

  6. Leedcode算法专题训练(位运算)

    https://www.cnblogs.com/findbetterme/p/10787118.html 看这个就完事了 1. 统计两个数的二进制表示有多少位不同 461. Hamming Dista ...

  7. Leedcode算法专题训练(数组与矩阵)

    1. 把数组中的 0 移到末尾 283. Move Zeroes (Easy) Leetcode / 力扣 class Solution { public void moveZeroes(int[] ...

  8. Leedcode算法专题训练(数学)

    204. 计数质数 难度简单523 统计所有小于非负整数 n 的质数的数量. class Solution { public int countPrimes(int n) { boolean[] is ...

  9. Leedcode算法专题训练(字符串)

    4. 两个字符串包含的字符是否完全相同 242. Valid Anagram (Easy) Leetcode / 力扣 可以用 HashMap 来映射字符与出现次数,然后比较两个字符串出现的字符数量是 ...

随机推荐

  1. 百万SPC即将空投,3.0公链NGK有多“豪横”?

    在1月2日晚间,比特币强势突破3万美金,随后还在一路上涨,现在价格33431.64美金.仅用了不到一个月的时间,比特币就从2万美金涨到了3万美金,这充分展示了市场对于数字货币的强烈信心.没有了天花板的 ...

  2. 「NGK每日快讯」12.4日NGK公链第31期官方快讯!

  3. 超详细Openstack核心组件——nova部署

    目录 OpenStack-nova组件部署 nova组件部署位置 计算节点Nova服务配置(CT配置) 计算节点配置Nova服务-c1节点配置 计算节点-c2(与c1相同)(除了IP地址) contr ...

  4. 【HTB靶场系列】靶机Carrier的渗透测试

    出品|MS08067实验室(www.ms08067.com) 本文作者:大方子(Ms08067实验室核心成员) Hack The Box是一个CTF挑战靶机平台,在线渗透测试平台.它能帮助你提升渗透测 ...

  5. Java自学no.1———带你初步认识java

    什么是Java Java语言是美国Sun公司(Stanford University Network),在1995年推出的高级的编程语言.所谓编程语言,是 计算机的语言,人们可以使用编程语言对计算机下 ...

  6. ThreadPoolExecutor中execute和submit的区别

    1:入参不同 excute() 传入的是 Runable, submit 传入的是 Callable 或 Runable 1):execute 方法源码 public void execute(Run ...

  7. python类的内部方法

    目录 一.绑定方法与非绑定方法 1.绑定方法 2.非绑定方法 二.property 1.什么是property? 2.为什么要用property? 3.如何使用property? 三.isinstan ...

  8. 下载HLS视频到本地

    现在绝大多数网站播放视频都采用HLS技术,像腾讯优酷爱奇艺等等.本篇博文将介绍如何下载这样的视频到本地. 前言 因疫情影响,上课部分课程采用腾讯课堂上课,腾讯课堂有直播回放功能,但这个功能腾讯显然没有 ...

  9. c++移动构造

    下面随笔给出c++移动构造. 在现实中有很多这样的例子,我们将钱从一个账号转移到另一个账号,将手机SIM卡转移到另一台手机,将文件从一个位置剪切到另一个位置--移动构造可以减少不必要的复制,带来性能上 ...

  10. new String("abc"),到底在不在常量池中存储"abc"?

    String str = new String("Hello World"); 问之:这行代码到底有没有在字符串常量池中创建"Hello World"字符串呢? ...