fucking-algorithm icon indicating copy to clipboard operation
fucking-algorithm copied to clipboard

双指针技巧秒杀七道链表题目 :: labuladong的算法小抄

Open utterances-bot opened this issue 4 years ago • 96 comments

文章链接点这里:双指针技巧秒杀七道链表题目

评论礼仪 见这里,违者直接拉黑。

utterances-bot avatar Sep 27 '21 07:09 utterances-bot

好文

RuiMM avatar Sep 27 '21 07:09 RuiMM

感谢大佬

CodeSheepX avatar Sep 29 '21 11:09 CodeSheepX

刚开始写相交链表,写的很多判断条件,最后去理解了一下,还是切换的逻辑上,没想到被绊倒了

luckywords avatar Oct 04 '21 08:10 luckywords

大佬,最后一个判断交叉的,当没有交叉时,是不是只要判断p1,p2都已经发生过一次交换后,再出现了需要交换的时候,就可以直接退出了。

func getIntersectionNode(a,b *LinkNode) *LinkNode {
	if a == nil || b == nil{
		return nil
	}
	p1, p2 := a, b
	tag, tag2 := false, false
	for p1.val != p2.val {
		if p1.next == nil {
			if tag && tag2 {
				return nil // 无交叉
			}
			p1 = b
			tag = true
		} else {
			p1 = p1.next
		}
		if p2.next == nil {
			if tag && tag2 {
				return nil // 无交叉
			}
			p2 = a
			tag2 = true
		} else {
			p2 = p2.next
		}
	}
	return p1 // 返回p1或者p2都可
}

lybxkl avatar Oct 17 '21 10:10 lybxkl

大佬

Lucas-ljx avatar Nov 04 '21 15:11 Lucas-ljx

太牛了

Lucas-ljx avatar Nov 04 '21 15:11 Lucas-ljx

相交链表的这个技巧太牛了

MrXIAGAO avatar Nov 10 '21 09:11 MrXIAGAO

讲的真的很不错,我现在能够解答一些类似题目了。。。很开心。谢谢大佬。

javasopp avatar Nov 15 '21 11:11 javasopp

相交链表,大佬这个思路确实巧妙,我想了一个复杂度一样的算法但是更好理解。分别统计两条链表的长度,差值表示非公共部分的长度差,那么让长链表的指针先走差值的步数,再齐头并进,那么如果两个指针相等则是相交的位置。

 public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p = headA, q = headB;
        int l1=0, l2=0;
        while(p.next!=null){ // 统计A链表长度
            p=p.next;
            l1++;
        }
        while(q.next!=null){// 统计B链表长度
            q=q.next;
            l2++;
        }
        p = headA;
        q = headB;
        if(l2>=l1){
            for(int i=0;i<l2-l1;i++){
                q=q.next;
            }
        }else{
            for(int i=0;i<l1-l2;i++){
                p=p.next;
            }
        }
        while(p!=null&&q!=null){
            if(p==q){ // 相等说明相交。
                return q;
            }
            p=p.next;
            q=q.next;
        }
        return null;

    }

Clarence-G avatar Nov 22 '21 13:11 Clarence-G

@Clarence-G 这个解法也不错!最后一个 while 循环其实不用那么麻烦,我稍改了下你的代码,你看下:

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    int lenA = 0, lenB = 0;
    // 计算两条链表的长度
    for (ListNode p = headA; p != null; p = p.next) {
        lenA++;
    }
    for (ListNode q = headB; q != null; q = q.next) {
        lenB++;
    }
    // 让 p 和 q 到达尾部的距离相同
    ListNode p = headA, q = headB;
    if (lenA > lenB) {
        for (int i = 0; i < lenA - lenB; i++) {
            p = p.next;
        }
    } else {
        for (int i = 0; i < lenB - lenA; i++) {
            q = q.next;
        }
    }
    // 看两个指针是否会相同,p == q 时有两种情况:
    // 1、要么是两条链表不相交,他俩同时走到尾部空指针
    // 2、要么是两条链表相交,他俩走到两条链表的相交点
    while (p != q) {
        p = p.next;
        q = q.next;
    }
    return p;
}

