本系列为Leetcode刷题笔记,刷题平台为Leetcode中文站, 刷题按Tag
分类。本系列题解汇总如下 (持续更新…):
本文主要是排序
相关题目题解总结。
[TOC]
排序
快速排序
用于求解 Kth 问题,使用快速排序的partition()进行实现,需要首先打乱数组,否则最坏情况下时间复杂度为O(N^2)。
堆排序
用于求解 TopK 问题,通过维护一个大小为K的堆,堆中的元素就是TopK elements;
- 堆排序也可以用于求解 Kth 问题,堆顶元素就是 Kth elements;
- 快速选择也可以求解 TopK Elements 问题,因为找到 Kth Element 之后,再遍历一次数组,所有小于等于 Kth Element 的元素都是 TopK Elements;
因此快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题。
桶排序
使用空间换时间,首先统计出数组中元素的频次。接着,将数组中的元素按照出现频次进行分组,即出现频次为 i 的元素存放在第 i 个桶。最后,从桶中逆序取出前 k 个元素;
桶排序使用Hashmap散列表。
56. 合并区间
题目描述
给出一个区间的集合,请合并所有重叠的区间。
- 示例 1:
输入: [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. - 示例 2:
输入: [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。
解题思路
- 将区间按起始元素排序,新建返回res数组,遍历所有区间;
若res为空或res中最后一个区间的结束元素小于当前区间的起始元素,则无需合并,直接将该区间添加到res中;
若res中最后一个区间的结束元素大于当前区间的起始元素,则需要合并,将res最后区间的结束元素置为原来的值与当前区间结束值的最大值。
1 | # Definition for an interval. |
57. 插入区间
题目描述
给出一个无重叠的 ,按照区间起始端点排序的区间列表。
在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间)。
示例 1:1
2输入: intervals = [[1,3],[6,9]], newInterval = [2,5]
输出: [[1,5],[6,9]]
示例 2:1
2
3输入: intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
输出: [[1,2],[3,10],[12,16]]
解释: 这是因为新的区间 [4,8] 与 [3,5],[6,7],[8,10] 重叠。
解题思路
1 | class Solution(object): |
1 | class Solution(object): |
75. 颜色分类
题目描述
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
- 注意:
不能使用代码库中的排序函数来解决这道题。
- 示例:
输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2] - 进阶:
一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。
你能想出一个仅使用常数空间的一趟扫描算法吗?
解题思路
- 设置两个头尾指针,头指针p0指向的位置是0该放置的位置,尾指针p2指向的位置是2该放置的位置。
- i用来遍历整个数组,碰到0把它和p0指向的数交换,碰到2把它和p2指向的数交换,碰到1继续向后遍历。
- 有点类似快速排序的分割数组这一步。
1 | class Solution: |
147. 对链表进行插入排序
题目描述
对链表进行插入排序。
插入排序的动画演示如上。从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。
每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。
- 插入排序算法:
插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
重复直到所有输入数据插入完为止。 - 示例 1:
输入: 4->2->1->3
输出: 1->2->3->4 - 示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5
解题思路
插入排序:插入排序的实现上,通常采用 in-place 排序(即只需用到O(1)的额外空间的排序),用指针 head 逐一向后遍历
- 申请一个 dummyHead 节点,其下一个节点指向头结点。如果要在头结点出插入,dummyHead 会给我们带来便利;
- 当 head 的值不大于下一节点值,就进行遍历下一节点;
- 当 head 的值大于下一节点,那么就将 head 的下一节点取出,从前向后扫描,在第一个比它的值大的节点之前插入该节点。
1 | # Definition for singly-linked list. |
1 | # Definition for singly-linked list. |
148. 排序链表
题目描述
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
- 示例 1:
输入: 4->2->1->3
输出: 1->2->3->4 - 示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5
解题思路
使用归并排序,使用快慢指针找到中间结点后进行递归。
1 | # Definition for singly-linked list. |
179. 最大数
题目描述
给定一组非负整数,重新排列它们的顺序使之组成一个最大的整数。
- 示例 1:
输入: [10,2]
输出: 210 - 示例 2:
输入: [3,30,34,5,9]
输出: 9534330 - 说明:
输出结果可能非常大,所以你需要返回一个字符串而不是整数。
解题思路
两层循环遍历,每一个数和之后的每一个数进行比较,交换大的数放在前面;
两个数谁应该放在前面:拼接两个字符串进行比较 a+b>b+a,则a在前面。
1 | class Solution(object): |
215. 数组中的第K个最大元素
题目描述
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
- 示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5 - 示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4 - 说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
解题思路
思路1:利用堆排序
- 堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法
堆积是一个近似完全二叉树的结构,并同时满足堆积的性质;
即子结点的键值或索引总是小于(或者大于)它的父节点。 - 通常堆是通过一维数组来实现的。在起始数组为 0 的情形中
父节点i的左子节点在位置 (2i+1);
父节点i的右子节点在位置 (2i+2);
子节点i的父节点在位置 floor((i-1)/2)。 - 堆的操作:在堆的数据结构中,堆中的最大值总是位于根节点。堆中定义以下几种操作
最大堆调整(Min_Heapify):将堆的末端子结点作调整,使得子结点永远小于父结点;
创建最大堆(Build_Min_Heap):将堆所有数据重新排序;
注:堆排序不是一种稳定排序。 - 用小根堆得办法寻找最大的K个数
用容量为K的最小堆来存储最大的K个数。最小堆的堆顶元素就是最大K个数中的最小的一个;
每次扫描一个数据X,如果X比堆顶元素Y小,则不需要改变原来的堆。如果X比堆顶元素大;
那么用X替换堆顶元素Y,在替换之后,X可能破坏了最小堆的结构,需要调整堆来维持堆的性质;
调整过程时间复杂度为O(logK)。 全部的时间复杂度为O(N*logK);
这种方法当数据量比较大的时候,比较方便。因为对所有的数据只会遍历一次。
堆排序1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40class Solution(object):
def findKthLargest(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
if len(nums) < k or k <= 0:
return 0
res = nums[:k]
change = True
for i in range(k,len(nums)+1):
if change:
for j in range(k//2,-1,-1):
self.adjust(res,j,k)
for j in range(k-1,0,-1):
res[j],res[0] = res[0],res[j]
self.adjust(res,0,j)
change = False
if i != len(nums) and nums[i] > res[k-1]:
res[k-1] = nums[i]
change = True
return res[k-1]
def adjust(self,res,parent,length):
temp = res[parent]
child = 2*parent+1
while child < length:
if child+1 < length and res[child+1]<res[child]:
child = child+1
if temp < res[child]:
break
res[parent] = res[child]
parent = child
child = 2*parent+1
res[parent] = temp
if __name__ == '__main__':
result = Solution().findKthLargest([3,2,3,1,2,4,5,5,6],4)
print(result)
思路2:利用快速排序
- 快速排序的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分:分割左边都是比它小的数,右边都是比它大的数。
- 然后在按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,因此达到整个数据变成有序序列。
- 举例:nums = [3,2,1,5,6,4]
- 首先以nums[0]=3为基准点,设置指针left指向第一个元素(0),right指向最后一个元素(len(nums)-1);
- 从右至左偏移right指针,寻找到第一个比基准点3小的元素,将该元素(这里为1)赋给left指针所指的位置,此时数组为[1,2,1,5,6,4];
- 从左至右偏移left指针,寻找到第一个比基准点3大的元素,将该元素赋给right指针所指的位置,此时数组
- 不断循环步骤一二,知道left和right重合,将基准点3赋给重合位置,一轮排序结束数组为[1,2,3,5,6,4];
- 经过递归过程,最后排序结束。
**快速排序1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32class Solution(object):
def findKthLargest(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
if len(nums) < k or k <= 0:
return 0
self.Qsort(nums, 0, len(nums)-1)
return nums
def Qsort(self, nums, left, right):
if left < right:
index = self.division(nums, left, right)
self.Qsort(nums, left, index-1)
self.Qsort(nums, index+1, right)
def division(self, nums, left, right):
base = nums[left]
while left < right:
while right > left and nums[right] >= base:
right -= 1
nums[left] = nums[right]
while left < right and nums[left] <= base:
left += 1
nums[right] = nums[left]
nums[left] = base
return left
思路3:归并排序1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40class Solution(object):
def findKthLargest(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
if len(nums) < k or k <= 0:
return 0
res = self.dfs(nums)
return res[-k]
def dfs(self, nums):
if len(nums) <= 1:
return nums
mid = len(nums)//2
left = self.dfs(nums[:mid])
right = self.dfs(nums[mid:])
return self.merge(left, right)
def merge(self, left, right):
res = []
i, j = 0, 0
while i < len(left) and j < len(right):
if left[i] < right[j]:
res.append(left[i])
i += 1
else:
res.append(right[j])
j += 1
if i < len(left):
res += left[i:]
else:
res += right[j:]
return res
347. 前K个高频元素
题目描述
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
- 示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2] - 示例 2:
输入: nums = [1], k = 1
输出: [1] - 说明:
你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
解题思路
使用桶排序算法
- 首先使用散列表统计数组中元素出现的频次;
- 接着新建len(nums)+1个桶,用于保存出现次数对应的元素;
- 最后,从逆序桶中取出前k个元素。
1 | class Solution(object): |
451. 根据字符出现频率排序
题目描述
给定一个字符串,请将字符串里的字符按照出现的频率降序排列。
- 示例 1:
输入:”tree”
输出: “eert”
解释: ‘e’出现两次,’r’和’t’都只出现一次。因此’e’必须出现在’r’和’t’之前。此外,”eetr”也是一个有效的答案。 - 示例 2:
输入:”cccaaa”
输出:”cccaaa”
解释:’c’和’a’都出现三次。此外,”aaaccc”也是有效的答案。注意”cacaca”是不正确的,因为相同的字母必须放在一起。 - 示例 3:
输入:”Aabb”
输出:”bbAa”
解释:此外,”bbaA”也是一个有效的答案,但”Aabb”是不正确的。注意’A’和’a’被认为是两种不同的字符。
解题思路
桶排序
1 | class Solution(object): |