排序类题目总结归纳

本文介绍了多种算法,包括合并有序数组、寻找多数元素、丢失的数字、两数组交集、第三大的数、三数之和、最接近的三数之和、排序链表、颜色分类、链表插入排序、数据流中位数和滑动窗口中位数。这些算法涉及排序、查找、集合操作和数据流处理,主要时间复杂度为O(nlogn)和O(n),空间复杂度通常为O(1)或O(n)。
摘要由CSDN通过智能技术生成
    1. 合并两个有序数组
      归并排序。num1空间足够,就地进行,倒序进行遍历。
      i、j分别指向num1、num2当前扫描位置。k指向当前已排序位置。
      num1[k]为num1[i]与num2[j]的较大者。
      时间复杂度:O(n)
      空间复杂度:O(1)
    1. 多数元素
      投票算法,寻找众数
      一遍扫描。
      count为0,candidate更新为当前数字num。
      如果num等于candidate,count++;否则,count–
      扫描完毕,candidate就是众数。
      时间复杂度:O(n)
      空间复杂度:O(1)
      算法理解:
      1.如果candidate不是众数,反对者比支持多,count最终不会大于0.
      2.如果candidate是众数,支持者比反对者多,count最终会大于0
    1. 丢失的数字
      自然数列1~n求和公式:n*(n+1)/2
    1. 两个数组的交集
  • 技巧:较小的集合作为key,在较大的集合里查找,减少查找次数。

  • 实现技巧:一次递归调用

    vector<int> GetIntersection(nums1_set, nums2_set)
    {  
        if(nums1_set.size() > nums2_set.size())
        {
            return GetIntersection(nums2_set, nums1_set);
        }
        else
        {
            ...
        }
   }
    1. 第三大的数
      利用stl::set的排序性质,维护一个包含前三大数的set,一遍扫描,迭代更新set。
      时间复杂度:O(n)
      空间复杂度:O(1)
    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的时候,跳过重复数字,些许优化。

    1. 最接近的三数之和
      跟上一题三数之和是相似题.大思路是一样的,先排序.然后用双指针.第二,三个数的下标从两边向中间移动.整体时间复杂度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的差值是不是更新了当期差值最小的记录.这个不耗时.
    1. 排序链表
      分治策略,小段排序后的链表,合并为更大的有序链表。一直合并。用递归实现。
      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);
}

试试非递归实现。

    1. 颜色分类
      没啥好说的,计数排序。
      时间复杂度:O(n)
      空间复杂度:O(1)
    1. 对链表进行插入排序
      题目规定了用插入排序,时间复杂度确定O(n^2).
      老规矩,dummy头节点,储存已排序节点。每次第一个未排序节点p,在dummy链表遍历查找,找到插入位置。
    1. 数据流的中位数
      中位数,首先想到双堆法。比中位数小的放入small,它是一个大顶堆;比中位数大的放入big,它是一个小顶堆。两个堆要维持平衡,要么个数一致,要么差一个。如果差两个,多的要挪堆顶到少的。
      实现方法:c++使用优先队列priority_queue,它是用堆实现的。
      时间复杂度:O(nlogn)。数据流n,每次调整堆logn。
      空间复杂度:O(n)
    1. 滑动窗口中位数
      上一题 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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值