labuladong avatar Nov 23 '21 02:11 labuladong

@labuladong 谢谢大佬,这样确实简单,null值也可以判等,我总是忽略这个条件。

Clarence-G avatar Nov 23 '21 13:11 Clarence-G

相交链表,大佬的思路真是清奇,撸完之后爽呆了!

iMinger avatar Nov 29 '21 03:11 iMinger

东哥,我有点飘了

savvy1st avatar Dec 01 '21 22:12 savvy1st

妙啊

coder-pig avatar Dec 14 '21 07:12 coder-pig

有个疑惑,我想请问下 相交链表的那个如果是这种呢 链表A->B->C->D 和链表F->G->C->H->I呢 也就是相交元素只有C元素,后面的并不是两个链表的公共部分,这样的话用逻辑拼接应该不可行了吧?

Money8888 avatar Dec 25 '21 05:12 Money8888

相交链表有点问题,如果题目是要求找2个链表的第一个相交点,这种解法才是最优吧;如果只要判断是否相交,感觉可以两个链表分别遍历到头,比较最后一个指针相等就可以了

hihozoo avatar Dec 27 '21 10:12 hihozoo

如果有关于快慢指针能解决判断是否有环的证明就更棒了

hihozoo avatar Dec 28 '21 08:12 hihozoo

关于寻找环起点的原理解释还漏了一点,慢指针在进入环之后,慢指针会在一圈内与快指针相遇,这才能证明 k - m 是 head 与环起点的距离。

关于慢指针会在一圈与快指针相遇,通过数学归纳法可证明, a) 快指针与慢指针之间差一步时,继续走,慢指针前进一步,快指针前进2步,两者相遇, b)快指针与慢指针之间差两步时,继续走,慢指针前进一步,快指针前进2步,两者距离差一步,转化为第一种情况 c)快指针之间差N步,继续走,慢指针前进一步,快指针前进两步,两者之间相差N-1步 所以,慢指针要走多少步会相遇?走N次,由于初始距离N必然小于环的周长,所以对于慢指针来说,一圈内就能与快指针相遇

hihozoo avatar Dec 28 '21 10:12 hihozoo

不懂就问,最后相交链表,如果两个链表长度一样,最后判断条件会不会出错

mojito-z avatar Jan 09 '22 14:01 mojito-z

如果整个链表是个环,怎么算起点

LazuriteMoon avatar Jan 10 '22 07:01 LazuriteMoon

@mojito-z 多动手,少动嘴,你试一下什么都知道了。

labuladong avatar Jan 11 '22 02:01 labuladong

打卡, 判断环那边之前做过一次, 但只想到快慢指针, 快慢指针是用来判断是否存在环的, 但是如何求得环入口结点我一直没搞懂, 然后再看的时候也带有恐惧, 但是玩了半个小时手机, 恐惧心理消失之后, 安下心来看, 一下子看懂了👀, 好文, 好耐下心慢慢看

Asber777 avatar Jan 15 '22 08:01 Asber777

有个疑惑,我想请问下 相交链表的那个如果是这种呢 链表A->B->C->D 和链表F->G->C->H->I呢 也就是相交元素只有C元素,后面的并不是两个链表的公共部分,这样的话用逻辑拼接应该不可行了吧?

@Money8888 不是,你单链表又不是二叉树,怎么可能C的next会同时有D和H?真的笑到了

ErronZrz avatar Jan 16 '22 03:01 ErronZrz

@Asber777 手机获得全场 mvp…… 不过学习确实是这样,有时候冥思苦想一整天都没有什么进展,但是睡一觉起来发现突然顿悟了。

labuladong avatar Jan 17 '22 06:01 labuladong

相交链表 把一个A的最后一个结点的next改成B,就成了 环形链表II 了

euyy avatar Jan 24 '22 11:01 euyy

