一、数组排序
1.练习题目(第05天)
一个数字位置越靠前,它的大小对整个数字的的大小影响越大,所以,我们可以比较数组中每两个数字的和的大小来排序,按照和的大小的顺序排序。例如若数组中有两个数1、2,要对它俩排序,只用把它俩转为字符串,并拼接,结果为“12”和“21”,按照和的顺序排列,自然是1排2前边。
此题使用了冒泡排序。
个人题解:
class Solution:
def minNumber(self, nums: List[int]) -> str:
for i in range(len(nums)-1):
flag=False#是否发生交换的标志
for j in range(len(nums)-i-1):
num1 = nums[j]
num2 = nums[j+1]
digit1_num1 = str(num1)#将两个数字转为字符串
digit1_num2 = str(num2)
if digit1_num1+digit1_num2>digit1_num2+digit1_num1:
nums[j],nums[j+1]=nums[j+1],nums[j]#按字符串拼接结果较小的方式排序
flag=True
if not flag:#如果此次循环无交换则排序完毕
break
result=""
for i in nums:#将数组中的每个元素转换为字符串类型,使用了一个简单的生成器表达式(str(num) for num in nums)
完成
result="".join(str(num) for num in nums)#调用字符串的 join()
方法将它们连接成一个新的字符串,中间用无分隔。
return result
这道题我们可以用计数器count
用于记录遇到的零元素的个数,在遇到非零元素后将非零元素移动到当前位置减去零元素个数的位置,最后再将该非零元素位置上的值改为0,完成非零元素的前移:
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
count=0
for i in range(len(nums)):#遍历数组中的每个元素
if nums[i]==0:
count+=1#如果第i+1个元素为0,count+1
continue#直接退出循环
nums[i-count]=nums[i]#若第i+1个元素不为0,则将该位置的元素向移count位置,就是上一次0元素出现的前一个位置,这样就完成了非零元素的移动
if count!=0:
nums[i]=0#如果count不为零则表示该位置前有0,并且前面的操作已完成了该非零数字的迁移,可将原来该位置上的非零数字改为0,就完成了0的后移
乍一看这不就是一个简单的升序排列吗,这还不简单,直接选择排序:
class Solution:
def sortArray(self, nums: List[int]) -> List[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]=temp
return nums
结果:
大量的数据使得需要依靠循环遍历排序的选择排序法直接超时,我们怎么才能将降低我们的时间复杂度,使得代码能够处理这样的数据呢?
python快排(Quick Sort),充分体现python的强大,但缺点是空间复杂度较高,由于使用随机数,算法的性能也偏随机。
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
if len(nums) <= 1:
return nums
num = random.choice(nums)#从数组中选择一个随机元素作为基准值
left = self.sortArray([x for x in nums if x < num])# 将小于基准值的元素放入左子数组并递归排序
right = self.sortArray([x for x in nums if x > num])# 将大于基准值的元素放入右子数组并递归排序
mid = [x for x in nums if x == num]# 将等于基准值的元素放入中间数组
return left + mid + right# 返回拼接后的结果
python的快速排序通过选择一个基准值将数组分割为两个子数组,递归地对子数组进行排序,然后将结果拼接起来。
这个方法看起来很简单,还有更简单的方法吗?当然有:
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
return sorted(nums)
这样写虽然违背了该题的初衷, 但python的设计目的就在此,尽量简化我们的代码编写。
2.练习题目(第06天)
此题为简单题,具体思路:可以先将score中的分数降序排序,然后通过 sorted_score
列表的 index
方法来确定每个成绩的名次,根据名次来给每个成绩赋予相应的奖牌,或者转换为字符串形式,该代码中的排序除了sorted函数外,还可以使用冒泡排序、归并排序等,但论效率,还是sorted一行代码解决所有问题好用:
class Solution:
def findRelativeRanks(self, score: List[int]) -> List[str]:
result=[]
sorted_score = sorted(score, reverse=True)#将score中的分数降序排序
for i in range(len(score)):
rank = sorted_score.index(score[i]) + 1#通过 sorted_score
列表的 index
方法来确定每个成绩的名次
if rank == 1:#根据名次来给每个成绩赋予相应的奖牌
result.append("Gold Medal")
elif rank == 2:
result.append("Silver Medal")
elif rank == 3:
result.append("Bronze Medal")
else:
result.append(str(rank))#转换为字符串形式
return result
这道题的思路是先即将两个数组合并,具体合并方法是将nums2中的元素取代nums1中后n个元素,然后通过希尔排序或冒泡排序等排序方法将nums1中的m+n个元素全部进行排序,这里我使用的是希尔排序,希尔排序是一种插入排序的改进版本,它使用不同的步长进行分组插入排序,最终达到整体排序的效果:
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
for i in range(n):
nums1[m+i]=nums2[i]#将nums2中的元素取代nums1中后n个元素
size=m+n
gap=size//2
while gap>0:#通过希尔排序将nums1中的m+n个元素全部进行排序
for i in range(gap,size):#每次循环时,从 gap
开始,将 nums1
分成多个子序列
temp=nums1[i]
j=i
while j>=gap and nums1[j-gap]>temp:#内部的插入排序逻辑与常规的插入排序相似,通过比较和交换元素来实现
nums1[j]=nums1[j-gap]
j-=gap
nums1[j]=temp
gap=gap//2#通过一个循环控制步长 gap
的大小,在每轮循环结束后,将步长 gap
减半,继续下一轮的排序操作,直到 gap
的值为 0
这道题原本的思路很简单,只需要通过两层循环冒泡排序,每次计数加一就可以,但这是个困难题,那就说明按照暴力简单的算法是肯定超时的,思来想去,还是觉得使用递归算法可以尽量减少时间和空间上的复杂度:
class Solution:
def reversePairs(self, nums: List[int]) -> int:
n=len(nums)
num=0 #初始化索引记录left数组中的元素位置
if n<=1:
return 0 # 如果数组长度小于等于1,则不存在逆序对,直接返回0
mid=n//2 # 取数组的中间索引
left=nums[0:mid]
right=nums[mid:]
result=self.reversePairs(left)+self.reversePairs(right)
# 递归计算左右子数组中的逆序对数量,并相加得到结果 ,合并左右子数组并排序
left.sort() #通过调用 sort()
方法,可以对 left
进行升序排序
right.sort()
for i in range(len(right)):
while num<len(left) and left[num]<=right[i]:
# 找到左子数组中第一个大于右子数组当前元素的位置
num+=1
result+=len(left)-num # 添加逆序对数量,即找到左子数组中第一个大于右子数组当前元素后剩余元素均大于该右子数组元素
return result
3.练习题目(第07天)
该题使用简单的计数法即可解决,计算出数组中0,1,2的数量,然后按顺序给数组中的的元素一一赋值相应数量的元素即可:
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
nums1=0
nums2=0
nums3=0
n=len(nums)
for i in nums:
if i==0: #统计元素0的数量
nums1+=1
if i==1: #统计元素1的数量
nums2+=1
if i==2: #统计元素2的数量
nums3+=1
for i in range(nums1):
nums[i]=0 #给nums1数量的元素赋0
for i in range(nums1,nums1+nums2):
nums[i]=1 #给nums2数量的元素赋1
for i in range(nums1+nums2,n):
nums[i]=2 #给nums3数量的元素赋2
同样可以使用统计排序,统计数组中各个元素出现的次数,然后从结果数组中找所需数字:
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
# 计算待排序序列中最小值和最大值
nums_min, nums_max = min(nums), max(nums)
# 创建一个数组,用于统计每个数字出现的次数
freq = [0] * (nums_max - nums_min + 1)
# 统计每个数字出现的次数
for num in nums:
freq[num - nums_min] += 1
count = 0
# 从右侧开始遍历,找到第 K 大的元素
for i in range(len(freq) - 1, -1, -1):
count += freq[i]
if count >= k:
return i + nums_min
return -1 # 如果输入无效,返回 -1
该题可采用统计排序的方法:
class Solution:
def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
result = [] # 存储最小的 k 个数
arr_min, arr_max = min(arr), max(arr) # 计算数组中的最小值和最大值
freq = [0] * (arr_max - arr_min + 1) # 创建一个频率数组,用于统计每个数字出现的次数
for num in arr:
freq[num - arr_min] += 1
count = 0
# 从左侧开始遍历,找到第 k 小的元素
for i in range(len(freq)):
count += freq[i] # 累计出现的次数
if count <= k and freq[i] != 0: # 当前数字还未超过 k,并且至少出现了一次
for j in range(freq[i]):
result.append(i + arr_min) # 添加当前数字到结果列表
elif count > k and count - freq[i] < k: # 当前数字超过了 k,但是之前的数字总数没有超过 k
while len(result) < k: # 继续添加当前数字直到结果列表中的数字总数为 k
result.append(i + arr_min)
return result
return result # 返回结果列表
4.练习题目(第08天)
此题可以用简单的循环来遍历arr2中的元素,如果存在在arr1中,则添加到结果数组中,后用循环来遍历arr1中的元素,若存在在arr2中则添加的有一个需排序的数组中,将数组排序后拼接即可得到答案:
class Solution:
def relativeSortArray(self, arr1: List[int], arr2: List[int]) -> List[int]:
result = [] # 存储相对排序后的结果
arr = [] # 存储与arr2中元素匹配的arr1中的元素
# 将arr1中与arr2中元素相等的元素添加到arr中
for i in arr2:
for j in arr1:
if i == j:
arr.append(j)
# 将arr1中未在arr2中出现的元素添加到result中
for j in arr1:
if j not in arr2:
result.append(j)
result.sort() # 对result进行排序
return arr + result # 返回arr和result拼接后的列表作为最终的结果
此题条件很容易理解,但它却是个困难题,我们很容易猜到此题在时间复杂度上做了限制,不出意外简明的算法 :
class Solution:
def containsNearbyAlmostDuplicate(self, nums: List[int], indexDiff: int, valueDiff: int) -> bool:
for i in range(len(nums)):
for j in range(i+1,len(nums)):
if abs(i-j)<=indexDiff and abs(nums[i]-nums[j])<=valueDiff:
return True
return False
两层循环的运行结果超时:
那用什么办法可以降低时间复杂度呢 ,上述的内存循环可以稍微优化一下, 之前我们从 i + 1 到 len(nums),实际上我们只需要 i + 1 到 min(len(nums), i +indexDiff + 1)。这样我们的 j - i <= indexDiff 也可以省略了,但优化过的代码仍然超时,这说明我们需要换一种算法了,这里我们可以引入 sortedcontainers
的 SortedSet
数据结构来辅助解决问题,它具有元素唯一、插入、删除和查找操作的时间复杂度为 O(log n)、集合中的元素按升序排列的特点:
from sortedcontainers import SortedSet
class Solution:
def containsNearbyAlmostDuplicate(self, nums: List[int], indexDiff: int, valueDiff: int) -> bool:
st = SortedSet() # 使用SortedSet数据结构来存储元素
for i, num in enumerate(nums):#使用 enumerate() 函数来遍历列表 nums 中的元素,并在每次迭代中获取元素的索引和对应的值
num = nums[i]
index = st.bisect_left(num -valueDiff) # 获取元素num - valueDiff的插入位置
if index < len(st) and st[index] <= num + valueDiff:
return True # 如果在st中找到了满足条件的近似重复元素,则返回True
st.add(num) # 将当前元素num添加到st中
if len(st) > indexDiff:
st.remove(nums[i - indexDiff]) # 若st中元素的数量超过了indexDiff,则删除最旧的元素
return False # 没有找到满足条件的近似重复元素,返回False
要在线性时间和线性额外空间内找到排序后相邻元素之间的最大差值,可以使用桶排序的思想来解决:
class Solution:
def maximumGap(self, nums: List[int]) -> int:
if len(nums) < 2: # 若数组长度小于2,直接返回0
return 0
if len(nums) == 2: # 若数组长度等于2,返回两个元素之间的绝对差值
return abs(nums[0] - nums[1])
nums_min, nums_max = min(nums), max(nums) # 找到数组的最小值和最大值
if nums_min == nums_max: # 如果最小值和最大值相等,返回0
return 0
bucket_size = max(1, (nums_max - nums_min) // (len(nums) - 1)) # 计算桶的大小
buckets = [[] for _ in range((nums_max - nums_min) // bucket_size + 1)] # 初始化桶数组
for i in nums:
bucket_index = (i - nums_min) // bucket_size # 计算元素应该放入的桶索引
buckets[bucket_index].append(i) # 将元素放入对应的桶中
result = 0
max_ = float('inf') # 初始化前一个非空桶的最大值为正无穷大
for i in range(len(buckets)):
if buckets[i] and max_ != float('inf'): # 如果当前桶不为空且前一个非空桶的最大值不是正无穷大
result = max(result, min(buckets[i]) - max_) # 更新最大差值,将其保留为目前找到的最大差值
if buckets[i]: # 如果当前桶不为空
max_ = max(buckets[i]) # 更新前一个非空桶的最大值
return result
本文参考:
https://datawhalechina.github.io/leetcode-notes/#/ch01/01.03/01.03.01-Array-Bubble-Sort