-
- 合并两个有序数组
归并排序。num1空间足够,就地进行,倒序进行遍历。
i、j分别指向num1、num2当前扫描位置。k指向当前已排序位置。
num1[k]为num1[i]与num2[j]的较大者。
时间复杂度:O(n)
空间复杂度:O(1)
- 合并两个有序数组
-
- 多数元素
投票算法,寻找众数
一遍扫描。
count为0,candidate更新为当前数字num。
如果num等于candidate,count++;否则,count–
扫描完毕,candidate就是众数。
时间复杂度:O(n)
空间复杂度:O(1)
算法理解:
1.如果candidate不是众数,反对者比支持多,count最终不会大于0.
2.如果candidate是众数,支持者比反对者多,count最终会大于0
- 多数元素
-
- 丢失的数字
自然数列1~n求和公式:n*(n+1)/2
- 丢失的数字
-
- 两个数组的交集
-
技巧:较小的集合作为key,在较大的集合里查找,减少查找次数。
-
实现技巧:一次递归调用
vector<int> GetIntersection(nums1_set, nums2_set)
{
if(nums1_set.size() > nums2_set.size())
{
return GetIntersection(nums2_set, nums1_set);
}
else
{
...
}
}
-
- 第三大的数
利用stl::set的排序性质,维护一个包含前三大数的set,一遍扫描,迭代更新set。
时间复杂度:O(n)
空间复杂度:O(1)
- 第三大的数
-
- 三数之和
双指针法,指向第二个数与第三个数的指针,一左一右,分别向中间移动,时间复杂度O(n)。指向第一个数的指针普通地遍历,复杂度O(n)。因此,这部分的复杂度是O(n2)。在遍历之前,需要排序,时间复杂度是O(nlogn)。整体复杂度还是O(n2)
- 三数之和
双指针法的正确性分析:
数据已升序排序。设第一、二、三个数的下标分别是i、j、k,且满足i<j<k.
当a[i]+a[j]+a[k]<0时,k- -没有意义,和肯定也小于0。于是要j++,关键在于k不需要复位到最右边,而是从当前位置继续左移。因为a[i]+a[j]+a[k+1] > 0, ⇒ a[i]+a[j+1]+a[k+1] > 0。所以,j++后,从当前k位置继续左移就行了。
其他技巧:
循环遍历i,以及j的时候,跳过重复数字,些许优化。
-
- 最接近的三数之和
跟上一题三数之和是相似题.大思路是一样的,先排序.然后用双指针.第二,三个数的下标从两边向中间移动.整体时间复杂度O(n^2)
细节区别:
第二层循环的终止条件,是第一次出现三数之和小于target.原因是k继续左移会让总和与target差的更多,于是此时要j右移.考虑一下这时k的迭代方式.隐含条件是a[i]+a[j]+a[k]>target. 又有a[i]+a[j+1]+a[k] > a[i]+a[j]+a[k], 与target的差值更大了.因此,j右移的同时,k左移.
每次迭代,都可以比较一下总和与target的差值是不是更新了当期差值最小的记录.这个不耗时.
- 最接近的三数之和
-
- 排序链表
分治策略,小段排序后的链表,合并为更大的有序链表。一直合并。用递归实现。
split实现技巧:快慢指针法,找到链表中间位置,分割链表。
merge实现技巧:在新的dummy头节点之后,做归并排序。p1、p2,哪个较小就链接哪个进来。最后把dummy节点删了,返回它的next。
时间复杂度:O(nlogn)
空间复杂度:O(1)
- 排序链表
List* sortList(List* p)
{
if(nullptr == p)
return nullptr;
List* p1 = p;
List* p2 = split(p);
p = merge(p1, p2);
return sortList(p);
}
试试非递归实现。
-
- 颜色分类
没啥好说的,计数排序。
时间复杂度:O(n)
空间复杂度:O(1)
- 颜色分类
-
- 对链表进行插入排序
题目规定了用插入排序,时间复杂度确定O(n^2).
老规矩,dummy头节点,储存已排序节点。每次第一个未排序节点p,在dummy链表遍历查找,找到插入位置。
- 对链表进行插入排序
-
- 数据流的中位数
中位数,首先想到双堆法。比中位数小的放入small,它是一个大顶堆;比中位数大的放入big,它是一个小顶堆。两个堆要维持平衡,要么个数一致,要么差一个。如果差两个,多的要挪堆顶到少的。
实现方法:c++使用优先队列priority_queue,它是用堆实现的。
时间复杂度:O(nlogn)。数据流n,每次调整堆logn。
空间复杂度:O(n)
- 数据流的中位数
-
- 滑动窗口中位数
上一题 295. 数据流的中位数的相似题。区别在于,窗口每次滑动要删除元素,但priority_queue不支持删除非堆顶元素。
解法1:使用priority_queue大小双堆,窗口滑动时推迟删除,只是记录欠账。另外维护一个balance,表示双堆有效数字的个数平衡情况,balance大于1时,调整,调整方法同上一题。何时删除欠款?如果欠款者出现在堆顶,出堆,直到堆顶不是欠款者。
欠款记录实现:用map实现,key是num,value是欠款次数。窗口滑出num时,map[num]++。调整堆的过程中,堆顶map[top]大于0时,弹出,map[top]–。
正确性分析:
欠账未删除的数字,是否影响双堆计算中位数?
balance维持了双堆中有效数字的平衡,堆顶不会出现欠账数字,于是,堆顶元素一直保持有效数字对应的中间两个数字。而计算中位数只会使用这两个数字,保证了正确性。
解法2:multiset + 调整mid下标
multiset是排序的,窗口滑动时,新加入元素push并且排序,通过find或者lower_bound查找滑出元素位置,然后删除。每次中位数计算,移动到中间元素的迭代器位置,取出计算。
进阶:每次计算时,从begin移动迭代器到multiset中间元素比较耗时。维护mid迭代器,指向中间元素,每次滑动后,更新。
- 滑动窗口中位数
//为了避免mid越界死机,插入与删除的顺序不能变。没想明白。
window.push(滑入的元素)
if(滑入与滑出的元素都大于或者都小于当前mid数值)
mid不动;
else //滑入与滑出的,分居mid两侧
if(滑入的比mid小) //滑出的比mid大
mid--;
if(滑出的比mid小) //滑入的比mid大
mid++;
window.erase(window.find(滑出的元素))
-
最大的数
重载std::multiset的比较函数,使用multiset排序就行了。重载的比较函数,就是两个数字A、B凑在一块,比较AB大还是BA大。
时间复杂度:O(n*logn)
空间复杂度:O(n) -
最长连续序列
1.并查集
unorder_set去重,不需要排序。
parent是一个unorder_map,parent[i]表示数字i所属的上一级。
查:n的相邻数字n+1是否存在?如果存在,查n、n+1所属的集合。
并:当前n、n+1属于不同的集合,则将n+1合并到n所属的集合。
时间复杂度:集合的树形结构未优化时,平均为O(n*logn),最差为O(n^2)。扁平化优化后,为O(n)。
空间复杂度:O(n)
本题可以用并查集求解,但不是最优方法,只是作为并查集的练习。
2.双循环优化
unorder_set去重,不需要排序。
普通双循环,遍历每个数n,然后遍历n+1、n+2、…n+m是否存在。这样的复杂度是O(n^2)。
优化:每组连续数字的第一个数字n,才启动内层循环遍历n+1、n+2、n+m。避免了重复。
当n满足n-1不存在时,n是每组连续数字的第一个数字。
优化后的时间复杂度:O(n)。每个数字,只属于一组连续数字中,也就是只被查找一次。因此是O(n)
空间复杂度:O(n)