首页 > 技术文章 > 链表常见题目--附具体分析和代码

linshuhui 2018-10-01 12:49 原文

一、链表的反转

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

分析:刚开始的时候很自然的想到,创建一个列表,然后遍历链表,将每个节点保存在列表中,然后根据列表,反向构造一个新的链表。但是这个很明显很low,一方面是空间复杂度为O(n),一方面是要遍历两遍。后来想都到了另外一种方法,只需要遍历一遍,然后所需的额外空间也非常少。
核心思想就是:遍历链表,创建一个新节点,保存当前节点的值。一个节点指向None,将第二个元素指向第一个节点,第三个指向第二个,以此类推。
代码查考如下

def reverseList( head):
        q = head
        f = None
        while q:
            new = ListNode(q.val)
            new.next = f
            f = new
            q = q.next
        return f

二、删除链表的倒数第N个节点

示例:

给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.

分析:
最容易想到的思路大致如下:先遍历一遍链表,求链表的长度,然后再次遍历,删除倒数的第N个节点。这种思路比较简单,自然效率也比较低。这种思路的具体代码就不加上了。
下面来看一种更为精妙的解法:设置两个指针,一个指针先走N步,然后两个指针一起走,当前面的指针走到链表尾部时,后面的指针下个元素就是要删除的元素。

def removeNthFromEnd(head, n):
        p1, p2 = head, head
        while n:
            p2 = p2.next
            n -= 1
        if p2 is None:
            return head.next
        while 1:
            if p2.next is None:
                p1.next = p1.next.next
                return head
            p1, p2 = p1.next, p2.next

三、求链表的中间节点

给定一个带有头结点 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

分析:跟上面的那个题目类似,设置两个指针,一个走一步,一个走两步,当这快指针走到None时,返回慢指针即可

def middleNode(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        p1 = head 
        p2 = head
        while p2 and p2.next:
            p1 = p1.next
            p2 = p2.next.next
        return p1
 

四、判断链表中是否有环

分析:
同样通过两个指针,一个每次移动一步,一个每次移动两步,如果存在环,那么这两个指针一定会相遇。

代码如下:

def hasCycle(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
        p,q = head,head
        while 1:
            if q is None or q.next is None :return False
            p = p.next
            q = q.next.next
            if p is q: 
                return True

五、找到环的入口

题目描述:
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
分析:
这题解法非常精妙,先按照上题思路,设置两个指针判断链表是否存在环,如果存在,让快指针回到链表开头,然后速度和慢指针一样设置为1,当再次相遇时,就是环的入口。
设起点到环入口距离为a,环入口到相遇点距离为b,相遇时慢指针走了n步。然后从相遇的点开始,快指针回到链表表头,然后速度也变为。
图解

六判断两个链表是否相交

分析:常见的解法有如下几种
*直接循环一个链表,看看这个链表每个元素是否在另外一个链表中。显然这个方法非常低效,时间复杂度为。O(n^2)
*针对第一个链表做一个哈希表,python中可以利用字典,然后再去遍历第二个链表,这种方法时间复杂度为O(n),但是空间复杂度也上升到O(n)
*将一个链表接到另外一个链表之后,如果是相交这那么新链表一定会形成一个环,然后利用上面的方法进行判读即可。这里还有更简单的方法,就是如果成环,那么第二个链表的表头一定在环上,这时候只要直接遍历第二个链表,看看会不会回到表头即可。
*最后这种是最简单的。如果两个链表相交,那么他们的最后一个为节点一定是相同的,所以只要判断最后一个节点是否相同即可,时间复杂度O(n)空间O(1)

def fun(head1,head2):
    while head1.next:
        head1 = head1.next
    while head2.next:
        head2 = head2.next
    if head1 == head2:return True
    else:return  False

七、找到相交链表的起始节点

A:          a1 → a2
                   ↘
                     c1 → c2 → c3
                   ↗            
B:     b1 → b2 → b3

返回c1,如果不相交返回None
分析:先用上面的方法判断时候相关,同时记录链表长度,若相关求出长度差n,然后利用两个指针,长的链表先走n步,然后同时开始遍历,第一个相同的节点就是相交节点。

def getIntersectionNode(headA, headB):
        if headA is None or headB is None:return None
        l1 ,l2 = 0,0
        p1,p2 = headA,headB
        tail_1,tail_2 = None,None
        while 1:
            l1+=1
            if p1.next is None:
                tail_1 = p1
                break;
            p1 = p1.next
        while p2:
            l2 += 1
            if p2.next is None:
                tail_2 = p2
                break;
            p2 = p2.next
        if tail_1 != tail_2:return None
        else:
            n = abs(l1 -l2)
            if l1 >= l2:
                while n:
                    headA = headA.next
                    n-=1
            else:
                while n:
                    headB = headB.next
                    n-=1
            while 1:
                if headA == headB:return headB
                else:
                    headA,headB = headA.next,headB.next

推荐阅读