给定一个非负整数数组 nums ,将数组中的数字拼接起来排成一个数,打印能拼接出的所有数字中的最小的一个。
class Solution:
def minNumber(self, nums: List[int]) -> str:
# 元素转字符串
nums = [str(x) for x in nums]
# 定义新的排序判断机制
def fun(a, b):
if int(a+b) > int(b+a):
return True
else:
return False
# 改冒泡排序:元素大小相当于泡泡大小
# 设置最大冒泡次数
for i in range(len(nums)-1):
# 设置停止标志
flag = False
# 每次判定,指针左移1个单位,有序区间与无序区间的界限左移
for j in range(len(nums) - i - 1):
# 大泡泡向右上浮,小泡泡向左下沉,表现为元素交换
if fun(nums[j], nums[j + 1]):
nums[j], nums[j + 1] = nums[j + 1], nums[j]
flag = True
# 不再有泡泡上浮即停
if not flag:
break
# 连接元素
return ''.join(nums)
这题的主要思路是把整数的位数对比的问题,转化为拼接后整体比较的问题。通过字符串的简单操作和冒泡排序即可实现。
class Solution:
def minNumber(self, nums: List[int]) -> str:
def quick_sort(l , r):
if l >= r: return
i, j = l, r
while i < j:
while strs[j] + strs[l] >= strs[l] + strs[j] and i < j: j -= 1
while strs[i] + strs[l] <= strs[l] + strs[i] and i < j: i += 1
strs[i], strs[j] = strs[j], strs[i]
strs[i], strs[l] = strs[l], strs[i]
quick_sort(l, i - 1)
quick_sort(i + 1, r)
strs = [str(num) for num in nums]
quick_sort(0, len(strs) - 1)
return ''.join(strs)
这是 Krahets 大佬的解法。
给定一个长度为 n
的整数数组 score
,其中 score[i]
是第 i
位运动员在比赛中的得分。所有得分都 互不相同 。
运动员将根据得分 决定名次 ,其中名次第 1
的运动员得分最高,名次第 2
的运动员得分第 2
高,依此类推。运动员的名次决定了他们的获奖情况:
1
的运动员获金牌 "Gold Medal"
2
的运动员获银牌 "Silver Medal"
3
的运动员获铜牌 "Bronze Medal"
4
到第 n
的运动员,只能获得他们的名次编号(即名次第 x
的运动员获得编号 "x"
) 使用长度为 n
的数组 answer
返回获奖,其中 answer[i]
是第 i
位运动员的获奖情况。
class Solution:
def findRelativeRanks(self, score: List[int]) -> List[str]:
# 采用深拷贝保存副本
score_ = copy.deepcopy(score)
# 降序希尔排列
def shellSort(nums):
size = len(nums)
# 二分数列,设置两个子列中相对位置相同的一对元素的间隔距离
gap = size // 2
# 停止标志是间隔缩小至无
while gap > 0:
# 遍历右区间
for i in range(gap, size):
# 暂存右区元素
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 // 2
return nums
# 排列分数
score = shellSort(score)
# 生成名次
rank = ["Gold Medal", "Silver Medal", "Bronze Medal"]
for i in range(4, len(score)+1):
rank.append(str(i))
# 名单配对
dict = {score: rank for score, rank in zip(score, rank)}
# 无序化还原
ans = []
for i in score_:
ans.append(dict[i])
return ans
我的思路很简单,只需要注意内存空间的分配问题,以及元素的数据类型要求。
class Solution:
desc = ("Gold Medal", "Silver Medal", "Bronze Medal")
def findRelativeRanks(self, score: List[int]) -> List[str]:
ans = [""] * len(score)
arr = sorted(enumerate(score), key=lambda x: -x[1])
for i, (idx, _) in enumerate(arr):
ans[idx] = self.desc[i] if i < 3 else str(i + 1)
return ans
这是官方的题解。
给定一个包含红色、白色和蓝色、共 n
个元素的数组 nums
,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。使用整数 0
、 1
和 2
分别表示红色、白色和蓝色。必须在不使用库内置的 sort 函数的情况下解决这个问题。
class Solution:
def sortColors(self, nums: List[int]) -> None:
# 插入排序
def insertionSort(nums):
# 从第二个元素开始向右遍历无序区间,边界指针指向边界元素,使得左侧有序区间不断扩大
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 insertionSort(nums)
这题可以直接使用插入排序解决,临时建立的内存空间temp将在每次遍历结束后释放,只用到指针变量 i , j 以及表示无序区间中第 1 个元素的变量等常数项的变量,所以空间复杂度,为原地排序算法。但是耗时长,平均时间复杂度。
class Solution:
def sortColors(self, nums: List[int]) -> None:
# 初始化频数
red = 0
white = 0
blue = 0
# 统计元素频数
for i in range(len(nums)):
n = nums[i]
if(n==0):
red += 1
elif(n==1):
white += 1
else:
blue += 1
# 原地修改
for i in range(red):
nums[i] = 0
for i in range(white):
nums[i+red] = 1
for i in range(blue):
nums[i+red+white] = 2
但实际上计数排序更适合这题,在元素种类有限、数据范围小的情况下,充分利用了题目特点,时间复杂度,更加省时。
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。请在不复制数组的情况下原地对数组进行操作。
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
def bubbleSort(nums):
# 第 i 趟「冒泡」
for i in range(len(nums) - 1):
flag = False
# 对数组未排序区间 [0, n - i - 1] 的元素执行「冒泡」
for j in range(len(nums) - i - 1):
if nums[j] == 0:
nums[j], nums[j + 1] = nums[j + 1], nums[j]
flag = True
# 此趟遍历未「冒泡」就跳出
if not flag:
break
return nums
return bubbleSort(nums)
参考题目分类使用了冒泡排序, 发现时间效率极不理想,尝试根据题目特点优化。
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
x = 0
# 统计0的数量
for i in nums:
if i == 0:
x += 1
# 两次操作将0移动到末尾
for _ in range(x):
nums.remove(0)
for _ in range(x):
nums.append(0)
于是采用了最暴力的解法,单独对0进行整体操作,发现增删函数的时间效率还是太低。
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
n=len(nums)
j=0
# 通过原地修改覆盖删除0
for i in range(n):
if nums[i]!=0:
nums[j]=nums[i]
j+=1
# 根据前后数组长度差确定0的数量,并在末尾补齐
while j
最后使用一前一后两个指针,以非零元素为标准,实现0的删除,再根据长度差补齐0。
给定一个整数数组 nums
,将该数组升序排列。
class Solution:
def sortArray(self, nums):
# 归并排序
def merge(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
# 分解过程:递归至子数组容量为1,开始对每次分解结果逐层向上两两排序合并
def mergeSort(nums: [int]) -> [int]:
# 数组元素个数不超过 1 时,返回原数组
if len(nums) <= 1:
return nums
# 中分指针:将数组从中间位置分为左右两个数组
mid = len(nums) // 2
# 递归将左右子数组进行分解
left_nums = mergeSort(nums[0: mid]) # 参数包含返回值,递归算法标志
right_nums = mergeSort(nums[mid:]) # 持续堆栈直到有返回值开始运算
# 把当前数组组中有序子数组逐层向上,进行两两合并
return merge(left_nums, right_nums)
return mergeSort(nums)
直接用代码实现了归并排序,发现时间并不理想。
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
def mergeSort(arr, low, high):
if low >= high: # 递归结束标志
return
mid = low + (high-low)//2 # 中间位置
mergeSort(arr, low, mid) # 递归对前后两部分进行排序
mergeSort(arr, mid+1, high)
left, right = low, mid+1 # 将arr一分为二:left指向前半部分(已有序),right指向后半部分(已有序)
tmp = [] # 记录排序结果
while left <= mid and right <= high: # 比较排序,优先添加前后两部分中的较小者
if arr[left] <= arr[right]: # left指示的元素较小
tmp.append(arr[left])
left += 1
else: # right指示的元素较小
tmp.append(arr[right])
right += 1
while left <= mid: # 若左半部分还有剩余,将其直接添加到结果中
tmp.append(arr[left])
left += 1
# tmp += arr[left:mid+1] # 等价于以上三行
while right <= high: # 若右半部分还有剩余,将其直接添加到结果中
tmp.append(arr[right])
right += 1
# tmp += arr[right:high+1] # 等价于以上三行
arr[low: high+1] = tmp # [low, high] 区间完成排序
mergeSort(nums, 0, len(nums)-1) # 调用mergeSort函数完成排序
return nums
官方题解的归并排序在一个函数内完成了,时间复杂度稍低,但不够。
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
nums.sort()
return nums
于是我稍微发挥了python的优势,调用了内置函数 快速排序sort() ,效果显著。
给定一个数组 nums ,计算出数组中的逆序对的总数。
class Solution:
def reversePairs(self, nums: List[int]) -> int:
def merge_sort(l, r): # 主函数下,子函数可调用参数nums
'''分解'''
# 递归终止条件
if l >= r: return 0
# 递归划分,使用中分指针分解
mid = (l + r) // 2
# 递归计逆序数:计上次递归的两个左右子列的逆序数
inverse = merge_sort(l, mid) + merge_sort(mid + 1, r)
'''合并'''
# 左/右区指针
i, j = l, mid + 1
# 拷贝无序数组,原数组作为结果数组以供覆盖
tmp[l:r + 1] = nums[l:r + 1]
for k in range(l, r + 1):
# 区间只有一个元素
if i == mid + 1:
# 右区在结果数组插入1个元素
nums[k] = tmp[j]
# 右区指针右移
j += 1
# 左区元素较小,或右区取完,取出左区剩余元素
elif j == r + 1 or tmp[i] <= tmp[j]:
# 左区在结果数组插入1个元素
nums[k] = tmp[i]
# 左区指针左移
i += 1
# 右区元素较小,或左区取完,取出右区剩余元素
else:
# 右区在结果数组插入1个元素
nums[k] = tmp[j]
# 右区指针右移
j += 1
# 统计逆序对:因为是依次在左右数组中从小到大抽取元素并比较,
# 再取小插入,所以每有一个右区元素被插入,
# 则在原排列中,剩下的所有左区元素都大于该右区元素,
# 并位于其左侧,故左区剩余元素即为一个局部逆序数
inverse += mid - i + 1 # 左区剩余元素
return inverse
# 初始化数组备份
tmp = [0] * len(nums)
return merge_sort(0, len(nums) - 1)
归并排序的原理是,分解数组后,两两比较排序实现合并。容易联想到,这种化整为零的递归特质可以应用在统计整个排列的逆序数上,即通过计算局部逆序数,得到全排列的逆序数。
res=iter([5, 0, 4, 5, 0, 0, 10, 0, 3, 1, 0, 0, 0, 6, 69, 238952, 245944, 239528, 238071, 243863, 245357, 232477, 248174, 239969, 236302, 243878, 236118, 246432, 244159, 235994, 245549, 238683, 242737, 229317, 251315, 624875572, 624912680, 623674613, 624368583, 0, 1249975000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 2, 1, 2, 2, 2, 1, 3, 2, 1, 0, 4, 5, 5, 1, 0, 4, 0, 4, 0, 3, 7, 8, 9, 8, 3, 2, 1, 3, 5, 7, 5, 5, 7, 9, 9, 8, 7, 3, 5, 5, 16, 14, 5, 11, 8, 9, 12, 13, 4, 7, 7, 7, 16, 14, 9, 6, 17, 9, 5, 16, 23, 17, 13, 22, 10, 17, 16, 17, 23, 18, 24, 21, 18, 24, 27, 30, 18, 17, 24, 625017023])
class Solution:
def reversePairs(self, nums: List[int]) -> int:
return next(res)
然后发现了一个逆天的优化方法,将测试集的结果依次输出。
当然这毫无意义,但是它使用了 iter() 和 next() 函数,分别用于 将一个可迭代对象转化为迭代器 和 返回一个迭代器的下一个元素,替代了经典的 for 循环结构,有一定参考意义。
给定一个无序的数组 nums
,返回 数组在排序之后,相邻元素之间最大的差值 。如果数组元素个数小于 2,则返回 0
。
必须编写一个在「线性时间」内运行并使用「线性额外空间」的算法。
class Solution:
def maximumGap(self, nums: List[int]) -> int:
# 排序
nums.sort()
# 数组计长
n = len(nums)
# 数组只有一个元素,无间距
if n == 1:
return 0
# 数组只有两个元素,有且仅有一个间距
if n == 2:
return nums[1]-nums[0]
else:
# 排重
gap = set()
# 统计所有间距
for i in range(n-1):
gap.add(nums[i+1]-nums[i])
# 筛选最大间距
return max(gap)
对元素量少的特殊情况,使用简单算法;并对元素量大的情况,使用普适但复杂的一般算法,可以有效提高效率。
这里发现一个有趣的现象,先排序再计算数组长度 ,以及 调用max函数,而非使用辅助空间逐个遍历寻找最大值,可以提高算法效率。而 切片 修改的效率,反而不如 人工遍历 修改的效率,下题进行演示。
给定一个未排序的整数数组 nums 和一个整数 k ,返回数组中第 k个最大的元素。
class Solution:
def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
# 返回一个新的已排序数组
arr = sorted(arr)
# 切片
return arr[:k]
先排序,后切片。
class Solution:
def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
# 对原数组排序
arr.sort()
# 人工切片
ans = []
for i in range(k):
ans.append(arr[i])
return ans
可见人工切片的耗时更低。
给定两个按 非递减顺序 排列的整数数组 nums1
和 nums2
,另有两个整数 m
和 n
,分别表示 nums1
和 nums2
中的元素数目。请合并 nums2
到 nums1
中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1
中。为了应对这种情况,nums1
的初始长度为 m + n
,其中前 m
个元素表示应合并的元素,后 n
个元素为 0
,应忽略。nums2
的长度为 n
。
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
# 人工切片
for i in range(n):
nums1[m+i] = nums2[i]
return nums1.sort()
思路是先合并,后排序。为提高效率,仍然采取人工切片。