B站左神算法课学习笔记(P6):链表

排序算法的稳定性

定义同样值的个体之间,如果不会因为排序而改变相对次序,则认为这个排序是稳定的。

:现在对下面的数组继续进行排序:

若排序之后,值为1的数的相对次序仍然不变(1、2),值为2的数的相对次序也不改变(a、b、c、d),值为n的也不改变,即可以认为该排序是稳定的。

针对基础类型数组的时候,稳定性用处不大;但是在非基础类型的数组中,稳定性影响大!

对于学生结构体(包含class、age两个属性),先按照年龄升序排序,再按照班级升序排序:

得到的结果是:班级内部的学生也是按年龄排序的!

原因:排序算法的稳定性能保留相对次序!

针对时间复杂度 O\left ( N^{2} \right ) 的排序

选择排序:不可 —— 交换过程破坏稳定性

反例:

冒泡排序:可 —— 要求:相等时两数不交换

例:

第一轮排序

注意,相等时不进行交换!

第二轮排序

等等。

插入排序:可 —— 要求:相等时两数不交换

例:

0 ~ 0有序,想要 0 ~ 1 有序:

仍然可以保持稳定性。

针对时间复杂度 O\left ( N*logN \right ) 的排序

归并排序:可, 要求:merge过程中,两数相等时先拷贝左侧的数

快速排序:做不到 —— partition过程破坏了稳定性!

例:假设以5作为划分值,运行后发现需要把3和6进行交换,此时就已经破坏了6的稳定性!

堆排序:做不到 —— 其根据完全二叉树二叉树进行运算,很轻易地破坏了稳定性!

        ---->        

