1.链表的概念:

用一组任意存储的单元来存储线性表的数据元素。一个对象存储着本身的值和下一个元素的地址。

  • 需要遍历才能查询到元素,查询慢。
  • 插入元素只需断开连接重新赋值,插入快。

2.链表的结构:

image.png

3.链表常用的操作

  • 单指针解法
  • 双指针解法
  • 哑节点解法
  • 递归解法

image.png

4.链表的题目

题目一:从尾到头打印链表

输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。

分析

要了解链表的数据结构:
val属性存储当前的值,next属性存储下一个节点的引用。
要遍历链表就是不断找到当前节点的next节点,当next节点是null时,说明是最后一个节点,停止遍历。
因为是从尾到头的顺序,使用一个队列来存储打印结果,每次从队列头部插入。

代码

  1. /*function ListNode(x){
  2. this.val = x;
  3. this.next = null;
  4. }*/
  5. function printListFromTailToHead(head)
  6. {
  7. const array = [];
  8. while(head){
  9. array.unshift(head.val);
  10. head = head.next;
  11. }
  12. return array;
  13. }

注:unshift() 方法将新项添加到数组的开头,并返回新的长度。unshift和push是从两个不同的方向向数组中添加元素。
注释:unshift() 方法会改变数组的长度。
提示:如需在数组末尾添加新项,请使用 push() 方法。

题目2:反转链表

  1. function reverseLinkNode(){
  2. // 创建一个之前的节点
  3. var pre = null
  4. // 创建当前节点
  5. var cur = head
  6. // 若当前节点存在则进入循环
  7. while(cur){
  8. next = cur.next
  9. cur.next = pre
  10. pre = cur
  11. cur = next
  12. }
  13. // 当pre是空的时候说明循环已经结束链表已经到头
  14. return pre
  15. }

题目3:复制一个复杂链表

image.png

方法1:回溯算法+哈希表

本题要求我们对一个特殊的链表进行深拷贝。如果是普通链表,我们可以直接按照遍历的顺序创建链表节点。而本题中因为随机指针的存在,当我们拷贝节点时,「当前节点的随机指针指向的节点」可能还没创建,因此我们需要变换思路。一个可行方案是,我们利用回溯的方式,让每个节点的拷贝操作相互独立。对于当前节点,我们首先要进行拷贝,然后我们进行「当前节点的后继节点」和「当前节点的随机指针指向的节点」拷贝,拷贝完成后将创建的新节点的指针返回,即可完成当前节点的两指针的赋值。

具体地,我们用哈希表记录每一个节点对应新节点的创建情况。遍历该链表的过程中,我们检查「当前节点的后继节点」和「当前节点的随机指针指向的节点」的创建情况。如果这两个节点中的任何一个节点的新节点没有被创建,我们都立刻递归地进行创建。当我们拷贝完成,回溯到当前层时,我们即可完成当前节点的指针赋值。注意一个节点可能被多个其他节点指向,因此我们可能递归地多次尝试拷贝某个节点,为了防止重复拷贝,我们需要首先检查当前节点是否被拷贝过,如果已经拷贝过,我们可以直接从哈希表中取出拷贝后的节点的指针并返回即可。

在实际代码中,我们需要特别判断给定节点为空节点的情况。

方法二:迭代+节点拆分

image.png

题目4:题目:倒数第k个节点

  1. /* 题目:倒数第k个节点
  2. 思路:输入一个链表,输出该链表中倒数第k个结点。
  3. 简单思路: 循环到链表末尾找到 length 在找到length-k节点 需要循环两次。
  4. 优化:
  5. 设定两个节点,间距相差k个节点,当前面的节点到达终点,取后面的节点。
  6. 前面的节点到达k后,后面的节点才出发。
  7. 注意: 需要考虑head为null,k为0,k大于链表长度的情况。*/
  8. function findKfromTail(head, k) {
  9. if (!head || !k) {
  10. return null
  11. }
  12. let front = head
  13. let back = head
  14. let count = 1
  15. while (front.next) {
  16. count++
  17. front = front.next
  18. if (count > k) {
  19. back = back.next
  20. }
  21. }
  22. return (k <= count) && back
  23. }

