算法题套路总结(一)——链表

2019-11-30  本文已影响0人  suoga

最近也做了很多题目,但是回过头一看发现好多题目虽然当时是我独立思考出来的,但是我又忘了该怎么做了,又得花好长时间是思考。不过想来想去发现其实大部分题目还是有套路的,所以我觉得是时候去总结一下这些套路。这是个系列文章,将会总结一些常见题型的套路。第一篇先说说链表。我遇见的所有算法题目所涉及到的链表,都是单链表,因此这里也只说单链表。

单链表其实就是一个data加上一个next指针。由于一个节点只通过next指针保存了它的下一个节点,失去了它的前驱节点信息,因此几乎所有单链表题目都是在前驱节点做文章。同时,单链表数据结构通常长度未知,很多题目也会在长度上做文章。基本上思考链表题目就这么两个思路下手。

围绕这两个问题,链表大致有以下几种经典题型:

双指针

针对于双指针,实际上更细化也分两种:

比如说:

针对链表的环,其实也有两个经典题目,一个是求环的起点142. 环形链表 II
,一个是求环的长度。
求环的长度其实可以转化成小学数学的追及问题。当快慢指针第一次相遇时,此时节点肯定在环上某个位置。我们以此为起点继续走,同时记录步数t。当快慢指针再次相遇时,相当于速度为2的经过t秒比速度为1的多跑了一圈,2*t-1*t=t,因此环的长度就是t
而求环的起点则稍微需要画个图来理解。(暂时缺图,后面补,证明过程在图上)总之结论就是,当快慢指针第一次相遇后,慢指针到环的起点的距离和链表头指针到环起点距离相等。也就是说,此时利用一个新的指针,从头开始走,当和慢指针相遇时,该节点就是环的起点。

环形链表的一个变种就是求两个链表的交点,比如160. 相交链表。这道题表面上看起来有两个链表,不过如果做一个辅助线,比如把B链表首尾相连,也就是让链表的末节点指向B链表的表头,那么这道题就变成了求单链表的环的起点。当然这种思路就需要做题时积累了,临场想的话还是稍微有点费劲。

翻转链表

翻转链表就是很直接的题目,主要是考实现而不是考算法。翻转链表的题目就比较种类繁多了,但各种翻转都万变不离其宗。只要掌握了翻转整个链表的方法,剩下大部分的都是花式处理边界case。

fn reverse_list(mut head: Option<Box<ListNode>>) ->   
  Option<Box<ListNode>> {
  let mut new_head = None;
  while let Some(node) = head.take() {
    let new_node = ListNode{
        val: node.val,
        next: new_head.take(),
    };
    new_head = new_node;
    head = node.next;
  }
  new_head
}

以上是通过新建链表来进行翻转,实际上还可以原地翻转,Rust不太方便实现,Go的伪代码如下:

func reverseLinkedList(head *ListNode) *ListNode {
  if head == nil {
    return nil
  }
  var newHead *ListNode
  cur := head
  for cur != nil {
    next := cur.Next
    cur.Next = newHead
    newHead = cur
    cur = next
  }
  return newHead
}

这种原地翻转实际上只是改变了节点指针的指向,更简单了。

由于直接翻转链表很简单,因此大部分题目都是花式翻转,比如:

翻转链表变种题目,大多数可能需要开辟额外空间或者建立新的虚拟链表,最终再拼接回去。比如:

高精度运算

这类题不多,但是其实解法很简单。因为加减法涉及到进位,所以先翻转链表,然后进行加法操作即可。

总结

经过上面的总结,其实链表基本就这么些题型,只要掌握了这些题型和核心解题思路,拿起双指针和翻转两大武器,基本上就不怕链表的题目了。

上一篇下一篇

猜你喜欢

热点阅读