计数排序和基础排序:能做到 —— 可以控制出桶的顺序!(思路上就与比较无关

排序算法大总结

排序方法 \ 评价指标时间复杂度空间复杂度稳定性
选择排序O\left ( N^2 \right )O\left ( 1 \right )×
冒泡排序O\left ( N^2 \right )O\left ( 1 \right )
插入排序O\left ( N^2 \right )O\left ( 1 \right )
归并排序O\left ( NlogN \right )O\left ( N \right )
快速排序(随机)O\left ( NlogN \right )O\left ( logN \right )×
堆排序O\left ( NlogN \right )O\left ( 1 \right )×

注意:(1)基于比较的排序,时间复杂度指标最低为O\left ( NlogN \right )

           (2)在时间复杂度 O\left ( NlogN \right ) 的情况下,空间复杂度不可能小于 O\left ( N \right ) 且稳定。

启示:根据需要进行取舍 —— 运行速度最快:快排(实际期望值最低);空间要求为O\left ( 1 \right ):堆排序;需要稳定性:归并排序,且必须一个 O\left ( N \right ) 的空间。

常见的坑:

  1. 归并排序的额外空间复杂度可以变成 O\left ( 1 \right ) ,代价是其不再稳定,且难以实现(内部缓存法),为何不用堆排序实现?【归并排序情况一】
  2. “原地归并排序”的帖子是垃圾:可以变成 O\left ( 1 \right ),但是时间复杂度变为O\left ( N^2 \right ),为何不采用插入排序?又简单又满足要求。【归并排序情况二】
  3. 快速排序可以做到稳定性,但是空间复杂度会变成 O\left ( N \right ) 水平(01 stable sort),为何不直接使用归并排序?
  4. 目前没有找到时间复杂度O\left ( NlogN \right )、外空间复杂度 O\left ( 1 \right )、又稳定的排序!
  5. 面试大坑题:对一个整型数组,要求排序后奇数在左偶数在右,且稳定,要求时间复杂度O\left ( NlogN \right )、空间复杂度O\left ( N \right ),问能否做到?
    :奇偶问题对应0-1标准,经典快排的partition也是0-1标准、是同一种调整策略,但是做不到稳定性。理论上您的要求在快排的基础上修改可以实现,但是这是一个论文级别的算法,我不会做,请面试官指教。

工程上对于排序的改进

(1)充分利用 O\left ( NlogN \right ) 和 O\left ( N^2 \right ) 排序各自的优势!   --->    “综合排序”
例如:在快速排序中,若样本量较小,eg:L > R - 60 时,直接使用插入排序。
即:取长补短,在大样本量的调度上,利用了快排的调度优势;在小样本量的时候,利用插入排序常数项小的优势(当 n 较小,NlogN 与 N^2 差别不大,此时其常数项对于复杂度影响更大)。

(2)稳定性方面:在语言自己封装的排序中,若发现传入基础类型,则使用快排;若发现传入非基础类型,考虑到可能需要利用稳定性的特质,会选择归并排序。

在 c 或 java 等的底层,其实使用了复杂的排序策略,以求达到排序效率的最大化。

哈希表

可理解成集合结构。

--HashSet(UnOrderedSet):只有 Key

--HashMap(UnOrderedMap):Key -> Value

增(put)删(remove)改(put)查(get)可认为时间复杂度为 O\left ( 1 \right ),但是常数时间比较大。

对于放入哈希表的内容,

若是基础类型,则按值传递(产生拷贝),内存占用即是所存东西的大小;

若是非基础类型,内部按引用传递,内存占用为这个东西占地址的大小。

(1)基础类型按值传递!如果 String 很长,则存储消耗很多空间!

(2)非基础类型按照引用传递,无论值如何,只保存地址,只要地址不同,则认为是不同的。

而其中的 Node 是自定义类型,按引用传递!

有序表

也可理解成集合结构,和哈希表的区别在于:有序表会把 Key 按照顺序组织起来。

--TreeSet(OrderedSet):只有 Key

--TressMap(OrderedMap):Key -> Value

相应地,有序表因为按 Key 组织,其也会提供对应的 api:

红黑树、AVL树、size-balance-tree 和跳表都属于有序表结构,只是底层实现不同。

对于放入有序表的内容,

若是基础类型,则按值传递(产生拷贝),内存占用即是所存东西的大小;

若是非基础类型,必须提供比较器,否则有序表不知道如何组织,内部按引用传递,内存占用为这个东西占地址的大小。

示例:

单链表和双链表

定义

如果在逆序链表题中涉及换头操作,形式应该类似:head = f (head);

链表题的解题方法论

(1)笔试:做出来即可,最多考虑时间复杂度

(2)面试:时间复杂度优先考虑,但是一定找到空间复杂度最省的方法!!

重要技巧:

1)额外数据结构记录(哈希表等)

2)快慢指针    ---->      一定要自己练习!

需要根据具体情况“定制,同时注意边界条件:

                             

例题1

不考虑空间复杂度,直接倒入栈。

但是应该使得额外空间复杂度达到 O(1),利用修改链表实现,修改后从两侧往中间走,一步步判断,直到不一样 / 某个指针走到空,注意返回时先还原链表:

**注意**,单链表题目准备的时候区分笔试和面试来准备。

核心code:

逆序后半部分链表:

两侧同时往中间比较:

恢复链表并返回:

这部分实在是太复杂了......我还是抽时间自己做一遍吧...


例题2

解:

code

把所有原链表按 value 划分成三个小链表:

合并操作(经过多次化简):

总结:链表题目难点——厘清边界条件的问题!!

若使用额外空间,则解法简单,使用哈希表(map)即可:

code


按要求不使用额外空间

重点:修改节点的存储位置为原节点的下一个,省去了哈希表!

code

下一步就只需要关心复制后的节点的 rand 指针怎么设置:

注意:此时原节点的 rand 位置的 next 指向复制节点的 rand 位置!(结合上方链表图理解)

最后分离出结果链表:使用 res 记录 head.next 即第一个复制节点,随后,如果原链表有下一个节点(next = cur.next.next 且 next != null), 则把新链表的 next 设置为 原节点的下一个位置(next.next)即对应的复制节点;否则返回 null 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值