LeetCode + 《剑指Offer II》刷题笔记。
排序
Interval
56 Merge Intervals
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
sort(intervals.begin(),intervals.end());
vector<vector<int>> res;
//avoid an edge case
res.push_back(intervals[0]);
for(int i=1;i<intervals.size();i++){
int start=intervals[i][0],end=intervals[i][1];
int lastEnd=res[res.size()-1][1];
if(start<=lastEnd){
//the reason of max: [1,5],[2,4]=[1,5]
res[res.size()-1][1]=max(lastEnd,end);
}else{
res.push_back(vector{start,end});
}
}
return res;
}
};
|
- 时间复杂度:O(nlog n),其中 n 为区间的数量
- 空间复杂度:O(log n),其中 n 为区间的数量。这里计算的是存储答案之外,使用的额外空间。O(logn) 即为排序所需要的空间复杂度。
注意1:
//avoid an edge case
res.push_back(intervals[0]);
注意2:
//the reason of max: [1,5],[2,4]=[1,5]
57 Insert Interval
You are given an array of non-overlapping intervals intervals
where intervals[i] = [starti, endi]
represent the start and the end of the ith
interval and intervals
is sorted in ascending order by starti
.
Notice:
non-overlapping intervals
intervals
is sorted in ascending order by starti
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class Solution {
public:
vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) {
vector<vector<int>> res;
int left=newInterval[0],right=newInterval[1];
for(int i=0;i<intervals.size();i++){
if(right<intervals[i][0]){
res.push_back({left,right});
for(int j=i;j<intervals.size();j++){
res.push_back(intervals[j]);
}
return res;
}else if(left>intervals[i][1]){
res.push_back(intervals[i]);
}else{
left=min(left,intervals[i][0]);
right=max(right,intervals[i][1]);
}
}
res.push_back({left,right});
return res;
}
};
|
计数排序
1122 Relative Sort Array
Notice:
1 <= arr1.length, arr2.length <= 1000
0 <= arr1[i], arr2[i] <= 1000
- All the elements of
arr2
are distinct.
- Each
arr2[i]
is in arr1
本题中元素的范围为 [0, 1000],这个范围不是很大,可以考虑不基于比较的排序,例如「计数排序」。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
vector<int> res;
vector<int> count(1001);
for(int i=0;i<arr1.size();i++){
count[arr1[i]]++;
}
for(int i=0;i<arr2.size();i++){
while(count[arr2[i]]--){
res.push_back(arr2[i]);
}
}
for(int i=0;i<1001;i++){
while((count[i]--)>0) res.push_back(i);
}
return res;
}
};
|
对空间复杂度进行一个小优化。实际上,不需要使用长度为 1001 的数组,而是可以找出数组 arr1中的最大值upper,使用长度为 upper+1 的数组即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
vector<int> res;
int upper=*max_element(arr1.begin(),arr1.end());
vector<int> frequency(upper+1);
for(int x:arr1){
frequency[x]++;
}
for(int x:arr2){
for(int i=0;i<frequency[x];i++){
res.push_back(x);
}
frequency[x]=0;
}
for(int x=0;x<=upper;x++){
for(int i=0;i<frequency[x];i++){
res.push_back(x);
}
}
return res;
}
};
|
- 时间复杂度:O(m+n+upper)
- 空间复杂度:O(upper),空间复杂度可以认为是O(1)
快速排序
215 Kth Largest Element in an Array
703 Kth Largest Element in a Stream中的数据位于一个数据流中,不能一次性地将所有数据全部读入内存。而本题不一样,数据都保存在一个数组中,所有操作都在内存中完成,因此有更快找出第k大的数字的算法。
在长度为n的排序数组中,第k大的数字的下标是n-k
。用快速排序的函数partition对数组分区,如果函数partition选取的中间值在分区之后的下标正好是n-k,分区后左边的值都比中间值小,右边的值都比中间值大,即使整个数组不是排序的,中间值也肯定是第k大的数字。
如果函数partition选取的中间值在分区之后的下标大于n-k,那么第k大的数字一定位于中间值的左侧,于是再对中间值左侧的子数组分区。类似地,如果函数partition选取的中间值在分区之后的下标小于n-k,那么第k大的数字一定位于中间值的右侧,于是再对中间值右侧的子数组分区。重复这个过程,直到函数partition的返回值正好是下标为n-k的位置。
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
40
41
42
|
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
srand(time(0));
// 第 1 大的数,下标是 n - 1;
// 第 2 大的数,下标是 n - 2;
// ...
// 第 k 大的数,下标是 n - k;
int target=nums.size()-k;
int left=0;
int right=nums.size()-1;
int index=random_partition(nums,left,right);
while(index!=target){
if(index<target){
left=index+1;
}else{
right=index-1;
}
index=random_partition(nums,left,right);
}
return nums[index];
}
int random_partition(vector<int>& nums,int left,int right){
int privot=rand()%(right-left+1)+left;
swap(nums[privot],nums[right]);
return partition(nums,left,right);
}
int partition(vector<int>& nums,int left,int right){
int small=left-1;
for(int j=left;j<right;j++){
if(nums[j]<nums[right]){
//注意每次swap之前都有small++
small++;
swap(nums[small],nums[j]);
}
}
//注意每次swap之前都有small++
small++;
swap(nums[small],nums[right]);
return small;
}
};
|
归并排序
148 Sort List
归并排序基于分治算法。最容易想到的实现方式是自顶向下的递归实现,考虑到递归调用的栈空间,自顶向下归并排序的空间复杂度是 O(logn)。如果要达到 O(1) 的空间复杂度,则需要使用自底向上的实现方式。
归并排序(递归法)
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
|
class Solution {
public:
ListNode* sortList(ListNode* head) {
if(head==nullptr || head->next==nullptr) return head;
ListNode* middle=middleNode(head);
//将middle->next置nullptr是为了split分开
ListNode* temp=middle->next;
middle->next=nullptr;
return mergeList(sortList(head),sortList(temp));
}
ListNode* middleNode(ListNode* head){
ListNode *slow=head,*fast=head;
//注意这里返回中间结点的第二个节点
while(fast->next && fast->next->next){
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
ListNode* mergeList(ListNode* front,ListNode* rear){
ListNode* dummyHead=new ListNode(0);
ListNode* node=dummyHead;
while(front && rear){
if(front->val < rear->val){
node->next=front;
front=front->next;
}else{
node->next=rear;
rear=rear->next;
}
node=node->next;
}
node->next=(front==nullptr?rear:front);
return dummyHead->next;
}
};
|
- 时间复杂度 O(nlogn)
- 空间复杂度 O(logn)。空间复杂度主要取决于递归调用的栈空间。
归并排序(从底至顶直接合并)
- 时间复杂度 O(nlogn)
- 空间复杂度 O(1)
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
|
class Solution {
public:
vector<int> temp;
int sum=0;
int reversePairs(vector<int>& nums) {
temp.resize(nums.size());
mergeSort(nums,0,nums.size()-1);
return sum;
}
void mergeSort(vector<int>& nums,int left,int right){
if(left>=right) return;
int mid=left+(right-left)/2;
mergeSort(nums,left,mid);
mergeSort(nums,mid+1,right);
merge(nums,left,mid,right);
}
void merge(vector<int>& nums,int left,int mid,int right){
for(int k=left;k<=right;k++){
temp[k]=nums[k];
}
int i=left,j=mid+1;
int index=left;
while(i<=mid || j<=right){
if(i>mid){
nums[index++]=temp[j++];
}else if(j>right){
nums[index++]=temp[i++];
}else if(temp[i]<=temp[j]){
nums[index++]=temp[i++];
}else{
nums[index++]=temp[j++];
sum+=(mid-i+1);
}
}
}
};
|
- 时间复杂度 O(nlogn)
- 空间复杂度 O(n)
堆排序
23 Merge k Sorted Lists
顺序合并
逐一合并两条链表
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
|
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
//注意这边res要初始化为nullptr
ListNode* res=nullptr;
for(ListNode* list:lists){
res=mergeTwoList(res,list);
}
return res;
}
ListNode* mergeTwoList(ListNode* a,ListNode* b){
if(!a || !b) return a?a:b;
ListNode* dummyHead=new ListNode(0);
ListNode* tail=dummyHead;
while(a && b){
if(a->val < b->val){
tail->next=a;
a=a->next;
}else{
tail->next=b;
b=b->next;
}
tail=tail->next;
}
tail->next=(a?a:b);
return dummyHead->next;
}
};
|
- 时间复杂度 O(k^2*n)
- 空间复杂度 O(1)
分治合并
考虑优化顺序合并
,用分治的方法进行合并。
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
|
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
return merge(lists,0,lists.size()-1);
}
ListNode* merge(vector<ListNode*>& lists,int left,int right){
if(left==right) return lists[left];
if(left>right) return nullptr;
int mid=left+(right-left)/2;
return mergeTwoList(merge(lists,left,mid),merge(lists,mid+1,right));
}
ListNode* mergeTwoList(ListNode* a,ListNode* b){
if(!a || !b) return a?a:b;
ListNode* dummyHead=new ListNode(0);
ListNode* tail=dummyHead;
while(a && b){
if(a->val < b->val){
tail->next=a;
a=a->next;
}else{
tail->next=b;
b=b->next;
}
tail=tail->next;
}
tail->next=(a?a:b);
return dummyHead->next;
}
};
|
- 时间复杂度 O(knlogk)
- 空间复杂度 O(logk) ,递归会用到 O(log k)的栈空间。
优先队列(最小堆)
用k个指针分别指向这k个链表的头节点,每次从这k个节点中选取值最小的节点。然后将指向值最小的节点的指针向后移动一步,再比较k个指针指向的节点并选取值最小的节点。重复这个过程,直到所有节点都被选取出来。
这种思路需要反复比较k个节点并选取值最小的节点。既可以每次都用一个for循环用 O(k) 的时间复杂度比较k个节点的值,也可以将k个节点放入一个最小堆中,位于堆顶的节点就是值最小的节点。每当选取某个值最小的节点之后,将它从堆中删除并将它的下一个节点添加到堆中。
从最小堆中得到位于堆顶的节点的时间复杂度是O(1),堆的删除和插入操作的时间复杂度是O(logk),因此使用最小堆比直观地用for循环的时间效率更高。
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
|
class Solution {
public:
struct myComparsion{
bool operator()(ListNode* a,ListNode* b){
return a->val>b->val;
}
};
ListNode* mergeKLists(vector<ListNode*>& lists) {
priority_queue<ListNode*,vector<ListNode*>,myComparsion> q;
ListNode* dummyHead=new ListNode(0);
ListNode* tail=dummyHead;
for(ListNode* list:lists){
//注意这里list的判空
if(list){
q.push(list);
}
}
while(!q.empty()){
ListNode* least=q.top();
q.pop();
tail->next=least;
tail=tail->next;
//注意这里least->next的判空
if(least->next){
q.push(least->next);
}
}
return dummyHead->next;
}
};
|
- 时间复杂度 O(knlogk)
- 空间复杂度 O(k) ,堆(优先队列)的大小为k。
Author
Anjana
LastMod
2022-07-01
License
原创文章,如需转载请注明作者和出处。谢谢!