23题,为什么在加入小根堆时不能直接把所有的节点都加入小根堆,而只是加入了每个链表的表头结点呢?

sheli00 avatar Feb 04 '22 08:02 sheli00

解决了,解法本身没问题,是忘记了将尾结点指向null

sheli00 avatar Feb 05 '22 03:02 sheli00

相交链表while循环也可以这么写,感觉读起来会简洁一些:

func getIntersectionNode(headA, headB *ListNode) *ListNode {
	p1 := headA
	p2 := headB
	for p1 != nil || p2 != nil {
		if p1 == p2 {
			return p1
		}
		if p1 == nil {
			p1 = headB
			continue
		}
		if p2 == nil {
			p2 = headA
			continue
		}
		p1 = p1.Next
		p2 = p2.Next
	}
	return nil
}

nce3xin avatar Feb 05 '22 12:02 nce3xin

js 版 23. 合并K个升序链表(困难)实现一个优先队列

var mergeKLists = function(lists) {
  class PriorityQueue {
    constructor(comparator = (a, b) => a > b) {
      this._heap = [];
      this._comparator = comparator;
    }
    // 交换节点位置
    _swap(i1, i2) {
      [this._heap[i1], this._heap[i2]] = [this._heap[i2], this._heap[i1]];
    }
    _compare(i1, i2) {
      return this._comparator(this._heap[i1], this._heap[i2]);
    }
    // 获得父节点
    _parent(i) {
      // return (i - 1) >> 1;
      return Math.floor((i - 1) / 2);
    }
    // 获得左节点
    _leftChild(i) {
      return 2 * i + 1;
    }
    // 获得右节点
    _rightChild(i) {
      return 2 * i + 2;
    }
    // 上移
    _shiftUp(index) {
      if (index === 0) return;

      const parent = this._parent(index);
      if (this._heap[parent] && !this._compare(parent, index)) {
        this._swap(parent, index);
        this._shiftUp(parent);
      }
    }
    // 下移
    _shiftDown(index) {
      const left = this._leftChild(index);
      const right = this._rightChild(index);
      if (this._heap[left] && this._compare(left, index)) {
        this._swap(left, index);
        this._shiftDown(left);
      }
      if (this._heap[right] && this._compare(right, index)) {
        this._swap(right, index);
        this._shiftDown(right);
      }
    }
    // 插入
    push(value) {
      this._heap.push(value);
      this._shiftUp(this._heap.length - 1);
    }
    // 删除堆顶
    pop() {
      if (this.size() === 1) return this._heap.shift();
      const top = this._heap[0];
      // pop()方法删除数组最后一个元素并返回,赋值给堆顶
      this._heap[0] = this._heap.pop();
      // 对堆顶重新排序
      this._shiftDown(0);
      return top;
    }
    // 获取堆顶
    peek() {
      return this._heap[0];
    }
    // 获取堆的大小
    size() {
      return this._heap.length;
    }
    isEmpty() {
      return this.size() === 0;
    }
  }

  if (lists.length === 0) return null;

  // 虚拟头结点
  const dummy = new ListNode(-1);
  let p = dummy;

  // 优先级队列,最小堆
  const pq = new PriorityQueue((a, b) => a.val < b.val);
  // 将 k 个链表的头结点加入最小堆
  for (const head of lists) {
    if (head) pq.push(head);
  }

  while (!pq.isEmpty()) {
    // 获取最小节点,接到结果链表中
    const node = pq.pop();
    p.next = node;
    if (node.next !== null) {
      pq.push(node.next);
    }
    // p 指针不断前进
    p = p.next;
  }

  return dummy.next;
};

jswxwxf avatar Feb 08 '22 02:02 jswxwxf

我用两个链表首尾相连转化为求环形链表起点的问题后,leetCode运行报错了,说改变了链表的结构:Intersected at '4', ERROR: linked structure was modified.

GreatWallt avatar Feb 11 '22 10:02 GreatWallt