zoukankan      html  css  js  c++  java
  • 02 链表编程题

    第二章 链表问题

    2-1 打印2个升序链表的公共部分

    基本思路:

    ​ 首先注意到这是升序的链表,因此之间在2个链表上滑动指针进行数值比较,相等都前进,不等数值小的前进。直到其中一个到达表尾。

    2-2 在链表中删除倒数第K个结点

    基本思路:

    ​ 双指针,让一个指针先跑K步,第二个指针然后跟着跑,主要考察链表的操作,定义dummy node避免头节点的删除的讨论。

    2-3 反转单双向链表

    要求时间复杂度为O(N),空间复杂度为O(1)。

    基本思路:

    ​ 3指针法,逐个处理中间指针的指向关系即可,双链表有2个指针不要搞混了。

    建议:

    • 方便的话,可以在纸上对算法进行模拟,确保不出错。
    反转部分单向链表

    leetcode反转链表ii


    2-4 环形列表的约瑟夫环问题(剑指offer45)

    基本思路:

    模拟: 最直接的方法就是去模拟这个过程,假设有n个节点,报数为m则枪毙。需要删除n-1个节点,每次都需要移动M不,复杂度为O(mn)

    计算: 很容易想到,只要确定好m和n,最终的存活节点肯定的是固定的,我们需要推导出f(n,m) 给定N,M,计算出存活节点的索引。

    关键:找出节点数量为n与节点数量为n-1下的存活节点的索引关系,这样就能从节点数量为1的情况下,一路递推到节点数量为n。

    结论:f(n,m) = (f(n-1,m)+m)%n (见参考说明)

    • 推导方法1:找规律
    • 推导方法2:数学推导(剑指offer 面试题45有详细推导)

    2-5 判断一个链表是否有回文结构

    官方提供了3个方法(3个方法时间复杂度都是线性时间):

    方法1:

    • 采用栈来进行判断,遍历链表,将所有元素压入栈,然后依次出栈并遍历链表对比,比较相同则是回文结构,具有回文结构的链表顺序遍历与逆序遍历的结果是完全一样的

      • 空间复杂度O(N)

    方法2:

    • 思想:同样使用栈,遍历链表,但可以只将一半的元素压入栈,然后依次出栈并遍历链表对比,如果是回文结构,他们的顺序应该是一致的。

      • 空间复杂度:O(N/2)

    方法3:

    分为三个基本步骤

    • 思想
      • step1:将链表的后半部分逆序
      • step2:设置2个指针一头一尾,然后依次对比,判断是否是回文结构。
      • step3:将链表的后半部分逆序恢复。
    • 空间复杂度:O(1)

    2-6 将单向链表按某值划分为左边小,中间相等,右边大的形式

    要求:给定一个链表,再给定一个整数 pivot,实现一个调整链表的函数,请将链表调整为左部分都是值小于 pivot 的节点,中间部分都是值等于 pivot 的节点, 右边部分都是大于 pivot 的节点。除此之外,对调整后的节点顺序没有更多要求

    进阶要求:要求左中右三部分的节点的顺序与原链表一致,且额外空间复杂度为O(1)。

    基本要求的解决策略

    方法:借助一个辅助数组进行排序。

    step1: 遍历链表,将链表中的值存入到数组中。

    step2:采用快速排序中”治“的思想,调整好顺序。

    step3:将数组转换为新的链表返回。

    时间复杂度:O(n), 空间复杂度O(n)

    进阶要求的解决策略

    基本思想:通过有限的变量完成链表的重组

    step1 定义6个指针,分别指向

    • 小于pivot的节点的链表头尾
    • 等于pivot的节点的链表头尾
    • 大于pivot的节点的链表头尾

    step2 遍历原链表,并将step1中定义的3个链表中填入节点

    step3 将3个链表拼接在一起(考虑3个链表中会存在空链表)

    step4 返回最终链表的指针(由于空链表的存在,需要考虑返回3个头指针的哪一个)


    2-8 2个单链表生成相加链表

    解决方法

    注意点:2个链表的长度可能很长,需要考虑大整数的相加。

    方法1:借助额外的数据结构,设置2个栈或者数组,然后 依次出栈/逆序遍历 相加对应位,将这个数字插入到结果链表中。

    时间复杂度: O(n) 额外空间复杂度: O(n)

    方法2:先将2个链表逆序,然后遍历2个链表,相加对应位,存入到新的链表中。

    时间复杂度: O(n) 额外空间复杂度: O(1)

    2-9 单链表每K个节点逆序

    解决方法

    注意点:对K值进行讨论,K小于2时,不需要对链表进行调整。

    方法1: 借助栈这种数据结构,遍历链表,当节点个数达到K个,进行逆序,注意每次逆序部分的链表的新的头尾节点的处理。

    时间复杂度: O(n) 额外空间复杂度: O(n)

    方法2: 直接在链表上进行逆序。

    • 注意新生成链表头节点的记录(第一组逆序链表需要特殊处理)
    • 注意K个短链表头尾之间的连接c

    时间复杂度: O(n) 额外空间复杂度: O(1)


    2-10 删除单链表中指定的值

    基本思想

    step1: 找到第一个不等于指定值的节点作为头节点

    step2:遍历链表,删除等于指定值的节点。

    2-11 单链表的选择排序

    要求额外空间复杂度为:O(1)

    step1:找到链表中的最小值,指定为新链表的头节点newHead并从链表中删除。

    step2:多次遍历链表,每次找到链表的最小值,从链表中删除,并连接到排好序的链表。

    时间复杂度: O(n) 额外空间复杂度: O(1)

    2-12 一种怪异的链表删除方式

    链表节点值类型为 int 类型,给定一个链表中的节点 node,但不给定整个链表的头节点。如何在链表中删除 node ? 请实现这个函数。

    要求:时间复杂度为O(1)

    基本思路:将需要删除的节点的内容替换为下一个节点的内容,然后删除下一个节点。

    注意点:

    • 上面的方法无法删除最后一个节点
    • 上面的方法在实际工程应用,单个节点的修改可能会带来问题

    2-13 环形链表插入节点

    基本思路:

    • 初始链表为空,则插入节点自己成环
    • 初始链表不为空,则遍历链表,找到插入位置,是前面的节点小于等于插入值,后面的节点大于等于插入值。
      • 如果找不到这样的位置,说明插入的节点应该插在头节点与尾节点之间。然后判断插入的值是最大值,还是最小值,如果是最小值,需将该节点作为新的节点。

    注意点:头节点与尾节点需要特殊考虑。

    2-14 合并2个有序的单链表

    基本思路:

    • 有链表为空,无需合并
    • 都不为空,确定值更小的节点为头节点,然后同步遍历2个链表,那个链表的值更小,则从原位置删除,并插入到新的链表后头。

    2-15 合并左右半区的2个链表

    熟练链表的操作。

    补充

    1 删除无序单链表中重复出现的值

    方法1:借助Hash表,遍历过程中维护Hash表,从而在遍历的过程中判断当前节点是否已经出现。

    时间复杂度: O(n) 额外空间复杂度: O(n)

    方法2:遍历链表多次,每次删除一种值的节点,直到没有重复。

    时间复杂度: O(n^2) 额外空间复杂度: O(1)


    2 2个单链表相交的一系列问题

    题目描述:给定2个单链表的头节点,注意这2个单链表可以有环,实现一个函数判断这2个链表是否相交,要求:

    • 相交则返回相交的第一个节点
    • 不相交则返回null

    要求:时间复杂度O(M+N),空间复杂度为1。

    解决这个问题,需要解决下面3个子问题:

    • 问题1:设计一个函数判断单链表是否有环,有则返回第一个进入环的节点,没有则返回null.
    • 问题2:设计一个函数判断2个无环单链表是否相加,有则返回第一个相交的节点,没有则返回null.
    • 问题3:设计一个函数判断2个有环单链表是否相加,有则返回第一个相交的节点,没有则返回null.

    注意:如果一个链表有环,另外一个链表无环,它们不可能相交,参照相交与有环的定义可以理解。

    关于链表相交与有环的定义

    • 链表相交:从相交节点开始,后面的链表是2个链表共同拥有的。
    • 链表有环:这个链表没有终止节点,链表的尾节点指向尾节点之前的节点。

    问题1:设计一个函数判断单链表是否有环,有则返回第一个进入环的节点,没有则返回null.

    基本思想:采用快慢指针,定义fast和slow2个指针。

    step1: fast指针以2步遍历,slow指针以单步遍历,如果fast能够达到尾部,则返回null,或者等到fast与slow指针相遇。

    step2:当2个指针第一次相遇,保持slow指针位置不边,将fast指针指向表头。

    step3:此时以步长为1移动fast指针,当2个指针再次相遇的时候,就是第一个进入环的节点。

    证明见: 题解

    备注:力扣网还提供了一种采用hash表记录的方法,这种方法在链表中存在重复的节点是是否有效


    问题2:设计一个函数判断2个无环单链表是否相加,有则返回第一个相交的节点,没有则返回null.

    基本思想:先判断链表是否相交,然后再找公共节点

    相交判定: 设置2个指针分别遍历2个链表并统计2个链表的长度为len1和len2。如果2个指针最后指向同一个尾部节点,说明2个链表相交,否则不相交。

    公共节点寻找:

    • step1:为2个链表分别指定2个头指针,让长度比较长的链表先走|len2-len1|步。
    • step2:2个指针一起走,第一次相遇的节点就是相交的节点。

    问题3:设计一个函数判断2个有环单链表是否相加,有则返回第一个相交的节点,没有则返回null.

    基本思想:在问题1的基础上,我们能够得到2个链表的进环节点设为loop1与loop2。

    情况1:loop1与loop2是同一个节点(如下图所示)

    上面的情况,我们可以将进环节点变为null,那么问题就转化为问题2了。

    情况2:loop1与loop2不是是同一个节点,需要判断2个链表是否相交(下图中左右2种情况)。

    当loop1 != loop2,如何判断2个链表是否相交

    • 让指针从loop1出发,遍历链表,如果指针回到loop1之前碰到loop2,说明2个有环链表相交。
    • 当判定2个链表相加的时候,第一个相交的节点可以是loop1或loop2中的任意一个

    3 将搜索二叉树转化为双向链表

    题目描述:上面这颗搜索二叉树转换得到的链表是1~9,假定双向链表的每个节点的next指针指向下一个节点,prior指针指向前一个节点。那么对于搜索二叉树,其每个节点与双向节点存在以下对应关系:

    • left指针<-------->prior指针
    • right指针<------->next指针

    方法1:

    step1:使用中序遍历并采用队列/数组存储所有得到的节点。

    step2:然后将所有节点链接成双向链表。

    时间复杂度: O(n) 额外空间复杂度: O(n)

    方法2:

    基本思想:利用递归函数实现

    注意:

    • 递归函数最后返回的是转换后的链表的尾指针

    时间复杂度: O(n) 额外空间复杂度: O(h) h为二叉树的高度


    4 复制含有随机指针的单链表

    题目描述:假设一个特殊的单链表的节点,除了普通的next指针,还有一个随机的rand指针指向该链表中任意节点。要求复制这种链表。

    难点:需要跨节点进行连接,即rand指针的设置,必须在链表结构信息完整的情况下才可以设置。

    方法1

    step1:第一次遍历原始列表,使用hashmap,将复制的每个节点都放入到hash表。

    step2:第二次遍历原始列表,设置每个节点的next与rand指针。(由于hash表查找节点特别块,所以设置rand指针比较方便)

    时间复杂度: O(n) 额外空间复杂度 O(n)

    方法2:

    ​ 复制原始列表每一个节点并插入到原始节点的后面。然后先设置rand指针,最后设置next指针。

    • 这里设置rand指针利用了原始节点与新的链表节点的相对关系这一特性

    时间复杂度: O(n) 额外空间复杂度 O(1)


    参考资料

    白话数据结构之排序算法

    程序员代码面试指南


    20210301

  • 相关阅读:
    使用Python将excel文件中的数据提取到txt中
    多项式加法的链表实现
    hdu 5976 Detachment 逆元的应用
    JavaScript 全局函数
    JavaScript 事件句柄
    JavaScript 运算符
    纯Javascript 实现的日历 ,在IE所有版本浏览器上测试通过,火狐、谷歌、360、QQ等浏览器均兼容,理论上均兼容所有浏览器
    HTML ASCII 代码
    JavaScript 浏览器版本判定
    Delphi WebBrowser内核版本修改
  • 原文地址:https://www.cnblogs.com/kfcuj/p/14462465.html
Copyright © 2011-2022 走看看