题目5:输入两个链表,找出他们的第一个公共节点

  1. /* 题目:输入两个链表,找出他们的第一个公共节点 */
  2. /* 思路:
  3. 1.先找到两个链表的长度:length1、length2;
  4. 2.让长一点的链表先走length2-length1步,让长链表和短链表起点相同;
  5. 3.两个链表一起前进,比较获得第一个相等的节点.
  6. */
  7. // 创建获取该链表长度的函数
  8. function getLength(head) {
  9. // 初始化长度和当前节点
  10. let current = head;
  11. let length = 0
  12. // 当链表没循环到底的时候就自增长度
  13. while (current) {
  14. current = current.next
  15. length++
  16. }
  17. return length
  18. }
  19. // 创建一个函数用来获取两个链表中相同的节点
  20. function getSameNode(phead1, phead2) {
  21. // 判断是否为空,为空直接return
  22. if (!phead1 || !phead2) {
  23. return null
  24. } else {
  25. // 长链表先行
  26. let length1 = getLength(phead1)
  27. let length2 = getLength(phead2)
  28. // 若1的长度>2
  29. // 声明较长链表,较短链表和长度差
  30. let long, short, interval
  31. if (length1 > length2) {
  32. long = phead1
  33. short = phead2
  34. interval = length1 - length2
  35. } else {
  36. long = phead2
  37. short = phead1
  38. interval = length2 - length1
  39. }
  40. // 长链表先行
  41. while (interval--) {
  42. long = long.next
  43. }
  44. // 寻找相同节点
  45. while (long) {
  46. if (long == short) {
  47. return long
  48. }
  49. long = long.next
  50. short = short.next
  51. }
  52. return null
  53. }
  54. }

题目6:合并两个排序的链表(从小到大)

  1. /* 题目:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。 */
  2. /* 思路:1.链表头部节点比较,取较小节点。
  3. 2.小节点的next等于小节点的next和大节点的较小值。
  4. 3.如此递归。
  5. 4.返回小节点。
  6. 5.考虑代码的鲁棒性,也是递归的终止条件,两个head为null的情况,取对方节点返回
  7. */
  8. function Merge(pHead1, pHead2) {
  9. if (!pHead1) {
  10. return pHead2;
  11. }
  12. if (!pHead2) {
  13. return pHead1;
  14. }
  15. let head;
  16. if (pHead1.val < pHead2.val) {
  17. head = pHead1;
  18. head.next = Merge(pHead1.next, pHead2);
  19. } else {
  20. head = pHead2;
  21. head.next = Merge(pHead1, pHead2.next);
  22. }
  23. return head;
  24. }
  25. var mergeTwoLists = function(l1, l2) {
  26. if(!l1){
  27. return l2
  28. }else if(!l2){
  29. return l1
  30. }else if(l1.val<l2.val){
  31. l1.next = mergeTwoLists(l1.next,l2)
  32. return l1
  33. }else{
  34. l2.next = mergeTwoLists(l1,l2.next)
  35. return l2
  36. }
  37. };

题目7:删除倒数第n个节点

思路:

双指针方法(设置双指针中间隔n个,先让第一个指针指向头部,第二个指针向后走n个节点;然后两个节点同时向后走,当第二个指针指向尾部的时候,第一个指针指的就是要删除的节点)

  1. var removeNthFromEnd = function(head, n) {
  2. let fast = head,slow =head
  3. while(fast && n>0){
  4. [fast,n] = [fast.next,n-1]
  5. }
  6. if(!fast){
  7. return head.next
  8. }
  9. while(fast.next){
  10. [fast,slow] = [fast.next,slow.next]
  11. }
  12. slow.next = slow.next.next
  13. return head
  14. };

优化:

双指针方法虽然能解决问题,但是当我们遇到要删除的节点是头部节点的时候就没有办法跟原来一样返回头部节点了。所以需要借助哑节点来解决问题——哑节点是在处理与链表相关的操作时,设置在链表头之前的指向链表头的节点,用于简化与链表头相关的操作。

题目8:给定值删除对应的节点

解法:单指针、双指针、哑节点、递归

代码示例:

  1. //单指针
  2. function deleteNode(head, val) {
  3. if (head.val == val) return head.next;
  4. let cur = head;
  5. while (cur.next) {
  6. if (cur.next.val == val) {
  7. cur.next = cur.next.next;
  8. return head;
  9. }
  10. cur = cur.next;
  11. }
  12. return head;
  13. }
  14. //dummy 单指针
  15. function deleteNode( head, val) {
  16. let dummy = new ListNode(-1,head);
  17. let pre = dummy;
  18. while (cur.next) {
  19. if (cur.next.val == val) {
  20. cur.next = cur.next.next;
  21. return dummy.next;
  22. }
  23. cur = cur.next;
  24. }
  25. return dummy.next;
  26. }
  27. //双指针
  28. function deleteNode( head, val) {
  29. if(head.val == val) return head.next;
  30. let pre = head, cur = head.next;
  31. while (cur) {
  32. if (cur.val == val) {
  33. pre.next = cur.next;
  34. return head;
  35. }
  36. pre = cur;
  37. cur = cur.next;
  38. }
  39. return head;
  40. }
  41. //dummy 双指针
  42. function deleteNode(head, val) {
  43. let dummy = new ListNode(-1, head);
  44. let pre = dummy,cur = head;
  45. while (cur) {
  46. if (cur.val == val) {
  47. pre.next = cur.next;
  48. return dummy.next;
  49. }
  50. pre = cur;
  51. cur = cur.next;
  52. }
  53. return dummy.next;
  54. };
  55. //递归
  56. var deleteNode = function(head, val) {
  57. if(head.val == val) return head.next
  58. head.next = deleteNode(head.next,val);
  59. return head
  60. };