链表题--使用双指针技巧汇总

leetcode:21. 合并两个有序链表

解题思路:

1、一个链表的算法题中是很常见的「虚拟头结点」技巧,也就是 dummy 节点

2、比较两个指针list1 和 list2 , 将值较小的的节点接到 cur 指针

var mergeTwoLists = function(list1, list2) {
  let dummy = new ListNode() 
  let cur = dummy 
  while (list1 && list2) {
    if (list1.val < list2.val) {
      cur.next = list1
      list1 = list1.next
    } else {
      cur.next = list2
      list2 = list2.next
    }
    cur = cur.next
  }
  cur.next = list1 != null ? list1 : list2
  return dummy.next
};

leetcode:86. 分隔链表

解题思路:

可以把原链表分成两个小链表,一个链表中的元素大小都小于 x,另一个链表中的元素都大于等于 x,最后再把这两条链表接到一起就可以了。

var partition = function (head, x) {
  let dummy1 = new ListNode() //存放小于x的链表的虚拟头结点
  let dummy2 = new ListNode() //存放大于等于x的链表的虚拟头结点
  let p1 = dummy1,
    p2 = dummy2 //  p1, p2 指针负责生成结果链表
  let cur = head //cur负责遍历原链表,类似合并两个有序链表的逻辑
  while (cur) {
    if (cur.val >= x) { 
      p2.next = cur
      p2 = p2.next
    } else {
      p1.next = cur
      p1 = p1.next
    }
    let temp = cur.next // 断开原链表中的每个节点的 next 指针
    cur.next = null
    cur = temp
  }
  p1.next = dummy2.next //连接两个链表
  return dummy1.next
};

 leetcode:23. 合并K个升序链表 (常考)

解题思路:

  • 遍历数组,合并两个成新的一个链表再继续合并下一个链表
var mergeKLists = function (lists) {
  let ans = null;
  //合并两个有序链表
  var mergeTwoLists = function (list1, list2) {
    let dummy = new ListNode()
    let cur = dummy
    while (list1 && list2) {
      if (list1.val < list2.val) {
        cur.next = list1
        list1 = list1.next
      } else {
        cur.next = list2
        list2 = list2.next
      }
      cur = cur.next
    }
    cur.next = list1 != null ? list1 : list2
    return dummy.next
  };
  for (let k = 0; k < lists.length; k++) {
    ans = mergeTwoLists(ans, lists[k])
  }
  return ans;
};

 剑指 Offer 22. 链表中倒数第k个节点

解题思路:

  1. 先让一个指针 p1 指向链表的头节点 head,然后走 k 步
  2. 现在的 p1,只要再走 n - k 步,就能走到链表末尾的空指针了,再用一个指针 p2 指向链表头节点 head
  3. 让 p1 和 p2 同时向前走,p1 走到链表末尾的空指针时前进了 n - k 步,p2 也从 head 开始前进了 n - k 步,停留在第 n - k + 1 个节点上,即恰好停链表的倒数第 k 个节点上
var getKthFromEnd = function(head, k) {
    let p1 = head 
    for(let i = 0 ; i< k ; i++){ //p1先走k步
        p1 = p1.next
    }
    let p2 = head 
    while(p1){ //p1还剩n-k步,p1和p2同时走n-k步,当p1走完,p2刚好停在n-k+1步,即倒数第k步
        p2 = p2.next
        p1 = p1.next
    }
    return p2 

};

 leetcode:19. 删除链表的倒数第 N 个结点

解题思路:

要删除倒数第 n 个节点,就得获得倒数第 n + 1 个节点的引用,可以用上面实现的 getKthFromEnd 来操作。

防止出现空指针的情况,使用虚拟节点 dummy 

var removeNthFromEnd = function(head, n) {
    var getKthFromEnd = function(head, k) {
        let p1 = head 
        for(let i = 0 ; i< k ; i++){ //p1先走k步
            p1 = p1.next
        }
        let p2 = head 
        while(p1){ //p1还剩n-k步,p1和p2同时走n-k步,当p1走完,p2刚好停在n-k+1步,即倒数第k步
            p2 = p2.next
            p1 = p1.next
        }
        return p2 
    };
    let dummy = new ListNode() //虚拟头结点
    dummy.next = head
    let x = getKthFromEnd(dummy,n+1) // 删除倒数第 n 个,要先找倒数第 n + 1 个节点
    x.next = x.next.next  // 删掉倒数第 n 个节点
    return dummy.next
};

 leetcode:876. 链表的中间结点

解题思路:

  1. 让两个指针 slow 和 fast 分别指向链表头结点 head
  2. 每当慢指针 slow 前进一步,快指针 fast 就前进两步,这样,当 fast 走到链表末尾时,slow 就指向了链表中点
  3. 如果有两个中间结点,则返回第二个中间结点。
  4. 这个思路可以用来做链表成环题
var middleNode = function(head) {
  let slow = head, fast = head //初始化快慢指针
  while (fast && fast.next) {
    slow = slow.next //慢指针走一步
    fast = fast.next.next //快指针走两步
  }
  return slow //快指针走完,慢指针刚好走到中点
};

 leetcode:141. 环形链表

解题思路:

  1. 每当慢指针 slow 前进一步,快指针 fast 就前进两步。
  2. 如果 fast 最终遇到空指针,说明链表中没有环;如果 fast 最终和 slow 相遇,那肯定是 fast 超过了 slow 一圈,说明链表中含有环。
var hasCycle = function(head) {
    let slow = head ,fast = head
    while(fast && fast.next){
        slow = slow.next
        fast = fast.next.next
        if(slow === fast) return true //相遇就说明有环
    }
    return false
};

 leetcode:142. 环形链表 II

解题思路:

  1. 假设快慢指针相遇时,慢指针 slow 走了 k 步,那么快指针 fast 一定走了 2k 步
  2. fast 一定比 slow 多走了 k 步
  3. 假设相遇点距环的起点的距离为 m,环的起点距头结点 head 的距离为 k - m,也就是说如果从 head 前进 k - m 步就能到达环起点。
  4. 只要把快慢指针中的任一个重新指向 head,然后两个指针同速前进,k - m 步后一定会相遇,相遇之处就是环的起点了
var detectCycle = function (head) {
  let slow = head,fast = head
  while (fast && fast.next) {
    slow = slow.next
    fast = fast.next.next
    if (fast == slow) break
  }
  if (!fast || !fast.next) return null //遇到空指针说明无环
  slow = head //重新指向头结点,fast也行
  while (slow != fast) { //同步前进,相交就是起点
    fast = fast.next
    slow = slow.next 
  }
  return slow
};

 leetcode:160. 相交链表

解题思路:

 

  1.  p1 遍历完链表 A 之后开始遍历链表 B, p2 遍历完链表 B 之后开始遍历链表 A,相当于「逻辑上」两条链表接在了一起。
  2. 如果这样进行拼接,就可以让 p1 和 p2 同时进入公共部分,也就是同时到达相交节点 c1
  3. 如下参考labuladong老师画的图:

var getIntersectionNode = function (headA, headB) {
  let p1 = headA,
    p2 = headB
  while (p1 != p2) {
    if (p1 == null) p1 = headB //p1走一步,如果headA走完就走到headB
    else p1 = p1.next
    if (p2 == null) p2 = headA //p2走一步,如果headB走完就走到headA
    else p2 = p2.next
  }
  return p1
};

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值