什么建站程序好收录,营销网站优化seo,网络营销推广品牌,网站制作公司重庆LeetCode 01-算法入门与数组-③数组排序 一. 冒泡排序
1. 冒泡排序算法思想 冒泡排序#xff08;Bubble Sort#xff09;基本思想#xff1a; 经过多次迭代#xff0c;通过相邻元素之间的比较与交换#xff0c;使值较小的元素逐步从后面移到前面#xff0c;值较大的元素… LeetCode 01-算法入门与数组-③数组排序
一. 冒泡排序
1. 冒泡排序算法思想 冒泡排序Bubble Sort基本思想 经过多次迭代通过相邻元素之间的比较与交换使值较小的元素逐步从后面移到前面值较大的元素从前面移到后面。 这个过程就像水底的气泡一样从底部向上「冒泡」到水面这也是冒泡排序法名字的由来。
接下来我们使用「冒泡」的方式来模拟一下这个过程。
首先将数组想象是一排「泡泡」元素值的大小与泡泡的大小成正比。然后从左到右依次比较相邻的两个「泡泡」 如果左侧泡泡大于右侧泡泡则交换两个泡泡的位置。如果左侧泡泡小于等于右侧泡泡则两个泡泡保持不变。 这 1 1 1 趟遍历完成之后最大的泡泡就会放置到所有泡泡的最右侧就像是「泡泡」从水底向上浮到了水面。 1 2 3 4 5 6 7 2. 冒泡排序算法步骤
假设数组的元素个数为 n n n 个则冒泡排序的算法步骤如下
第 1 1 1 趟「冒泡」对前 n n n 个元素执行「冒泡」从而使第 1 1 1 个值最大的元素放置在正确位置上。 先将序列中第 1 1 1 个元素与第 2 2 2 个元素进行比较如果前者大于后者则两者交换位置否则不交换。然后将第 2 2 2 个元素与第 3 3 3 个元素比较如果前者大于后者则两者交换位置否则不交换。依次类推直到第 n − 1 n - 1 n−1 个元素与第 n n n 个元素比较或交换为止。经过第 1 1 1 趟排序使得 n n n 个元素中第 i i i 个值最大元素被安置在第 n n n 个位置上。 第 2 2 2 趟「冒泡」对前 n − 1 n - 1 n−1 个元素执行「冒泡」从而使第 2 2 2 个值最大的元素放置在正确位置上。 先将序列中第 1 1 1 个元素与第 2 2 2 个元素进行比较若前者大于后者则两者交换位置否则不交换。然后将第 2 2 2 个元素与第 3 3 3 个元素比较若前者大于后者则两者交换位置否则不交换。依次类推直到对 n − 2 n - 2 n−2 个元素与第 n − 1 n - 1 n−1 个元素比较或交换为止。但是少时诵诗书所所所所是撒是撒是撒是撒是撒是撒是撒是撒是撒是撒是撒是撒是撒是撒是撒是撒是撒是撒是撒是撒是撒是撒是撒是撒是撒是撒经过第 2 2 2 趟排序使得数组中第 2 2 2 个值最大元素被安置在第 n n n 个位置上。 依次类推重复上述「冒泡」过程直到某一趟排序过程中不出现元素交换位置的动作则排序结束。
我们以 [ 5 , 2 , 3 , 6 , 1 , 4 ] [5, 2, 3, 6, 1, 4] [5,2,3,6,1,4] 为例演示一下冒泡排序的整个过程。 3. 冒泡排序代码实现
class Solution:def bubbleSort(self, nums: [int]) - [int]:# 第 i 趟「冒泡」for i in range(len(nums) - 1):flag False # 是否发生交换的标志位# 从数组中前 n - i 1 个元素的第 1 个元素开始相邻两个元素进行比较for j in range(len(nums) - i - 1):# 相邻两个元素进行比较如果前者大于后者则交换位置if nums[j] nums[j 1]:nums[j], nums[j 1] nums[j 1], nums[j]flag Trueif not flag: # 此趟遍历未交换任何元素直接跳出breakreturn numsdef sortArray(self, nums: [int]) - [int]:return self.bubbleSort(nums)4. 冒泡排序算法分析
最佳时间复杂度 O ( n ) O(n) O(n)。最好的情况下初始时序列已经是升序排列只需经过 1 1 1 趟排序总共经过 n n n 次元素之间的比较并且不移动元素算法就可以结束排序。因此冒泡排序算法的最佳时间复杂度为 O ( n ) O(n) O(n)。最坏时间复杂度 O ( n 2 ) O(n^2) O(n2)。最差的情况下初始时序列已经是降序排列或者最小值元素处在序列的最后则需要进行 n n n 趟排序总共进行 ∑ i 2 n ( i − 1 ) n ( n − 1 ) 2 ∑^n_{i2}(i−1) \frac{n(n−1)}{2} ∑i2n(i−1)2n(n−1) 次元素之间的比较因此冒泡排序算法的最坏时间复杂度为 O ( n 2 ) O(n^2) O(n2)。空间复杂度 O ( 1 ) O(1) O(1)。冒泡排序为原地排序算法只用到指针变量 i i i、 j j j 以及标志位 f l a g flag flag 等常数项的变量。冒泡排序适用情况冒泡排序方法在排序过程中需要移动较多次数的元素并且排序时间效率比较低。因此冒泡排序方法比较适合于参加排序序列的数据量较小的情况尤其是当序列的初始状态为基本有序的情况。排序稳定性由于元素交换是在相邻元素之间进行的不会改变相等元素的相对顺序因此冒泡排序法是一种 稳定排序算法。
二. 选择排序
1. 选择排序算法思想 选择排序Selection Sort基本思想 将数组分为两个区间左侧为已排序区间右侧为未排序区间。每趟从未排序区间中选择一个值最小的元素放到已排序区间的末尾从而将该元素划分到已排序区间。 选择排序是一种简单直观的排序算法其思想简单代码也相对容易。
2. 选择排序算法步骤
假设数组的元素个数为 n n n 个则选择排序的算法步骤如下
初始状态下无已排序区间未排序区间为 [ 0 , n − 1 ] [0, n - 1] [0,n−1]。第 1 1 1 趟选择 遍历未排序区间 [ 0 , n − 1 ] [0, n - 1] [0,n−1]使用变量 m i n ‾ i min\underline{}i mini 记录区间中值最小的元素位置。将 m i n ‾ i min\underline{}i mini 与下标为 0 0 0 处的元素交换位置。如果下标为 0 0 0 处元素就是值最小的元素位置则不用交换。此时 [ 0 , 0 ] [0, 0] [0,0] 为已排序区间 [ 1 , n − 1 ] [1, n - 1] [1,n−1]总共 n − 1 n - 1 n−1 个元素为未排序区间。 第 2 2 2 趟选择 遍历未排序区间 [ 1 , n − 1 ] [1, n - 1] [1,n−1]使用变量 m i n ‾ i min\underline{}i mini 记录区间中值最小的元素位置。将 m i n ‾ i min\underline{}i mini 与下标为 1 1 1 处的元素交换位置。如果下标为 1 1 1 处元素就是值最小的元素位置则不用交换。此时 [ 0 , 1 ] [0, 1] [0,1] 为已排序区间 [ 2 , n − 1 ] [2, n - 1] [2,n−1]总共 n − 2 n - 2 n−2 个元素为未排序区间。 依次类推对剩余未排序区间重复上述选择过程直到所有元素都划分到已排序区间排序结束。
我们以 [ 5 , 2 , 3 , 6 , 1 , 4 ] [5, 2, 3, 6, 1, 4] [5,2,3,6,1,4] 为例演示一下选择排序的整个过程。 1 2 3
4 5 6 7 3. 选择排序代码实现
class Solution:def selectionSort(self, nums: [int]) - [int]:for i in range(len(nums) - 1):# 记录未排序区间中最小值的位置min_i ifor j in range(i 1, len(nums)):if nums[j] nums[min_i]:min_i j# 如果找到最小值的位置将 i 位置上元素与最小值位置上的元素进行交换if i ! min_i:nums[i], nums[min_i] nums[min_i], nums[i]return numsdef sortArray(self, nums: [int]) - [int]:return self.selectionSort(nums)4. 选择排序算法分析
时间复杂度 O ( n 2 ) O(n^2) O(n2)。排序法所进行的元素之间的比较次数与序列的原始状态无关时间复杂度总是 O ( n 2 ) O(n^2) O(n2)。 这是因为无论序列中元素的初始排列状态如何第 i i i 趟排序要找出值最小元素都需要进行 n − i n − i n−i 次元素之间的比较。因此整个排序过程需要进行的元素之间的比较次数都相同为 ∑ i 2 n ( i − 1 ) n ( n − 1 ) 2 ∑^n_{i2}(i - 1) \frac{n(n−1)}{2} ∑i2n(i−1)2n(n−1) 次。 空间复杂度 O ( 1 ) O(1) O(1)。选择排序算法为原地排序算法只用到指针变量 i i i、 j j j 以及最小值位置 m i n ‾ i min\underline{}i mini 等常数项的变量。选择排序适用情况选择排序方法在排序过程中需要移动较多次数的元素并且排序时间效率比较低。因此选择排序方法比较适合于参加排序序列的数据量较小的情况。选择排序的主要优点是仅需要原地操作无需占用其他空间就可以完成排序因此在空间复杂度要求较高时可以考虑选择排序。排序稳定性由于值最小元素与未排序区间第 1 1 1 个元素的交换动作是在不相邻的元素之间进行的因此很有可能会改变相等元素的相对顺序因此选择排序法是一种 不稳定排序算法。
三. 插入排序
1. 插入排序算法思想 插入排序Insertion Sort基本思想 将数组分为两个区间左侧为有序区间右侧为无序区间。每趟从无序区间取出一个元素然后将其插入到有序区间的适当位置。 插入排序在每次插入一个元素时该元素会在有序区间找到合适的位置因此每次插入后有序区间都会保持有序。
2. 插入排序算法步骤
假设数组的元素个数为 n n n 个则插入排序的算法步骤如下
初始状态下有序区间为 [ 0 , 0 ] [0, 0] [0,0]无序区间为 [ 1 , n − 1 ] [1, n - 1] [1,n−1]。第 1 1 1 趟插入 取出无序区间 [ 1 , n − 1 ] [1, n - 1] [1,n−1] 中的第 1 1 1 个元素即 n u m s [ 1 ] nums[1] nums[1]。从右到左遍历有序区间中的元素将比 n u m s [ 1 ] nums[1] nums[1] 小的元素向后移动 1 1 1 位。如果遇到大于或等于 n u m s [ 1 ] nums[1] nums[1] 的元素时说明找到了插入位置将 n u m s [ 1 ] nums[1] nums[1] 插入到该位置。插入元素后有序区间变为 [ 0 , 1 ] [0, 1] [0,1]无序区间变为 [ 2 , n − 1 ] [2, n - 1] [2,n−1]。 第 2 2 2 趟插入 取出无序区间 [ 2 , n − 1 ] [2, n - 1] [2,n−1] 中的第 1 1 1 个元素即 n u m s [ 2 ] nums[2] nums[2]。从右到左遍历有序区间中的元素将比 n u m s [ 2 ] nums[2] nums[2] 小的元素向后移动 1 1 1 位。如果遇到大于或等于 n u m s [ 2 ] nums[2] nums[2] 的元素时说明找到了插入位置将 n u m s [ 2 ] nums[2] nums[2] 插入到该位置。插入元素后有序区间变为 [ 0 , 2 ] [0, 2] [0,2]无序区间变为 [ 3 , n − 1 ] [3, n - 1] [3,n−1]。 依次类推对剩余无序区间中的元素重复上述插入过程直到所有元素都插入到有序区间中排序结束。
我们以 [ 5 , 2 , 3 , 6 , 1 , 4 ] [5, 2, 3, 6, 1, 4] [5,2,3,6,1,4] 为例演示一下插入排序的整个过程。 3. 插入排序代码实现
class Solution:def insertionSort(self, nums: [int]) - [int]:# 遍历无序区间for i in range(1, len(nums)):temp nums[i]j i# 从右至左遍历有序区间while j 0 and nums[j - 1] temp:# 将有序区间中插入位置右侧的元素依次右移一位nums[j] nums[j - 1]j - 1# 将该元素插入到适当位置nums[j] tempreturn numsdef sortArray(self, nums: [int]) - [int]:return self.insertionSort(nums)4. 插入排序算法分析
最佳时间复杂度 O ( n ) O(n) O(n)。最好的情况下初始时区间已经是升序排列每个元素只进行一次元素之间的比较因而总的比较次数最少为 ∑ i 2 n 1 n − 1 ∑^n_{i 2}1 n − 1 ∑i2n1n−1并不需要移动元素记录这是最好的情况。最差时间复杂度 O ( n 2 ) O(n^2) O(n2)。最差的情况下初始时区间已经是降序排列每个元素 n u m s [ i ] nums[i] nums[i] 都要进行 i − 1 i - 1 i−1 次元素之间的比较元素之间总的比较次数达到最大值为 ∑ i 2 n ( i − 1 ) n ( n − 1 ) 2 ∑^n_{i2}(i − 1) \frac{n(n−1)}{2} ∑i2n(i−1)2n(n−1)。平均时间复杂度 O ( n 2 ) O(n^2) O(n2)。如果区间的初始情况是随机的即参加排序的区间中元素可能出现的各种排列的概率相同则可取上述最小值和最大值的平均值作为插入排序时所进行的元素之间的比较次数约为 n 2 4 \frac{n^2}{4} 4n2。由此得知插入排序算法的平均时间复杂度为 O ( n 2 ) O(n^2) O(n2)。空间复杂度 O ( 1 ) O(1) O(1)。插入排序算法为原地排序算法只用到指针变量 i i i、 j j j 以及表示无序区间中第 1 1 1 个元素的变量等常数项的变量。排序稳定性在插入操作过程中每次都讲元素插入到相等元素的右侧并不会改变相等元素的相对顺序。因此插入排序方法是一种 稳定排序算法。
四. 练习题目1
1. 剑指 Offer 45. 把数组排成最小的数
1.1 题目大意
描述给定一个非负整数数组 nums。
要求将数组中的数字拼接起来排成一个数打印能拼接出的所有数字中的最小的一个。
说明 0 n u m s . l e n g t h ≤ 100 0 nums.length \le 100 0nums.length≤100。输出结果可能非常大所以你需要返回一个字符串而不是整数。拼接起来的数字可能会有前导 0最后结果不需要去掉前导 0。
示例
输入[3,30,34,5,9]
输出30334591.2 解题思路
思路 1自定义排序
本质上是给数组进行排序。假设 x、y 是数组 nums 中的两个元素。则排序的判断规则如下所示
如果拼接字符串 x y y x则 x 大于 y y 应该排在 x 前面从而使拼接起来的数字尽可能的小。反之如果拼接字符串 x y y x则 x 小于 y x 应该排在 y 前面从而使拼接起来的数字尽可能的小。
按照上述规则对原数组进行排序。这里使用了 functools.cmp_to_key 自定义排序函数。
思路 1自定义排序代码
from functools import cmp_to_keyclass Solution:def minNumber(self, nums: List[int]) - str:nums [*map(str, nums)]nums.sort(keycmp_to_key(lambda x, y: - (x y y x)))return .join(nums)思路 1复杂度分析
时间复杂度 O ( n × log 2 n ) O(n \times \log_2n) O(n×log2n)。排序算法的时间复杂度为 O ( n × log 2 n ) O(n \times \log_2n) O(n×log2n)。空间复杂度 O ( 1 ) O(1) O(1)。
2. 0283. 移动零
2.1 题目大意
描述给定一个数组 nums。
要求将所有 0 移动到末尾并保持原有的非 0 数字的相对顺序。
说明
只能在原数组上进行操作。 1 ≤ n u m s . l e n g t h ≤ 1 0 4 1 \le nums.length \le 10^4 1≤nums.length≤104。 − 2 31 ≤ n u m s [ i ] ≤ 2 31 − 1 -2^{31} \le nums[i] \le 2^{31} - 1 −231≤nums[i]≤231−1。
示例
输入: nums [0,1,0,3,12]
输出: [1,3,12,0,0]输入: nums [0]
输出: [0]2.2 解题思路
思路 1快慢指针
使用两个指针 slowfast。slow 指向处理好的非 0 数字数组的尾部fast 指针指向当前待处理元素。不断向右移动 fast 指针每次移动到非零数则将左右指针对应的数交换交换同时将 slow 右移。此时slow 指针左侧均为处理好的非零数而从 slow 指针指向的位置开始 fast 指针左边为止都为 0。
遍历结束之后则所有 0 都移动到了右侧且保持了非零数的相对位置。
思路 1代码
class Solution:def moveZeroes(self, nums: List[int]) - None:s 0for f in range(len(nums)):if nums[f]:if f - s:nums[s] nums[f]nums[f] 0s 1思路 1复杂度分析
时间复杂度 O ( n ) O(n) O(n)。空间复杂度 O ( 1 ) O(1) O(1)。
3. 0912. 排序数组
3.1 题目大意
描述给定一个整数数组 nums。
要求将该数组升序排列。
说明 1 ≤ n u m s . l e n g t h ≤ 5 ∗ 1 0 4 1 \le nums.length \le 5 * 10^4 1≤nums.length≤5∗104。 − 5 ∗ 1 0 4 ≤ n u m s [ i ] ≤ 5 ∗ 1 0 4 -5 * 10^4 \le nums[i] \le 5 * 10^4 −5∗104≤nums[i]≤5∗104。
示例
输入nums [5,2,3,1]
输出[1,2,3,5]输入nums [5,1,1,2,0,0]
输出[0,0,1,1,2,5]3.2 解题思路
思路 1真 · 快速排序
真 · 快速排序基本思想
调用API
思路 1代码
class Solution:def sortArray(self, nums: List[int]) - List[int]:nums.sort()return nums思路 1复杂度分析
时间复杂度 O ( n × log 2 n ) O(n \times \log_2 n) O(n×log2n)。空间复杂度 O ( n ) O(n) O(n)。
五. 归并排序
1. 归并排序算法思想 归并排序Merge Sort基本思想 采用经典的分治策略先递归地将当前数组平均分成两半然后将有序数组两两合并最终合并成一个有序数组。 2. 归并排序算法步骤
假设数组的元素个数为 n n n 个则归并排序的算法步骤如下
分解过程先递归地将当前数组平均分成两半直到子数组长度为 1 1 1。 找到数组中心位置 m i d mid mid从中心位置将数组分成左右两个子数组 l e f t ‾ n u m s left\underline{}nums leftnums、 r i g h t ‾ n u m s right\underline{}nums rightnums。对左右两个子数组 l e f t ‾ n u m s left\underline{}nums leftnums、 r i g h t ‾ n u m s right\underline{}nums rightnums 分别进行递归分解。最终将数组分解为 n n n 个长度均为 1 1 1 的有序子数组。 归并过程从长度为 1 1 1 的有序子数组开始依次将有序数组两两合并直到合并成一个长度为 n n n 的有序数组。 使用数组变量 n u m s nums nums 存放合并后的有序数组。使用两个指针 l e f t ‾ i left\underline{}i lefti、 r i g h t ‾ i right\underline{}i righti 分别指向两个有序子数组 l e f t ‾ n u m s left\underline{}nums leftnums、 r i g h t ‾ n u m s right\underline{}nums rightnums 的开始位置。比较两个指针指向的元素将两个有序子数组中较小元素依次存入到结果数组 n u m s nums nums 中并将指针移动到下一位置。重复步骤 3 3 3直到某一指针到达子数组末尾。将另一个子数组中的剩余元素存入到结果数组 n u m s nums nums 中。返回合并后的有序数组 n u m s nums nums。
我们以 [ 0 , 5 , 7 , 3 , 1 , 6 , 8 , 4 ] [0, 5, 7, 3, 1, 6, 8, 4] [0,5,7,3,1,6,8,4] 为例演示一下归并排序的整个过程。 3. 归并排序代码实现
class Solution:# 合并过程def merge(self, left_nums: [int], right_nums: [int]):nums []left_i, right_i 0, 0while left_i len(left_nums) and right_i len(right_nums):# 将两个有序子数组中较小元素依次插入到结果数组中if left_nums[left_i] right_nums[right_i]:nums.append(left_nums[left_i])left_i 1else:nums.append(right_nums[right_i])right_i 1# 如果左子数组有剩余元素则将其插入到结果数组中while left_i len(left_nums):nums.append(left_nums[left_i])left_i 1# 如果右子数组有剩余元素则将其插入到结果数组中while right_i len(right_nums):nums.append(right_nums[right_i])right_i 1# 返回合并后的结果数组return nums# 分解过程def mergeSort(self, nums: [int]) - [int]:# 数组元素个数小于等于 1 时直接返回原数组if len(nums) 1:return numsmid len(nums) // 2 # 将数组从中间位置分为左右两个数组left_nums self.mergeSort(nums[0: mid]) # 递归将左子数组进行分解和排序right_nums self.mergeSort(nums[mid:]) # 递归将右子数组进行分解和排序return self.merge(left_nums, right_nums) # 把当前数组组中有序子数组逐层向上进行两两合并def sortArray(self, nums: [int]) - [int]:return self.mergeSort(nums)4. 归并排序算法分析
时间复杂度 O ( n × log n ) O(n \times \log n) O(n×logn)。归并排序算法的时间复杂度等于归并趟数与每一趟归并的时间复杂度乘积。子算法 merge(left_nums, right_nums): 的时间复杂度是 O ( n ) O(n) O(n)因此归并排序算法总的时间复杂度为 O ( n × log n ) O(n \times \log n) O(n×logn)。空间复杂度 O ( n ) O(n) O(n)。归并排序方法需要用到与参加排序的数组同样大小的辅助空间。因此算法的空间复杂度为 O ( n ) O(n) O(n)。排序稳定性因为在两个有序子数组的归并过程中如果两个有序数组中出现相等元素merge(left_nums, right_nums): 算法能够使前一个数组中那个相等元素先被复制从而确保这两个元素的相对顺序不发生改变。因此归并排序算法是一种 稳定排序算法。
六. 希尔排序
1. 希尔排序算法思想 希尔排序Shell Sort基本思想 将整个数组切按照一定的间隔取值划分为若干个子数组每个子数组分别进行插入排序。然后逐渐缩小间隔进行下一轮划分子数组和对子数组进行插入排序。直至最后一轮排序间隔为 1 1 1对整个数组进行插入排序。 2. 希尔排序算法步骤
假设数组的元素个数为 n n n 个则希尔排序的算法步骤如下
确定一个元素间隔数 g a p gap gap。将参加排序的数组按此间隔数从第 1 1 1 个元素开始一次分成若干个子数组即分别将所有位置相隔为 g a p gap gap 的元素视为一个子数组。在各个子数组中采用某种排序算法例如插入排序算法进行排序。减少间隔数并重新将整个数组按新的间隔数分成若干个子数组再分别对各个子数组进行排序。依次类推直到间隔数 g a p gap gap 值为 1 1 1最后进行一次排序排序结束。
我们以 [ 7 , 2 , 6 , 8 , 0 , 4 , 1 , 5 , 9 , 3 ] [7, 2, 6, 8, 0, 4, 1, 5, 9, 3] [7,2,6,8,0,4,1,5,9,3] 为例演示一下希尔排序的整个过程。 1 2 3 4 5 6 7 3. 希尔排序代码实现
class Solution:def shellSort(self, nums: [int]) - [int]:size len(nums)gap size // 2# 按照 gap 分组while gap 0:# 对每组元素进行插入排序for i in range(gap, size):# temp 为每组中无序数组第 1 个元素temp nums[i]j i# 从右至左遍历每组中的有序数组元素while j gap and nums[j - gap] temp:# 将每组有序数组中插入位置右侧的元素依次在组中右移一位nums[j] nums[j - gap]j - gap# 将该元素插入到适当位置nums[j] temp# 缩小 gap 间隔gap gap // 2return numsdef sortArray(self, nums: [int]) - [int]:return self.shellSort(nums)4. 希尔排序算法分析 时间复杂度介于 O ( n × log 2 n ) O(n \times \log^2 n) O(n×log2n) 与 O ( n 2 ) O(n^2) O(n2) 之间。 希尔排序方法的速度是一系列间隔数 g a p i gap_i gapi 的函数而比较次数与 g a p i gap_i gapi 之间的依赖关系比较复杂不太容易给出完整的数学分析。本文采用 g a p i ⌊ g a p i − 1 / 2 ⌋ gap_i \lfloor gap_{i-1}/2 \rfloor gapi⌊gapi−1/2⌋ 的方法缩小间隔数对于具有 n n n 个元素的数组如果 g a p 1 ⌊ n / 2 ⌋ gap_1 \lfloor n/2 \rfloor gap1⌊n/2⌋则经过 p ⌊ log 2 n ⌋ p \lfloor \log_2 n \rfloor p⌊log2n⌋ 趟排序后就有 g a p p 1 gap_p 1 gapp1因此希尔排序方法的排序总躺数为 ⌊ log 2 n ⌋ \lfloor \log_2 n \rfloor ⌊log2n⌋。从算法中也可以看到外层 while gap 0 的循环次数为 log n \log n logn 数量级内层插入排序算法循环次数为 n n n 数量级。当子数组分得越多时子数组内的元素就越少内层循环的次数也就越少反之当所分的子数组个数减少时子数组内的元素也随之增多但整个数组也逐步接近有序而循环次数却不会随之增加。因此希尔排序算法的时间复杂度在 O ( n × log 2 n ) O(n \times \log^2 n) O(n×log2n) 与 O ( n 2 ) O(n^2) O(n2) 之间。 空间复杂度 O ( 1 ) O(1) O(1)。希尔排序中用到的插入排序算法为原地排序算法只用到指针变量 i i i、 j j j 以及表示无序区间中第 1 1 1 个元素的变量、间隔数 g a p gap gap 等常数项的变量。 排序稳定性在一次插入排序是稳定的不会改变相等元素的相对顺序但是在不同的插入排序中相等元素可能在各自的插入排序中移动。因此希尔排序方法是一种 不稳定排序算法。
七. 练习题目2
4. 0506. 相对名次
4.1 题目大意
描述给定一个长度为 n 的数组 score。其中 score[i] 表示第 i 名运动员在比赛中的成绩。所有成绩互不相同。
要求找出他们的相对名次并授予前三名对应的奖牌。前三名运动员将会被分别授予「金牌Gold Medal」「银牌Silver Medal」和「铜牌Bronze Medal」。
说明 n s c o r e . l e n g t h n score.length nscore.length。 1 ≤ n ≤ 1 0 4 1 \le n \le 10^4 1≤n≤104。 0 ≤ s c o r e [ i ] ≤ 1 0 6 0 \le score[i] \le 10^6 0≤score[i]≤106。score 中的所有值互不相同。
示例
输入score [5,4,3,2,1]
输出[Gold Medal,Silver Medal,Bronze Medal,4,5]
解释名次为 [1st, 2nd, 3rd, 4th, 5th] 。输入score [10,3,8,9,4]
输出[Gold Medal,5,Bronze Medal,Silver Medal,4]
解释名次为 [1st, 5th, 3rd, 2nd, 4th] 。4.2 解题思路
思路 1排序
先对数组 score 进行排序。再将对应前三个位置上的元素替换成对应的字符串Gold Medal, Silver Medal, Bronze Medal。
思路 1代码
class Solution:def findRelativeRanks(self, score: List[int]) - List[str]:mark(Gold Medal, Silver Medal, Bronze Medal)for i, j in enumerate(sorted(range(len(score)), keylambda x: -score[x])):score[j] str(i 1) if i 2 else mark[i]return score思路 1复杂度分析
时间复杂度 O ( n × log 2 n ) O(n \times \log_2n) O(n×log2n)。因为采用了时间复杂度为 O ( n × log 2 n ) O(n \times \log_2n) O(n×log2n) 的快速排序。空间复杂度 O ( n ) O(n) O(n)。
5. 0088. 合并两个有序数组
5.1 题目大意
描述给定两个有序数组 n u m s 1 nums1 nums1、 n u m s 2 nums2 nums2。
要求将 n u m s 2 nums2 nums2 合并到 n u m s 1 nums1 nums1 中使 n u m s 1 nums1 nums1 成为一个有序数组。
说明
给定数组 n u m s 1 nums1 nums1 空间大小为 m n m n mn 个其中前 m m m 个为 n u m s 1 nums1 nums1 的元素。 n u m s 2 nums2 nums2 空间大小为 n n n。这样可以用 n u m s 1 nums1 nums1 的空间来存储最终的有序数组。 n u m s 1. l e n g t h m n nums1.length m n nums1.lengthmn。 n u m s 2. l e n g t h n nums2.length n nums2.lengthn。 0 ≤ m , n ≤ 200 0 \le m, n \le 200 0≤m,n≤200。 1 ≤ m n ≤ 200 1 \le m n \le 200 1≤mn≤200。 − 1 0 9 ≤ n u m s 1 [ i ] , n u m s 2 [ j ] ≤ 1 0 9 -10^9 \le nums1[i], nums2[j] \le 10^9 −109≤nums1[i],nums2[j]≤109。
示例
示例 1
输入nums1 [1,2,3,0,0,0], m 3, nums2 [2,5,6], n 3
输出[1,2,2,3,5,6]
解释需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] 其中斜体加粗标注的为 nums1 中的元素。示例 2
输入nums1 [1], m 1, nums2 [], n 0
输出[1]
解释需要合并 [1] 和 [] 。
合并结果是 [1] 。5.2 解题思路
思路 1快慢指针
将两个指针 index1、index2 分别指向 nums1、nums2 数组的尾部再用一个指针 index 指向数组 nums1 的尾部。从后向前判断当前指针下 nums1[index1] 和 nums[index2] 的值大小将较大值存入 num1[index] 中然后继续向前遍历。最后再将 nums2 中剩余元素赋值到 num1 前面对应位置上。
思路 1代码
class Solution:def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) - None:m, n m - 1, 1while nums2:if m 0 and nums1[m] nums2[-1]:nums1[-n] nums1[m]m - 1else:nums1[-n] nums2.pop()n 1思路 1复杂度分析
时间复杂度 O ( m n ) O(m n) O(mn)。空间复杂度 O ( m n ) O(m n) O(mn)。
6. 剑指 Offer 51. 数组中的逆序对
6.1 题目大意
描述给定一个数组 nums。
要求计算出数组中的逆序对的总数。
说明
逆序对在数组中的两个数字如果前面一个数字大于后面的数字则这两个数字组成一个逆序对。 0 ≤ n u m s . l e n g t h ≤ 50000 0 \le nums.length \le 50000 0≤nums.length≤50000。
示例
输入: [7,5,6,4]
输出: 56.2 解题思路
思路 1树状数组
数组 tree[i] 表示数字 i 是否在序列中出现过如果数字 i 已经存在于序列中tree[i] 1否则 tree[i] 0。
按序列从左到右将值为 nums[i] 的元素当作下标为nums[i]赋值为 1 插入树状数组里这时比 nums[i] 大的数个数就是 i 1 - query(a)。将全部结果累加起来就是逆序数了。
思路 1代码
import bisectclass BinaryIndexTree:def __init__(self, n):self.size nself.tree [0 for _ in range(n 1)]def lowbit(self, index):return index (-index)def update(self, index, delta):while index self.size:self.tree[index] deltaindex self.lowbit(index)def query(self, index):res 0while index 0:res self.tree[index]index - self.lowbit(index)return resclass Solution:def reversePairs(self, nums: List[int]) - int:size len(nums)sort_nums sorted(nums)for i in range(size):nums[i] bisect.bisect_left(sort_nums, nums[i]) 1bit BinaryIndexTree(size)ans 0for i in range(size):bit.update(nums[i], 1)ans (i 1 - bit.query(nums[i]))return ans思路 1复杂度分析
时间复杂度 O ( n × log 2 n ) O(n \times \log_2n) O(n×log2n)。空间复杂度 O ( n ) O(n) O(n)。
八. 快速排序
1. 快速排序算法思想 快速排序Quick Sort基本思想 采用经典的分治策略选择数组中某个元素作为基准数通过一趟排序将数组分为独立的两个子数组一个子数组中所有元素值都比基准数小另一个子数组中所有元素值都比基准数大。然后再按照同样的方式递归的对两个子数组分别进行快速排序以达到整个数组有序。 2. 快速排序算法步骤
假设数组的元素个数为 n n n 个则快速排序的算法步骤如下
哨兵划分选取一个基准数将数组中比基准数大的元素移动到基准数右侧比他小的元素移动到基准数左侧。 从当前数组中找到一个基准数 p i v o t pivot pivot这里以当前数组第 1 1 1 个元素作为基准数即 p i v o t n u m s [ l o w ] pivot nums[low] pivotnums[low]。使用指针 i i i 指向数组开始位置指针 j j j 指向数组末尾位置。从右向左移动指针 j j j找到第 1 1 1 个小于基准值的元素。从左向右移动指针 i i i找到第 1 1 1 个大于基准数的元素。交换指针 i i i、指针 j j j 指向的两个元素位置。重复第 3 ∼ 5 3 \sim 5 3∼5 步直到指针 i i i 和指针 j j j 相遇时停止最后将基准数放到两个子数组交界的位置上。 递归分解完成哨兵划分之后对划分好的左右子数组分别进行递归排序。 按照基准数的位置将数组拆分为左右两个子数组。对每个子数组分别重复「哨兵划分」和「递归分解」直到各个子数组只有 1 1 1 个元素排序结束。
我们以 [ 4 , 7 , 5 , 2 , 6 , 1 , 3 ] [4, 7, 5, 2, 6, 1, 3] [4,7,5,2,6,1,3] 为例演示一下快速排序的整个步骤。
我们先来看一下单次「哨兵划分」的过程。 1 2 3 4 5 6 7 在经过一次「哨兵划分」过程之后数组就被划分为左子数组、基准数、右子树组三个独立部分。接下来只要对划分好的左右子数组分别进行递归排序即可完成排序。整个步骤如下 3. 快速排序代码实现
import randomclass Solution:# 随机哨兵划分从 nums[low: high 1] 中随机挑选一个基准数并进行移位排序def randomPartition(self, nums: [int], low: int, high: int) - int:# 随机挑选一个基准数i random.randint(low, high)# 将基准数与最低位互换nums[i], nums[low] nums[low], nums[i]# 以最低位为基准数然后将数组中比基准数大的元素移动到基准数右侧比他小的元素移动到基准数左侧。最后将基准数放到正确位置上return self.partition(nums, low, high)# 哨兵划分以第 1 位元素 nums[low] 为基准数然后将比基准数小的元素移动到基准数左侧将比基准数大的元素移动到基准数右侧最后将基准数放到正确位置上def partition(self, nums: [int], low: int, high: int) - int:# 以第 1 位元素为基准数pivot nums[low]i, j low, highwhile i j:# 从右向左找到第 1 个小于基准数的元素while i j and nums[j] pivot:j - 1# 从左向右找到第 1 个大于基准数的元素while i j and nums[i] pivot:i 1# 交换元素nums[i], nums[j] nums[j], nums[i]# 将基准节点放到正确位置上nums[i], nums[low] nums[low], nums[i]# 返回基准数的索引return idef quickSort(self, nums: [int], low: int, high: int) - [int]:if low high:# 按照基准数的位置将数组划分为左右两个子数组pivot_i self.randomPartition(nums, low, high)# 对左右两个子数组分别进行递归快速排序self.quickSort(nums, low, pivot_i - 1)self.quickSort(nums, pivot_i 1, high)return numsdef sortArray(self, nums: [int]) - [int]:return self.quickSort(nums, 0, len(nums) - 1)4. 快速排序算法分析
快速排序算法的时间复杂度主要跟基准数的选择有关。本文中是将当前数组中第 1 1 1 个元素作为基准值。
在这种选择下如果参加排序的元素初始时已经有序的情况下快速排序方法花费的时间最长。也就是会得到最坏时间复杂度。
在这种情况下第 1 1 1 趟排序经过 n − 1 n - 1 n−1 次比较以后将第 1 1 1 个元素仍然确定在原来的位置上并得到 1 1 1 个长度为 n − 1 n - 1 n−1 的子数组。第 2 2 2 趟排序进过 n − 2 n - 2 n−2 次比较以后将第 2 2 2 个元素确定在它原来的位置上又得到 1 1 1 个长度为 n − 2 n - 2 n−2 的子数组。
最终总的比较次数为 ( n − 1 ) ( n − 2 ) … 1 n ( n − 1 ) 2 (n − 1) (n − 2) … 1 \frac{n(n − 1)}{2} (n−1)(n−2)…12n(n−1)。因此这种情况下的时间复杂度为 O ( n 2 ) O(n^2) O(n2)也是最坏时间复杂度。
我们可以改进一下基准数的选择。如果每次我们选中的基准数恰好能将当前数组平分为两份也就是刚好取到当前数组的中位数。
在这种选择下每一次都将数组从 n n n 个元素变为 n 2 \frac{n}{2} 2n 个元素。此时的时间复杂度公式为 T ( n ) 2 × T ( n 2 ) Θ ( n ) T(n) 2 \times T(\frac{n}{2}) \Theta(n) T(n)2×T(2n)Θ(n)。根据主定理可以得出 T ( n ) O ( n × log n ) T(n) O(n \times \log n) T(n)O(n×logn)也是最佳时间复杂度。
而在平均情况下我们可以从当前数组中随机选择一个元素作为基准数。这样每一次选择的基准数可以看做是等概率随机的。其期望时间复杂度为 O ( n × log n ) O(n \times \log n) O(n×logn)也就是平均时间复杂度。
下面来总结一下
最佳时间复杂度 O ( n × log n ) O(n \times \log n) O(n×logn)。每一次选择的基准数都是当前数组的中位数此时算法时间复杂度满足的递推式为 T ( n ) 2 × T ( n 2 ) Θ ( n ) T(n) 2 \times T(\frac{n}{2}) \Theta(n) T(n)2×T(2n)Θ(n)由主定理可得 T ( n ) O ( n × log n ) T(n) O(n \times \log n) T(n)O(n×logn)。最坏时间复杂度 O ( n 2 ) O(n^2) O(n2)。每一次选择的基准数都是数组的最终位置上的值此时算法时间复杂度满足的递推式为 T ( n ) T ( n − 1 ) Θ ( n ) T(n) T(n - 1) \Theta(n) T(n)T(n−1)Θ(n)累加可得 T ( n ) O ( n 2 ) T(n) O(n^2) T(n)O(n2)。平均时间复杂度 O ( n × log n ) O(n \times \log n) O(n×logn)。在平均情况下每一次选择的基准数可以看做是等概率随机的。其期望时间复杂度为 O ( n × log n ) O(n \times \log n) O(n×logn)。空间复杂度 O ( n ) O(n) O(n)。无论快速排序算法递归与否排序过程中都需要用到堆栈或其他结构的辅助空间来存放当前待排序数组的首、尾位置。最坏的情况下空间复杂度为 O ( n ) O(n) O(n)。如果对算法进行一些改写在一趟排序之后比较被划分所得到的两个子数组的长度并且首先对长度较短的子数组进行快速排序这时候需要的空间复杂度可以达到 O ( l o g 2 n ) O(log_2 n) O(log2n)。排序稳定性在进行哨兵划分时基准数可能会被交换至相等元素的右侧。因此快速排序是一种 不稳定排序算法。
九. 堆排序
1. 堆结构
「堆排序Heap sort」是一种基于「堆结构」实现的高效排序算法。在介绍「堆排序」之前我们先来了解一下什么是「堆结构」。
1.1 堆的定义 堆Heap一种满足以下两个条件之一的完全二叉树 大顶堆Max Heap任意节点值 ≥ 其子节点值。小顶堆Min Heap任意节点值 ≤ 其子节点值。 1.2 堆的存储结构
堆的逻辑结构就是一颗完全二叉树。而我们在「07.树 - 01.二叉树 - 01.树与二叉树的基础知识」章节中学过对于完全二叉树尤其是满二叉树来说采用顺序存储结构数组的形式来表示完全二叉树能够充分利用存储空间。
当我们使用顺序存储结构即数组来表示堆时堆中元素的节点编号与数组的索引关系为
如果某二叉树节点非叶子节点的下标为 i i i那么其左孩子节点下标为 2 × i 1 2 \times i 1 2×i1右孩子节点下标为 2 × i 2 2 \times i 2 2×i2。如果某二叉树节点非根结点的下标为 i i i那么其根节点下标为 ⌊ i − 1 2 ⌋ \lfloor \frac{i - 1}{2} \rfloor ⌊2i−1⌋向下取整。
class MaxHeap:def __init__(self):self.max_heap []1.3 访问堆顶元素 访问堆顶元素指的是从堆结构中获取位于堆顶的元素。 在堆中堆顶元素位于根节点当我们使用顺序存储结构即数组来表示堆时堆顶元素就是数组的首个元素。
class MaxHeap:......def peek(self) - int:# 大顶堆为空if not self.max_heap:return None# 返回堆顶元素return self.max_heap[0]访问堆顶元素不依赖于数组中元素个数因此时间复杂度为 O ( 1 ) O(1) O(1)。
1.4 向堆中插入元素 向堆中插入元素指的将一个新的元素添加到堆中调整堆结构以保持堆的特性不变。 向堆中插入元素的步骤如下
将新元素添加到堆的末尾保持完全二叉树的结构。从新插入的元素节点开始将该节点与其父节点进行比较。 如果新节点的值大于其父节点的值则交换它们以保持最大堆的特性。如果新节点的值小于等于其父节点的值说明已满足最大堆的特性此时结束。 重复上述比较和交换步骤直到新节点不再大于其父节点或者达到了堆的根节点。
这个过程称为「上移调整Shift Up」。因为新插入的元素会逐步向堆的上方移动直到找到了合适的位置保持堆的有序性。 1 2 3 4 5 6 7 class MaxHeap:......def push(self, val: int):# 将新元素添加到堆的末尾self.max_heap.append(val)size len(self.max_heap)# 从新插入的元素节点开始进行上移调整self.__shift_up(size - 1)def __shift_up(self, i: int):while (i - 1) // 2 0 and self.max_heap[i] self.max_heap[(i - 1) // 2]:self.max_heap[i], self.max_heap[(i - 1) // 2] self.max_heap[(i - 1) // 2], self.max_heap[i]i (i - 1) // 2在最坏情况下「向堆中插入元素」的时间复杂度为 O ( log n ) O(\log n) O(logn)其中 n n n 是堆中元素的数量这是因为堆的高度是 log n \log n logn。
1.5 删除堆顶元素 删除堆顶元素指的是从堆中移除位于堆顶的元素并重新调整对结果以保持堆的特性不变。 删除堆顶元素的步骤如下
将堆顶元素即根节点与堆的末尾元素交换。移除堆末尾的元素之前的堆顶即将其从堆中剔除。从新的堆顶元素开始将其与其较大的子节点进行比较。 如果当前节点的值小于其较大的子节点则将它们交换。这一步是为了将新的堆顶元素「下沉」到适当的位置以保持最大堆的特性。如果当前节点的值大于等于其较大的子节点说明已满足最大堆的特性此时结束。 重复上述比较和交换步骤直到新的堆顶元素不再小于其子节点或者达到了堆的底部。
这个过程称为「下移调整Shift Down」。因为新的堆顶元素会逐步向堆的下方移动直到找到了合适的位置保持堆的有序性。 1 2 3 4 5 6 7 class MaxHeap:...... def pop(self) - int:# 堆为空if not self.max_heap:raise IndexError(堆为空)size len(self.max_heap)self.max_heap[0], self.max_heap[size - 1] self.max_heap[size - 1], self.max_heap[0]# 删除堆顶元素val self.max_heap.pop()# 节点数减 1size - 1 # 下移调整self.__shift_down(0, size)# 返回堆顶元素return valdef __shift_down(self, i: int, n: int):while 2 * i 1 n:# 左右子节点编号left, right 2 * i 1, 2 * i 2# 找出左右子节点中的较大值节点编号if 2 * i 2 n:# 右子节点编号超出范围只有左子节点larger leftelse:# 左子节点、右子节点都存在if self.max_heap[left] self.max_heap[right]:larger leftelse:larger right# 将当前节点值与其较大的子节点进行比较if self.max_heap[i] self.max_heap[larger]:# 如果当前节点值小于其较大的子节点则将它们交换self.max_heap[i], self.max_heap[larger] self.max_heap[larger], self.max_heap[i]i largerelse:# 如果当前节点值大于等于于其较大的子节点此时结束break「删除堆顶元素」的时间复杂度通常为 O ( log n ) O(\log n) O(logn)其中 n n n 是堆中元素的数量因为堆的高度是 log n \log n logn。
2. 堆排序
2.1 堆排序算法思想 堆排序Heap sort基本思想 借用「堆结构」所设计的排序算法。将数组转化为大顶堆重复从大顶堆中取出数值最大的节点并让剩余的堆结构继续维持大顶堆性质。 2.2 堆排序算法步骤 构建初始大顶堆 定义一个数组实现的堆结构将原始数组的元素依次存入堆结构的数组中初始顺序不变。从数组的中间位置开始从右至左依次通过「下移调整」将数组转换为一个大顶堆。 交换元素调整堆 交换堆顶元素第 1 1 1 个元素与末尾最后 1 1 1 个元素的位置交换完成后堆的长度减 1 1 1。交换元素之后由于堆顶元素发生了改变需要从根节点开始对当前堆进行「下移调整」使其保持堆的特性。 重复交换和调整堆 重复第 2 2 2 步直到堆的大小为 1 1 1 时此时大顶堆的数组已经完全有序。
2.2.1 构建初始大顶堆 1 2 3 4 5 6 7 2.2.2 交换元素调整堆 1 2 3 4 5 6 7 8 9 10 11 12 2.3 堆排序代码实现
class MaxHeap:......def __buildMaxHeap(self, nums: [int]):size len(nums)# 先将数组 nums 的元素按顺序添加到 max_heap 中for i in range(size):self.max_heap.append(nums[i])# 从最后一个非叶子节点开始进行下移调整for i in range((size - 2) // 2, -1, -1):self.__shift_down(i, size)def maxHeapSort(self, nums: [int]) - [int]:# 根据数组 nums 建立初始堆self.__buildMaxHeap(nums)size len(self.max_heap)for i in range(size - 1, -1, -1):# 交换根节点与当前堆的最后一个节点self.max_heap[0], self.max_heap[i] self.max_heap[i], self.max_heap[0]# 从根节点开始对当前堆进行下移调整self.__shift_down(0, i)# 返回排序后的数组return self.max_heapclass Solution:def maxHeapSort(self, nums: [int]) - [int]:return MaxHeap().maxHeapSort(nums)def sortArray(self, nums: [int]) - [int]:return self.maxHeapSort(nums)print(Solution().sortArray([10, 25, 6, 8, 7, 1, 20, 23, 16, 19, 17, 3, 18, 14]))2.4 堆排序算法分析
时间复杂度 O ( n × log n ) O(n \times \log n) O(n×logn)。 堆积排序的时间主要花费在两个方面「建立初始堆」和「下移调整」。设原始数组所对应的完全二叉树深度为 d d d算法由两个独立的循环组成 在第 1 1 1 个循环构造初始堆积时从 i d − 1 i d - 1 id−1 层开始到 i 1 i 1 i1 层为止对每个分支节点都要调用一次调整堆算法而一次调整堆算法对于第 i i i 层一个节点到第 d d d 层上建立的子堆积所有节点可能移动的最大距离为该子堆积根节点移动到最后一层第 d d d 层 的距离即 d − i d - i d−i。而第 i i i 层上节点最多有 2 i − 1 2^{i-1} 2i−1 个所以每一次调用调整堆算法的最大移动距离为 2 i − 1 ∗ ( d − i ) 2^{i-1} * (d-i) 2i−1∗(d−i)。因此堆积排序算法的第 1 1 1 个循环所需时间应该是各层上的节点数与该层上节点可移动的最大距离之积的总和即 ∑ i d − 1 1 2 i − 1 ( d − i ) ∑ j 1 d − 1 2 d − j − 1 × j ∑ j 1 d − 1 2 d − 1 × j 2 j ≤ n × ∑ j 1 d − 1 j 2 j 2 × n \sum_{i d - 1}^1 2^{i-1} (d-i) \sum_{j 1}^{d-1} 2^{d-j-1} \times j \sum_{j 1}^{d-1} 2^{d-1} \times {j \over 2^j} \le n \times \sum_{j 1}^{d-1} {j \over 2^j} 2 \times n ∑id−112i−1(d−i)∑j1d−12d−j−1×j∑j1d−12d−1×2jj≤n×∑j1d−12jj2×n。这一部分的时间花费为 O ( n ) O(n) O(n)。在第 2 2 2 个循环中每次调用调整堆算法一次节点移动的最大距离为这棵完全二叉树的深度 d ⌊ log 2 ( n ) ⌋ 1 d \lfloor \log_2(n) \rfloor 1 d⌊log2(n)⌋1一共调用了 n − 1 n - 1 n−1 次调整堆算法所以第 2 2 2 个循环的时间花费为 ( n − 1 ) ( ⌊ log 2 ( n ) ⌋ 1 ) O ( n × log n ) (n-1)(\lfloor \log_2 (n)\rfloor 1) O(n \times \log n) (n−1)(⌊log2(n)⌋1)O(n×logn)。 因此堆积排序的时间复杂度为 O ( n × log n ) O(n \times \log n) O(n×logn)。 空间复杂度 O ( 1 ) O(1) O(1)。由于在堆积排序中只需要一个记录大小的辅助空间因此堆积排序的空间复杂度为 O ( 1 ) O(1) O(1)。排序稳定性在进行「下移调整」时相等元素的相对位置可能会发生变化。因此堆排序是一种 不稳定排序算法。
十. 练习题目3
7. 0075. 颜色分类
7.1 题目大意
描述给定一个数组 nums元素值只有 0、1、2分别代表红色、白色、蓝色。
要求将数组进行排序使得红色在前白色在中间蓝色在最后。
说明
要求不使用标准库函数同时仅用常数空间一趟扫描解决。 n n u m s . l e n g t h n nums.length nnums.length。 1 ≤ n ≤ 300 1 \le n \le 300 1≤n≤300。nums[i] 为 0、1 或 2。
示例
输入nums [2,0,2,1,1,0]
输出[0,0,1,1,2,2]输入nums [2,0,1]
输出[0,1,2]7.2 解题思路
思路 1双指针 快速排序思想
快速排序算法中的 partition 过程利用双指针将序列中比基准数 pivot 大的元素移动到了基准数右侧将比基准数 pivot 小的元素移动到了基准数左侧。从而将序列分为了三部分比基准数小的部分、基准数、比基准数大的部分。
这道题我们也可以借鉴快速排序算法中的 partition 过程将 1 作为基准数 pivot然后将序列分为三部分0即比 1 小的部分、等于 1 的部分、2即比 1 大的部分。具体步骤如下
使用两个指针 left、right分别指向数组的头尾。left 表示当前处理好红色元素的尾部right 表示当前处理好蓝色的头部。再使用一个下标 index 遍历数组如果遇到 nums[index] 0就交换 nums[index] 和 nums[left]同时将 left 右移。如果遇到 nums[index] 2就交换 nums[index] 和 nums[right]同时将 right 左移。直到 index 移动到 right 位置之后停止遍历。遍历结束之后此时 left 左侧都是红色right 右侧都是蓝色。
注意移动的时候需要判断 index 和 left 的位置因为 left 左侧是已经处理好的数组所以需要判断 index 的位置是否小于 left小于的话需要更新 index 位置。
思路 1代码
class Solution:def sortColors(self, nums: List[int]) - None:n len(nums)p0, p2 0, n - 1i 0while i p2:while i p2 and nums[i] 2:nums[i], nums[p2] nums[p2], nums[i]p2 - 1if nums[i] 0:nums[i], nums[p0] nums[p0], nums[i]p0 1i 1思路 1复杂度分析
时间复杂度 O ( n ) O(n) O(n)。空间复杂度 O ( 1 ) O(1) O(1)。
8. 0215. 数组中的第K个最大元素
8.1 题目大意
描述给定一个未排序的整数数组 nums 和一个整数 k。
要求返回数组中第 k 个最大的元素。
说明
要求使用时间复杂度为 O ( n ) O(n) O(n) 的算法解决此问题。 1 ≤ k ≤ n u m s . l e n g t h ≤ 1 0 5 1 \le k \le nums.length \le 10^5 1≤k≤nums.length≤105。 − 1 0 4 ≤ n u m s [ i ] ≤ 1 0 4 -10^4 \le nums[i] \le 10^4 −104≤nums[i]≤104。
示例
输入: [3,2,1,5,6,4], k 2
输出: 5输入: [3,2,3,1,2,4,5,5,6], k 4
输出: 48.2 解题思路
思路 1快速排序
使用快速排序在每次调整时都会确定一个元素的最终位置且以该元素为界限将数组分成了左右两个子数组左子数组中的元素都比该元素小右子树组中的元素都比该元素大。
这样只要某次划分的元素恰好是第 k 个下标就找到了答案。并且我们只需关注第 k 个最大元素所在区间的排序情况与第 k 个最大元素无关的区间排序都可以忽略。这样进一步减少了执行步骤。
思路 1代码
import randomclass Solution:# 从 arr[low: high 1] 中随机挑选一个基准数并进行移动排序def randomPartition(self, arr: [int], low: int, high: int):# 随机挑选一个基准数i random.randint(low, high)# 将基准数与最低位互换arr[i], arr[low] arr[low], arr[i]# 以最低位为基准数然后将序列中比基准数大的元素移动到基准数右侧比他小的元素移动到基准数左侧。最后将基准数放到正确位置上return self.partition(arr, low, high)# 以最低位为基准数然后将序列中比基准数大的元素移动到基准数右侧比他小的元素移动到基准数左侧。最后将基准数放到正确位置上def partition(self, arr: [int], low: int, high: int):pivot arr[low] # 以第 1 为为基准数i low 1 # 从基准数后 1 位开始遍历保证位置 i 之前的元素都小于基准数for j in range(i, high 1):# 发现一个小于基准数的元素if arr[j] pivot:# 将小于基准数的元素 arr[j] 与当前 arr[i] 进行换位保证位置 i 之前的元素都小于基准数arr[i], arr[j] arr[j], arr[i]# i 之前的元素都小于基准数所以 i 向右移动一位i 1# 将基准节点放到正确位置上arr[i - 1], arr[low] arr[low], arr[i - 1]# 返回基准数位置return i - 1def quickSort(self, arr, low, high, k):size len(arr)if low high:# 按照基准数的位置将序列划分为左右两个子序列pi self.randomPartition(arr, low, high)if pi size - k:return arr[size - k]if pi size - k:# 对左子序列进行递归快速排序self.quickSort(arr, low, pi - 1, k)if pi size - k:# 对右子序列进行递归快速排序self.quickSort(arr, pi 1, high, k)return arr[size - k]def findKthLargest(self, nums: List[int], k: int) - int:return self.quickSort(nums, 0, len(nums) - 1, k)思路 1复杂度分析
时间复杂度 O ( n ) O(n) O(n)。证明过程可参考「算法导论 9.2期望为线性的选择算法」。空间复杂度 O ( log 2 n ) O(\log_2 n) O(log2n)。递归使用栈空间的空间代价期望为 O ( log 2 n ) O(\log_2n) O(log2n)。
9. 剑指 Offer 40. 最小的k个数
9.1 题目大意
描述给定整数数组 arr再给定一个整数 k。
要求返回数组 arr 中最小的 k 个数。
说明 0 ≤ k ≤ a r r . l e n g t h ≤ 10000 0 \le k \le arr.length \le 10000 0≤k≤arr.length≤10000。 0 ≤ a r r [ i ] ≤ 10000 0 \le arr[i] \le 10000 0≤arr[i]≤10000。
示例
输入arr [3,2,1], k 2
输出[1,2] 或者 [2,1]输入arr [0,1,2,1], k 1
输出[0]9.2 解题思路
思路 1排序
对原数组从小到大排序后取出前 k个数即可。
思路 1代码
class Solution:def getLeastNumbers(self, arr: List[int], k: int) - List[int]:arr.sort()return arr[:k]思路 1复杂度分析
时间复杂度 O ( n log 2 n ) O(n\log_2n) O(nlog2n)。空间复杂度 O ( log 2 n ) O(\log_2n) O(log2n)。
十一. 计数排序 计数排序Counting Sort基本思想 通过统计数组中每个元素在数组中出现的次数根据这些统计信息将数组元素有序的放置到正确位置从而达到排序的目的。 2. 计数排序算法步骤 计算排序范围遍历数组找出待排序序列中最大值元素 n u m s ‾ m a x nums\underline{}max numsmax 和最小值元素 n u m s ‾ m i n nums\underline{}min numsmin计算出排序范围为 n u m s ‾ m a x − n u m s ‾ m i n 1 nums\underline{}max - nums\underline{}min 1 numsmax−numsmin1。 定义计数数组定义一个大小为排序范围的计数数组 c o u n t s counts counts用于统计每个元素的出现次数。其中 数组的索引值 n u m − n u m s ‾ m i n num - nums\underline{}min num−numsmin 表示元素的值为 n u m num num。数组的值 c o u n t s [ n u m − n u m s ‾ m i n ] counts[num - nums\underline{}min] counts[num−numsmin] 表示元素 n u m num num 的出现次数。 对数组元素进行计数统计遍历待排序数组 n u m s nums nums对每个元素在计数数组中进行计数即将待排序数组中「每个元素值减去最小值」作为索引将「对计数数组中的值」加 1 1 1即令 c o u n t s [ n u m − n u m s ‾ m i n ] counts[num - nums\underline{}min] counts[num−numsmin] 加 1 1 1。 生成累积计数数组从 c o u n t s counts counts 中的第 1 1 1 个元素开始每一项累家前一项和。此时 c o u n t s [ n u m − n u m s ‾ m i n ] counts[num - nums\underline{}min] counts[num−numsmin] 表示值为 n u m num num 的元素在排序数组中最后一次出现的位置。 逆序填充目标数组逆序遍历数组 n u m s nums nums将每个元素 n u m num num 填入正确位置。 将其填充到结果数组 r e s res res 的索引 c o u n t s [ n u m − n u m s ‾ m i n ] counts[num - nums\underline{}min] counts[num−numsmin] 处。 放入后令累积计数数组中对应索引减 1 1 1从而得到下个元素 n u m num num 的放置位置。
我们以 [ 3 , 0 , 4 , 2 , 5 , 1 , 3 , 1 , 4 , 5 ] [3, 0, 4, 2, 5, 1, 3, 1, 4, 5] [3,0,4,2,5,1,3,1,4,5] 为例演示一下计数排序的整个步骤。 3. 计数排序代码实现
class Solution:def countingSort(self, nums: [int]) - [int]:# 计算待排序数组中最大值元素 nums_max 和最小值元素 nums_minnums_min, nums_max min(nums), max(nums)# 定义计数数组 counts大小为 最大值元素 - 最小值元素 1size nums_max - nums_min 1counts [0 for _ in range(size)]# 统计值为 num 的元素出现的次数for num in nums:counts[num - nums_min] 1# 生成累积计数数组for i in range(1, size):counts[i] counts[i - 1]# 反向填充目标数组res [0 for _ in range(len(nums))]for i in range(len(nums) - 1, -1, -1):num nums[i]# 根据累积计数数组将 num 放在数组对应位置res[counts[num - nums_min] - 1] num# 将 num 的对应放置位置减 1从而得到下个元素 num 的放置位置counts[nums[i] - nums_min] - 1return resdef sortArray(self, nums: [int]) - [int]:return self.countingSort(nums)4. 计数排序算法分析
时间复杂度 O ( n k ) O(n k) O(nk)。其中 k k k 代表待排序数组的值域。空间复杂度 O ( k ) O(k) O(k)。其中 k k k 代表待排序序列的值域。由于用于计数的数组 c o u n t s counts counts 的长度取决于待排序数组中数据的范围大小等于待排序数组最大值减去最小值再加 1 1 1。所以计数排序算法对于数据范围很大的数组需要大量的内存。计数排序适用情况计数排序一般用于整数排序不适用于按字母顺序、人名顺序排序。排序稳定性由于向结果数组中填充元素时使用的是逆序遍历可以避免改变相等元素之间的相对顺序。因此计数排序是一种 稳定排序算法。
十二. 桶排序
1. 桶排序算法思想 桶排序Bucket Sort基本思想 将待排序数组中的元素分散到若干个「桶」中然后对每个桶中的元素再进行单独排序。 2. 桶排序算法步骤
确定桶的数量根据待排序数组的值域范围将数组划分为 k k k 个桶每个桶可以看做是一个范围区间。分配元素遍历待排序数组元素将每个元素根据大小分配到对应的桶中。对每个桶进行排序对每个非空桶内的元素单独排序使用插入排序、归并排序、快排排序等算法。合并桶内元素将排好序的各个桶中的元素按照区间顺序依次合并起来形成一个完整的有序数组。
我们以 [ 39 , 49 , 8 , 13 , 22 , 15 , 10 , 30 , 5 , 44 ] [39, 49, 8, 13, 22, 15, 10, 30, 5, 44] [39,49,8,13,22,15,10,30,5,44] 为例演示一下桶排序的整个步骤。 3. 桶排序代码实现
class Solution:def insertionSort(self, nums: [int]) - [int]:# 遍历无序区间for i in range(1, len(nums)):temp nums[i]j i# 从右至左遍历有序区间while j 0 and nums[j - 1] temp:# 将有序区间中插入位置右侧的元素依次右移一位nums[j] nums[j - 1]j - 1# 将该元素插入到适当位置nums[j] tempreturn numsdef bucketSort(self, nums: [int], bucket_size5) - [int]:# 计算待排序序列中最大值元素 nums_max、最小值元素 nums_minnums_min, nums_max min(nums), max(nums)# 定义桶的个数为 (最大值元素 - 最小值元素) // 每个桶的大小 1bucket_count (nums_max - nums_min) // bucket_size 1# 定义桶数组 bucketsbuckets [[] for _ in range(bucket_count)]# 遍历待排序数组元素将每个元素根据大小分配到对应的桶中for num in nums:buckets[(num - nums_min) // bucket_size].append(num)# 对每个非空桶内的元素单独排序排序之后按照区间顺序依次合并到 res 数组中res []for bucket in buckets:self.insertionSort(bucket)res.extend(bucket)# 返回结果数组return resdef sortArray(self, nums: [int]) - [int]:return self.bucketSort(nums)4. 桶排序算法分析
时间复杂度 O ( n ) O(n) O(n)。当输入元素个数为 n n n桶的个数是 m m m 时每个桶里的数据就是 k n m k \frac{n}{m} kmn 个。每个桶内排序的时间复杂度为 O ( k × log k ) O(k \times \log k) O(k×logk)。 m m m 个桶就是 m × O ( k × log k ) m × O ( n m × log n m ) O ( n × log n m ) m \times O(k \times \log k) m \times O(\frac{n}{m} \times \log \frac{n}{m}) O(n \times \log \frac{n}{m}) m×O(k×logk)m×O(mn×logmn)O(n×logmn)。当桶的个数 m m m 接近于数据个数 n n n 时 log n m \log \frac{n}{m} logmn 就是一个较小的常数所以排序桶排序时间复杂度接近于 O ( n ) O(n) O(n)。空间复杂度 O ( n m ) O(n m) O(nm)。由于桶排序使用了辅助空间所以桶排序的空间复杂度是 O ( n m ) O(n m) O(nm)。排序稳定性桶排序的稳定性取决于桶内使用的排序算法。如果桶内使用稳定的排序算法比如插入排序算法并且在合并桶的过程中保持相等元素的相对顺序不变则桶排序是一种 稳定排序算法。反之则桶排序是一种 不稳定排序算法。
十三. 基数排序
1. 基数排序算法思想 基数排序Radix Sort基本思想 将整数按位数切割成不同的数字然后从低位开始依次到高位逐位进行排序从而达到排序的目的。 2. 基数排序算法步骤
基数排序算法可以采用「最低位优先法Least Significant Digit First」或者「最高位优先法Most Significant Digit first」。最常用的是「最低位优先法」。
下面我们以最低位优先法为例讲解一下算法步骤。
确定排序的最大位数遍历数组元素获取数组最大值元素并取得对应位数。从最低位个位开始到最高位为止逐位对每一位进行排序 定义一个长度为 10 10 10 的桶数组 b u c k e t s buckets buckets每个桶分别代表 0 ∼ 9 0 \sim 9 0∼9 中的 1 1 1 个数字。按照每个元素当前位上的数字将元素放入对应数字的桶中。清空原始数组然后按照桶的顺序依次取出对应元素重新加入到原始数组中。
我们以 [ 692 , 924 , 969 , 503 , 871 , 704 , 542 , 436 ] [692, 924, 969, 503, 871, 704, 542, 436] [692,924,969,503,871,704,542,436] 为例演示一下基数排序的整个步骤。 3. 基数排序代码实现
class Solution:def radixSort(self, nums: [int]) - [int]:# 桶的大小为所有元素的最大位数size len(str(max(nums)))# 从最低位个位开始逐位遍历每一位for i in range(size):# 定义长度为 10 的桶数组 buckets每个桶分别代表 0 ~ 9 中的 1 个数字。buckets [[] for _ in range(10)]# 遍历数组元素按照每个元素当前位上的数字将元素放入对应数字的桶中。for num in nums:buckets[num // (10 ** i) % 10].append(num)# 清空原始数组nums.clear()# 按照桶的顺序依次取出对应元素重新加入到原始数组中。for bucket in buckets:for num in bucket:nums.append(num)# 完成排序返回结果数组return numsdef sortArray(self, nums: [int]) - [int]:return self.radixSort(nums)4. 基数排序算法分析
时间复杂度 O ( n × k ) O(n \times k) O(n×k)。其中 n n n 是待排序元素的个数 k k k 是数字位数。 k k k 的大小取决于数字位的选择十进制位、二进制位和待排序元素所属数据类型全集的大小。空间复杂度 O ( n k ) O(n k) O(nk)。排序稳定性基数排序采用的桶排序是稳定的。基数排序是一种 稳定排序算法。
十四. 练习题目4
10. 1122. 数组的相对排序
10.1 题目大意
描述给定两个数组arr1 和 arr2其中 arr2 中的元素各不相同arr2 中的每个元素都出现在 arr1 中。
要求对 arr1 中的元素进行排序使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 的末尾。
说明 1 ≤ a r r 1. l e n g t h , a r r 2. l e n g t h ≤ 1000 1 \le arr1.length, arr2.length \le 1000 1≤arr1.length,arr2.length≤1000。 0 ≤ a r r 1 [ i ] , a r r 2 [ i ] ≤ 1000 0 \le arr1[i], arr2[i] \le 1000 0≤arr1[i],arr2[i]≤1000。
示例
输入arr1 [2,3,1,3,2,4,6,7,9,2,19], arr2 [2,1,4,3,9,6]
输出[2,2,2,1,4,3,3,9,6,7,19]输入arr1 [28,6,22,8,44,17], arr2 [22,28,8,6]
输出[22,28,8,6,17,44]10.2 解题思路
思路 1计数排序
因为元素值范围在 [0, 1000]所以可以使用计数排序的思路来解题。
使用数组 count 统计 arr1 各个元素个数。遍历 arr2 数组将对应元素num2 按照个数 count[num2] 添加到答案数组 ans 中同时在 count 数组中减去对应个数。然后在处理 count 中剩余元素将 count 中大于 0 的元素下标依次添加到答案数组 ans 中。最后返回答案数组 ans。
思路 1代码
class Solution:def relativeSortArray(self, arr1: List[int], arr2: List[int]) - List[int]:# 计算待排序序列中最大值元素 arr_max 和最小值元素 arr_minarr1_min, arr1_max min(arr1), max(arr1)# 定义计数数组 counts大小为 最大值元素 - 最小值元素 1size arr1_max - arr1_min 1counts [0 for _ in range(size)]# 统计值为 num 的元素出现的次数for num in arr1:counts[num - arr1_min] 1res []for num in arr2:while counts[num - arr1_min] 0:res.append(num)counts[num - arr1_min] - 1for i in range(size):while counts[i] 0:num i arr1_minres.append(num)counts[i] - 1return res思路 1复杂度分析
时间复杂度 O ( m n m a x ( a r r 1 ) ) O(m n max(arr_1)) O(mnmax(arr1))。其中 m m m 是数组 a r r 1 arr_1 arr1 的长度 n n n 是数组 a r r 2 arr_2 arr2 的长度 m a x ( a r r 1 ) max(arr_1) max(arr1) 是数组 a r r 1 arr_1 arr1 的最大值。空间复杂度 O ( m a x ( a r r 1 ) ) O(max(arr_1)) O(max(arr1))。
11. 0220. 存在重复元素 III
11.1 题目大意
描述给定一个整数数组 nums以及两个整数 k、t。
要求判断数组中是否存在两个不同下标的 i 和 j其对应元素满足 abs(nums[i] - nums[j]) t同时满足 abs(i - j) k。如果满足条件则返回 True不满足条件返回 False。
说明 0 ≤ n u m s . l e n g t h ≤ 2 ∗ 1 0 4 0 \le nums.length \le 2 * 10^4 0≤nums.length≤2∗104。 − 2 31 ≤ n u m s [ i ] ≤ 2 31 − 1 -2^{31} \le nums[i] \le 2^{31} - 1 −231≤nums[i]≤231−1。 0 ≤ k ≤ 1 0 4 0 \le k \le 10^4 0≤k≤104。 0 ≤ t ≤ 2 31 − 1 0 \le t \le 2^{31} - 1 0≤t≤231−1。
示例
输入nums [1,2,3,1], k 3, t 0
输出True输入nums [1,0,1,1], k 1, t 2
输出True11.2 解题思路
思路 1滑动窗口固定长度
使用一个长度为 k 的滑动窗口每次遍历到 nums[right] 时滑动窗口内最多包含 nums[right] 之前最多 k 个元素。只需要检查前 k 个元素是否在 [nums[right] - t, nums[right] t] 区间内即可。检查 k 个元素是否在 [nums[right] - t, nums[right] t] 区间可以借助保证有序的数据结构比如 SortedList 二分查找来解决从而减少时间复杂度。
具体步骤如下
使用有序数组类 window 维护一个长度为 k 的窗口满足数组内元素有序且支持增加和删除操作。left、right 都指向序列的第一个元素。即left 0right 0。将当前元素填入窗口中即 window.add(nums[right])。当窗口元素大于 k 个时即 right - left k移除窗口最左侧元素并向右移动 left。当窗口元素小于等于 k 个时 使用二分查找算法查找 nums[right] 在 window 中的位置 idx。判断 window[idx] 与相邻位置上元素差值绝对值若果满足 abs(window[idx] - window[idx - 1]) t 或者 abs(window[idx 1] - window[idx]) t 时返回 True。 向右移动 right。重复 3 ~ 6 步直到 right 到达数组末尾如果还没找到满足条件的情况则返回 False。
思路 1代码
from sortedcontainers import SortedListclass Solution:def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) - bool:size len(nums)window SortedList()left, right 0, 0while right size:window.add(nums[right])if right - left k:window.remove(nums[left])left 1idx bisect.bisect_left(window, nums[right])if idx 0 and abs(window[idx] - window[idx - 1]) t:return Trueif idx len(window) - 1 and abs(window[idx 1] - window[idx]) t:return Trueright 1return False思路 1复杂度分析
时间复杂度 O ( n × log 2 ( m i n ( n , k ) ) ) O(n \times \log_2(min(n, k))) O(n×log2(min(n,k)))。空间复杂度 O ( m i n ( n , k ) ) O(min(n, k)) O(min(n,k))。
12. 0164. 最大间距
12.1 题目大意
描述给定一个无序数组 nums。
要求找出数组在排序之后相邻元素之间最大的差值。如果数组元素个数小于 2则返回 0。
说明
所有元素都是非负整数且数值在 32 位有符号整数范围内。请尝试在线性时间复杂度和空间复杂度的条件下解决此问题。
示例
输入: nums [3,6,9,1]
输出: 3
解释: 排序后的数组是 [1,3,6,9], 其中相邻元素 (3,6) 和 (6,9) 之间都存在最大差值 3。输入: nums [10]
输出: 0
解释: 数组元素个数小于 2因此返回 0。12.2 解题思路
思路 1基数排序
这道题的难点在于要求时间复杂度和空间复杂度为 O ( n ) O(n) O(n)。
这道题分为两步
数组排序。计算相邻元素之间的差值。
第 2 步直接遍历数组求解即可时间复杂度为 O ( n ) O(n) O(n)。所以关键点在于找到一个时间复杂度和空间复杂度为 O ( n ) O(n) O(n) 的排序算法。根据题意可知所有元素都是非负整数且数值在 32 位有符号整数范围内。所以我们可以选择基数排序。基数排序的步骤如下
遍历数组元素获取数组最大值元素并取得位数。以个位元素为索引对数组元素排序。合并数组。之后依次以十位百位…直到最大值元素的最高位处值为索引进行排序并合并数组最终完成排序。
最后还要注意数组元素个数小于 2 的情况需要特别判断一下。
思路 1代码
class Solution:def radixSort(self, arr):size len(str(max(arr)))for i in range(size):buckets [[] for _ in range(10)]for num in arr:buckets[num // (10 ** i) % 10].append(num)arr.clear()for bucket in buckets:for num in bucket:arr.append(num)return arrdef maximumGap(self, nums: List[int]) - int:if len(nums) 2:return 0arr self.radixSort(nums)return max(arr[i] - arr[i - 1] for i in range(1, len(arr)))思路 1复杂度分析
时间复杂度 O ( n ) O(n) O(n)。空间复杂度 O ( n ) O(n) O(n)。
十五. 排序算法题目
冒泡排序题目
题号标题题解标签难度剑指 Offer 45把数组排成最小的数网页链接、Github 链接贪心、字符串、排序中等0283移动零网页链接、Github 链接数组、双指针简单
选择排序题目
题号标题题解标签难度0215数组中的第K个最大元素网页链接、Github 链接数组、分治、快速选择、排序、堆优先队列中等
插入排序题目
题号标题题解标签难度0075颜色分类网页链接、Github 链接数组、双指针、排序中等
希尔排序题目
题号标题题解标签难度0912排序数组网页链接、Github 链接数组、分治、桶排序、计数排序、基数排序、排序、堆优先队列、归并排序中等0506相对名次网页链接、Github 链接数组、排序、堆优先队列简单
归并排序题目
题号标题题解标签难度0912排序数组网页链接、Github 链接数组、分治、桶排序、计数排序、基数排序、排序、堆优先队列、归并排序中等0088合并两个有序数组网页链接、Github 链接数组、双指针、排序简单剑指 Offer 51数组中的逆序对网页链接、Github 链接树状数组、线段树、数组、二分查找、分治、有序集合、归并排序困难0315计算右侧小于当前元素的个数树状数组、线段树、数组、二分查找、分治、有序集合、归并排序困难
快速排序题目
题号标题题解标签难度0912排序数组网页链接、Github 链接数组、分治、桶排序、计数排序、基数排序、排序、堆优先队列、归并排序中等0169多数元素网页链接、Github 链接数组、哈希表、分治、计数、排序简单
堆排序题目
题号标题题解标签难度0912排序数组网页链接、Github 链接数组、分治、桶排序、计数排序、基数排序、排序、堆优先队列、归并排序中等0215数组中的第K个最大元素网页链接、Github 链接数组、分治、快速选择、排序、堆优先队列中等剑指 Offer 40最小的k个数网页链接、Github 链接数组、分治、快速选择、排序、堆优先队列简单
计数排序题目
题号标题题解标签难度0912排序数组网页链接、Github 链接数组、分治、桶排序、计数排序、基数排序、排序、堆优先队列、归并排序中等1122数组的相对排序网页链接、Github 链接数组、哈希表、计数排序、排序简单
桶排序题目
题号标题题解标签难度0912排序数组网页链接、Github 链接数组、分治、桶排序、计数排序、基数排序、排序、堆优先队列、归并排序中等0220存在重复元素 III网页链接、Github 链接数组、桶排序、有序集合、排序、滑动窗口困难0164最大间距网页链接、Github 链接数组、桶排序、基数排序、排序困难
基数排序题目
题号标题题解标签难度0164最大间距网页链接、Github 链接数组、桶排序、基数排序、排序困难0561数组拆分贪心、数组、计数排序、排序简单
其他排序题目
题号标题题解标签难度0217存在重复元素网页链接、Github 链接数组、哈希表、排序简单0136只出现一次的数字网页链接、Github 链接位运算、数组简单0056合并区间网页链接、Github 链接数组、排序中等0179最大数网页链接、Github 链接贪心、数组、字符串、排序中等0384打乱数组数组、数学、随机化中等剑指 Offer 45把数组排成最小的数网页链接、Github 链接贪心、字符串、排序中等