*内容来自leetcode
1.颜色分类
题目要求
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
进阶:
- 你能想出一个仅使用常数空间的一趟扫描算法吗?
思路
这道题实际上就是一个按大小排序的问题,在不考虑附加条件的情况下就很简单。
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
for i in range(n):
for j in range(n-1):
if nums[j] >= nums[j+1]:
temp = nums[j]
nums[j] = nums[j+1]
nums[j+1] = temp
这应该是最简单的排序实现,但是相应的时间花费也差不多是最多的。这道题作为中等题肯定不能满足于这种解法。考虑实现进阶提出的一趟扫描。可以用一次扫描来将原数组中0、1、2用于生成三个新的数组,最后再将三个数组组合起来即可。
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
nums1 = list()
nums2 = list()
nums3 = list()
for i in range(len(nums)):
if nums[i] == 0:
nums1.append(nums[i])
if nums[i] == 1:
nums2.append(nums[i])
if nums[i] == 2:
nums3.append(nums[i])
nums[:] = nums1 + nums2 + nums3
值得一提的是,由于排序是通过调用sortColors()这个函数来完成的,最后修改原数组时使用的是nums[:],如果使用nums,则原数组不会有任何变化。
#将原有列表中所有元素进行替换
nums[:] = nums1 + nums2 + nums3
#将nums指向新列表,并不会修改原有列表
nums = nums1 + nums2 + nums3
官方给出了三种解法,分别使用了单指针和双指针完成。三种思路都是基于交换完成的。
单指针
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
poi = 0
for i in range(len(nums)):
if nums[i] == 0:
nums[i],nums[poi] = nums[poi],nums[i]
poi += 1
for i in range(poi,len(nums)):
if nums[i] == 1:
nums[i],nums[poi] = nums[poi],nums[i]
poi += 1
两种双指针的区别在于具体识别那个数字,这里只放出识别0/1的代码
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
#双指针
#识别0/1
poi1 = 0
poi0 = 0
for i in range(len(nums)):
if nums[i] == 0:
nums[i],nums[poi0] = nums[poi0],nums[i]
if poi1 > poi0:
nums[i],nums[poi1] = nums[poi1],nums[i]
poi1 +=1
poi0 +=1
elif nums[i] == 1:
nums[i],nums[poi1] = nums[poi1],nums[i]
poi1 +=1
2.前k个高频元素
题目要求
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
思路
最开始的思路是将每个数出现的次数用一个列表记录下来,然后在对列表进行排序,依据计数返回对应的的数字。
这样考虑的问题在于忽略了nums中的数的大小是有可能比数组的大小更大的,就不能让记录数组的大小为nums数组的大小。如果非要使用这种方式,那就有可能需要非常大的数组来进行记录。还有一个问题就是数组中的数可能为负,也需要加以考虑。
在考虑到上述问题后,能够完成题目要求,但是性能并不理想。同时在我写这段的时候,发现可以用字典代替列表来进行记录,就不会用上面的问题了。先贴上记录数组实现的代码。
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
if n == 1:
return nums
#初始化记录数组,分为正负两个数组
maxNum = max(nums)
minNum = min(nums)
if n > maxNum:
countP = [0]*(n+1)
else:
countP = [0]*(maxNum+1)
if n > -minNum:
countN = [0]*(n+1)
else:
countN = [0]*(-minNum+1)
res = []
#进行记录
for i in range(n):
if nums[i] >= 0:
countP[nums[i]] +=1
else:
countN[-nums[i]] +=1
#对记录结果进行排序
sortCountP = countP[:]
sortCountP.sort(reverse=True)
sortCountN = countN[:]
sortCountN.sort(reverse=True)
#寻找出现频率最高的数字
poiP,poiN = 0,0
for i in range(k):
if sortCountP[poiP] > sortCountN[poiN]:
index = countP.index(sortCountP[poiP])
res.append(index)
countP[index] = -1
poiP +=1
else:
index = countN.index(sortCountN[poiN])
res.append(-index)
countN[index] = -1
poiN +=1
return res
用字典实现。实际看来只有内存消耗有所减少,执行时间反而变长了。。。
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
count = {}
res = []
for i in range(n):
#当键不存在会新添键进字典,默认值为1,导致计数值会比实际多一个
#由于比较是相对的,所以不影响结果
if count.setdefault(nums[i],1):
count[nums[i]] +=1
for i in range(k):
maxKey = 0
maxVal = 0
for key,value in count.items():
if value > maxVal:
maxVal = value
maxKey = key
res.append(maxKey)
count[maxKey] = -1
return res
官方给出的思路也是先进行计数,然后再找出前k个频率最高的数。不同之处在于对找频率最高的k个数这个过程的优化。
第一种方法是使用堆
在这里,我们可以利用堆的思想:建立一个小顶堆,然后遍历「出现次数数组」:
如果堆的元素个数小于 k,就可以直接插入堆中。
如果堆的元素个数等于 k,则检查堆顶与当前出现次数的大小。如果堆顶更大,说明至少有 k 个数字的出现次数比当前值大,故舍弃当前值;否则,就弹出堆顶,并将当前值插入堆中。
遍历完成后,堆中的元素就代表了「出现次数数组」中前 k 大的值。
由于之前没有出现过堆的使用,这里对堆进行一些补充。
堆是一种非连续的树形储存数据结构,每个节点有一个值,整棵树是经过排序的。堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。堆通常是一个可以被看做一棵完全二叉树的数组对象。堆有很多应用,例如在排序算法中使用,或者在优先队列中使用。请问还有什么其他问题吗?了解详细信息:
堆的基本存储 | 菜鸟教程
在python中,内置库heapq对堆进行了实现。默认为小根堆
heapq模块中的一些函数有:
- heappush(heap, item):将item压入堆heap中。
- heappop(heap):从堆heap中弹出最小的元素。
- heapify(x):将列表x转换成堆,原地,线性时间内。
- heapreplace(heap, item):弹出最小的元素,并将item压入堆中。
- nlargest(n, iterable, key=None):返回iterable中n个最大的元素,key是排序函数。
- nsmallest(n, iterable, key=None):返回iterable中n个最小的元素,key是排序函数。
补充完这些知识,回到这道题。这道题官方的解答中没有python的解答,光看C++实在是不知道怎么用python写出来。借助于new bing对C++的代码用python进行了复现。这里需要注意的一个问题是,在堆中的数据是以(x,y)的形式存在时,默认是以x来进行大小的判断的,而这道题中是要以计数的大小,也就是y来进行判断,所以在写入堆和判断时需要交换一下num和val的次序。应该是我在copy的时候没注意到这个问题,写错了,newbing生成的代码应该是没问题的。不过以此发现了一个问题也还是不错。写完这个再回去看我写的,有种原始的美感。
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
count = {}
res = []
#统计数字出现的次数
for v in nums:
count[v] = count.get(v, 0) + 1
q = []
for num, val in count.items():
if len(q) == k:
if q[0][0] < val:
heapq.heappop(q)
#注意这里是交换了val、num后再写入的
heapq.heappush(q, (val,num))
else:
heapq.heappush(q, (val,num))
while q:
res.append(heapq.heappop(q)[1])
return res
第二种算法是
使用基于快速排序的方法,求出「出现次数数组」的前 k 大的值。
在对数组 arr[l…r]做快速排序的过程中,我们首先将数组划分为两个部分 arr[i…q−1] 与 arr[q+1…j],并使得 arr[i…q−1]中的每一个值都不超过 arr[q],且 arr[q+1…j]中的每一个值都大于 arr[q]。
于是,我们根据 k 与左侧子数组 arr[i…q−1]的长度(为 q−i)的大小关系:
如果 k≤q−i,则数组 arr[l…r] 前 k 大的值,就等于子数组 arr[i…q−1]前 k 大的值。
否则,数组 arr[l…r]前 k大的值,就等于左侧子数组全部元素,加上右侧子数组 arr[q+1…j]中前 k−(q−i)大的值。
原版的快速排序算法的平均时间复杂度为 O(NlogN)。我们的算法中,每次只需在其中的一个分支递归即可,因此算法的平均时间复杂度降为 O(N)。
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
def quickSort(nums, left, right, k):
if left >= right: return
l, r = left, right
pivot = nums[left]
while l < r:
while l < r and nums[r][1] >= pivot[1]:
r -= 1
nums[l] = nums[r]
while l < r and nums[l][1] <= pivot[1]:
l += 1
nums[r] = nums[l]
nums[l] = pivot
mid = l
if mid + 1 == k:
return
elif mid + 1 < k:
quickSort(nums, mid + 1, right, k)
else:
quickSort(nums, left, mid - 1, k)
store = collections.Counter(nums)
s = list(store.items())
quickSort(s, 0, len(store) - 1, len(store) - k + 1)
res = []
for i in range(len(s)-1, len(s)-k-1, -1):
res.append(s[i][0])
return res
也可以通过调用内置函数完成
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
#基于快速排序实现
occurrences = collections.Counter(nums)
return [x[0] for x in occurrences.most_common(k)]
文章介绍了LeetCode上的两道算法题,一是颜色分类,通过不同的排序策略实现数组中红白蓝三色的排序;二是找出数组中出现频率前k的元素,讨论了不同计数和查找方法,包括使用堆和快速排序的优化策略。
86万+

被折叠的 条评论
为什么被折叠?



