前言
` 最近打卡Leetcode学习了常见的排序算法,大致包括LowB三人组和NB三人组以及其他一些算法。
LowB三人组指的是冒泡排序、插入排序和选择排序,其时间复杂度为[ O(n^2) ],时间效率较低。
NB三人组指的是快速排序、归并排序和堆排序,其时间复杂度为[ O(n*log n) ],时间效率有所提高。其他的排序还有希尔排序、计数排序、桶排序及基数排序,其时间复杂度或多或少都有所降低,但也相应牺牲了一部分空间复杂度。
其次我刷了些Leetcode中排序算法对应的题,在这里做下记录。
一、把数组排成最小的数
思路:自定义排序方法,设x和y是数组中的两个元素,指定判断规则如下:若拼接字符串x+y>y+x,则x大于y,y应在排在x前面,反之则x应排在y前面,从而使拼接起来的数字尽可能的小。
按照这一思路设计代码如下:
import functools
class Solution:
def minNumber(self, nums: List[int]) -> str:
def cmp(a,b):
if a+b>b+a:
return 1#表示元素a应该排在b后面
elif a+b<b+a:
return -1#表示元素a应该排在b前面
else:
return 0
nums_s=list(map(str,nums))
nums_s.sort(key=functools.cmp_to_key(cmp))
return "".join(nums_s)
代码是解读:通过自定义比较函数 cmp 和利用 functools.cmp_to_key 函数,实现对给定整数列表 nums 中的数字进行排序,并返回最小的字符串形式。
具体来说,代码中定义了 cmp 函数,该函数传入两个参数 a 和 b,用于比较两个数字的拼接结果哪个更小。如果 a+b 的拼接结果小于 b+a 的拼接结果,表示 a 应该排在 b 前面;如果 a+b 大于 b+a,则 a 应该排在 b 后面;如果两者相等,则说明它们的顺序无所谓。
原理是什么呢?这是由于 sort 函数的工作原理所决定的。
当我们给 sort 函数传递一个自定义的比较函数时,该函数应该满足以下规则:
如果比较结果为负数,表示第一个参数应该排在第二个参数之前。
如果比较结果为零,表示两个参数相等,顺序无所谓。
如果比较结果为正数,表示第一个参数应该排在第二个参数之后。
与上面一一对应。
然后,通过 map(str, nums) 将整数列表 nums 中的每个元素转换为字符串类型,并存储到新的列表 nums_s 中。
接下来,使用 nums_s.sort(key=functools.cmp_to_key(cmp)) 对 nums_s 进行排序。其中,key 参数指定了按照 cmp 函数的比较结果进行排序。
最后,通过 “”.join(nums_s) 将排序后的 nums_s 列表中的字符串元素连接成一个字符串,并将其作为结果进行返回。
二、移动零
思路:采用快慢双指针slow和fast,slow指向处理好的非0数组的尾部,fast指向当前待处理的元素。不断向右移动fast指针,直到遇到非零数后,将快慢指针的数交换,同时slow指针右移一位。此时slow指针左侧都为处理好的非零数,从slow指针指向位置到fast指针左边为止都为0.
遍历结束即可实现所有0都移到了右侧。
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
slow = 0
fast = 0
while fast < len(nums):
if nums[fast] != 0:
nums[slow], nums[fast] = nums[fast], nums[slow]
slow += 1
fast += 1
三、排序数组
思路1(不讲武德法哈哈哈):直接使用python内置排序函数直接搞定,但为了练习,思路2之后尝试采用别的排序。
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
nums.sort()
return nums
考虑到冒泡、选择、插入排序时间复杂度过高,会超时,这里采用别的排序方法。
思路2(希尔排序):基本思想是将整个数组切按照一定的间隔取值划分为若干个子数组,每个子数组分别进行插入排序。然后逐渐缩小间隔(除以2)进行下一轮划分子数组和对子数组进行插入排序。直至最后一轮排序间隔为 1,对整个数组进行插入排序。
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 // 2
return nums
def sortArray(self, nums: [int]) -> [int]:
return self.shellSort(nums)
思路3(归并排序):采用经典的分治策略,先递归地将当前数组平均分成两半,然后将有序数组两两合并,最终合并成一个有序数组。
class Solution:
# 合并过程
def merge(self, left_nums: [int], right_nums: [int]):
nums = []
left_i, right_i = 0, 0
while 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 += 1
else:
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 nums
mid = 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)
四、相对名次
思路:希尔排序先对score进行排序,再将对应前三个位置上的元素替换成对应的字符串:“Gold Medal”, “Silver Medal”, “Bronze Medal”。
class Solution:
def shellSort(self, arr):
size = len(arr)
gap = size // 2
while gap > 0:
for i in range(gap, size):
temp = arr[i]
j = i
while j >= gap and arr[j - gap] < temp:
arr[j] = arr[j - gap]
j -= gap
arr[j] = temp
gap = gap // 2
return arr
def findRelativeRanks(self, score: List[int]) -> List[str]:
nums = score.copy()
nums = self.shellSort(nums)
score_map = dict()
for i in range(len(nums)):
score_map[nums[i]] = i + 1
res = []
for i in range(len(score)):
if score[i] == nums[0]:
res.append("Gold Medal")
elif score[i] == nums[1]:
res.append("Silver Medal")
elif score[i] == nums[2]:
res.append("Bronze Medal")
else:
res.append(str(score_map[score[i]]))
return res
五、合并两个有序数组
思路:快慢双指针法,将两个指针index1和index2分别指向nums1和nums2数组的尾部,再用一个指针index指向nums1的尾部。从后向前判断当前指针下nums1[index1]以及nums2[index2]的值大小。将较大值存入nums1[index]中,然后继续向前遍历。最后再将nums2中剩余元素赋值到num1对应位置上。
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
index1 = m - 1
index2 = n - 1
index = m + n - 1
while index1 >= 0 and index2 >= 0:
if nums1[index1] < nums2[index2]:
nums1[index] = nums2[index2]
index2 -= 1
else:
nums1[index] = nums1[index1]
index1 -= 1
index -= 1
nums1[:index2+1] = nums2[:index2+1]
六、数组中的逆序对
思路:归并排序,在归并过程中,使用左右指针分别指向左右两个有序子序列的开始位置。若左指针元素小于等于右指针元素,则将左指针元素存入,将左指针右移,否则记录当前左子序列中元素与当前右子序列元素所形成的逆序对的个数,为左序列长度减去左指针索引。其余和归并排序过程一样。
class Solution:
cnt = 0
def merge(self, left_arr, right_arr): # 归并过程
arr = []
left_i, right_i = 0, 0
while left_i < len(left_arr) and right_i < len(right_arr):
# 将两个有序子序列中较小元素依次插入到结果数组中
if left_arr[left_i] <= right_arr[right_i]:
arr.append(left_arr[left_i])
left_i += 1
else:
self.cnt += len(left_arr) - left_i
arr.append(right_arr[right_i])
right_i += 1
while left_i < len(left_arr):
# 如果左子序列有剩余元素,则将其插入到结果数组中
arr.append(left_arr[left_i])
left_i += 1
while right_i < len(right_arr):
# 如果右子序列有剩余元素,则将其插入到结果数组中
arr.append(right_arr[right_i])
right_i += 1
return arr # 返回排好序的结果数组
def mergeSort(self, arr): # 分割过程
if len(arr) <= 1: # 数组元素个数小于等于 1 时,直接返回原数组
return arr
mid = len(arr) // 2 # 将数组从中间位置分为左右两个数组。
left_arr = self.mergeSort(arr[0: mid]) # 递归将左子序列进行分割和排序
right_arr = self.mergeSort(arr[mid:]) # 递归将右子序列进行分割和排序
return self.merge(left_arr, right_arr) # 把当前序列组中有序子序列逐层向上,进行两两合并。
def reversePairs(self, nums: List[int]) -> int:
self.cnt = 0
self.mergeSort(nums)
return self.cnt
七、颜色分类
思路1:我们可以统计出数组中 0,1,2的个数,再根据它们的数量,重写整个数组。
import collections
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# dict=collections.Counter(nums)
# for i in range(dict[0]):
# nums[i]=0
# for j in range(dict[1]):
# nums[dict[0]+j]=1
# for k in range(dict[2]):
# nums[dict[0]+dict[1]+k]=2
# return nums
思路2:双指针+快速排序,将1作为快速排序的基准数,将序列分为0、1、2三部分。设置左右指针分别指向数组的头尾。再用一个下标index遍历数组,若nums[index]==0,则交换nums[index]和nums[left],同时将left右移。若nums[index]==2,则交换nums[index]和nums[right],同时将right左移。
注意:移动的时候需要判断index和 left的位置,因为 left左侧是已经处理好的数组,所以需要判断index 的位置是否小于left,小于的话,需要更新 index位置。
class Solution:
def sortColors(self, nums: List[int]) -> None:
left = 0
right = len(nums) - 1
index = 0
while index <= right:
if index < left:
index += 1
elif nums[index] == 0:
nums[index], nums[left] = nums[left], nums[index]
left += 1
elif nums[index] == 2:
nums[index], nums[right] = nums[right], nums[index]
right -= 1
else:
index += 1