原题链接:206、反转链表

image.png

借鉴循环链表思想

观察input与output结点之间的差别,6->2 变为了 2->6 所以我们可在遍历链表时记录下每个结点的新next结点
即现有链表上的上一个结点
image.png

此时,我们可以看到,如果只看prev结点属性,从链表尾向表头看(从右向左),此时链表已经实现反转的效果了
我们接下来要做的,只需要把prev属性替换为next属性即可[滑稽.png]

此时head指针指向链表末尾,然后在链表末尾往回遍历的过程中,实现“属性替换”
image.png
head.next = head.prev;
head = head.prev;
为了链表整洁,删除新增的prev属性

  1. function reverseList(head) {
  2. if(head==null||head.next ==null){
  3. return head;
  4. }
  5. // 遍历1
  6. let prev = null; // 初始化 head 结点的prev为null
  7. while(head) {
  8. head.prev = prev;
  9. prev = head;
  10. head = head.next;
  11. }
  12. // 此时head指向null 指向链尾的是prev
  13. head = prev;
  14. let p = head;
  15. let pprev = p.prev;
  16. // 遍历2 将prev指向的值替换到next
  17. while (p && pprev) { // 因为这里要引用p.prev 而 while里又要删去prev属性 所以使用pprev变量
  18. delete p.prev;
  19. p.next = pprev;
  20. p = pprev;
  21. pprev = p.prev;
  22. }
  23. // 头结点处理
  24. p.next = null;
  25. delete p.prev;
  26. return head;
  27. }

优化-顺向遍历时修改指向

第一种方法是给链表上的每个结点都加上了prev属性,实现了链表结点直接的链接反转,其实仔细回顾实现方式,关键的步骤:
1、两个指针同时指向prev结点 和 当前结点
2、当前结点.next = prev结点
3、更新prev结点、当前结点
4、遍历结束,返回链尾作为新表头

所以,可以将两边遍历优化为一遍,且无需全局的prev属性记录上一个结点
只需在第一次遍历的时候,修改指向即可,

当前结点.next = prev结点 & prev结点.next 不再指向当前结点(由于prev的next在上一轮的当前结点.next即修改了指向 所以无需再修改)

image.png

  1. // 优化,依赖局部变量修改链表里结点的指向关系
  2. function reverseList(head) {
  3. if(head==null||head.next ==null){
  4. return head;
  5. }
  6. let prev = null;
  7. while (head && head.next) {
  8. let next_temp = head.next;
  9. head.next = prev;
  10. prev = head;
  11. head = next_temp;
  12. }
  13. head.next = prev;
  14. return head;
  15. }

优化-链尾开始修改指向

我们除了在第一次遍历的时候修改每两个结点之间的指向,遍历到结束的时候直接返回链尾作为新表头外,可以在遍历到链尾的时候再开始修改结点之间的关联
假设现在已经到链尾了 head.next = null
image.png

通过反向遍历修改结点指向,必须手动断开两个结点直接链接(head.next = null )
而最简便的方式能一次触达链尾且顺其自然地反向遍历的实现,即是递归啦~
递归出口即是链尾处,递归弹栈时即为反向遍历过程

  1. function reverseList2(head){
  2. // 链尾
  3. if(head==null||head.next ==null){
  4. return head;
  5. }
  6. // 返回新链表的头节点 即原链尾
  7. let newhead = reverseList(head.next);
  8. // 此时的head是倒数第二个节点 (当第一次回溯的时候)
  9. head.next.next = head;
  10. head.next = null;// 断开原链接 不然要环
  11. return newhead;
  12. }

借鉴链表头插法

使用头插法建立链表时,链表与输入顺序相反,利用此特性,我们可以在遍历链表时,使用头插法将当前遍历元素塞入新head的后面,然后返回新head即可

  1. // 头插法思路借鉴
  2. function reverseList1(head) {
  3. let dummy = new ListNode();
  4. let curr = dummy;
  5. while(head){
  6. let temp = head.next;
  7. head.next = curr.next;
  8. curr.next = head;
  9. head = temp;
  10. }
  11. return dummy.next ;
  12. };

扩展