数据结构之链表

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhouhong0284/article/details/79977855

链表应该是面试时被提及最频繁的数据结构,链表的结构很简单,它由指针把若干个节点连接成链状结构。
链表的创建、插入节点、删除节点等操作都只需要20行左右的代码就能实现,因此适合面试时提问。
常见的面试题有:
1)从尾到头打印链表;
2)删除链表的节点;
3)删除链表中倒数第k个结点;
4)反转链表;
5)合并两个排序的链表;
6)两个链表的第一个公共结点;
7)判断链表中是否有环;找出环的入口
8)判断一个链表是否为回文;

对应的解题思路为:

1

1)首先从头到尾打印这个链表,将打印的值放入一个栈中,然后从栈中依次弹出元素。依赖栈的后入先出的结构实现了从尾到头打印链表。

2

2)输入:将被删除的节点Node
将这个节点的数值赋值为下一个节点的数值,然后让它指向下下个节点.

Node.val=Node.next.val
Node.next=Node.next.next

也就是将下一个节点赋值给这个节点来实现删除这个节点的操作。

3

3) 在链表中,因为内存不是连续的,表示节点的位置需要一个辅助的指针来实现。
比如说要找倒数第k个结点,那么就使用两个指针,fast在slow指针的前k个位置点,当fast指向none时,slow就指向倒数第k个结点。

class Solution(object):
    def removeNthFromEnd(self, head, n):
        slow=fast=dummy=ListNode(0)
        dummy.next=head
        for i in range(n+1):
            fast=fast.next
        while fast:
            slow=slow.next
            fast=fast.next
        slow.next=slow.next.next
        return dummy.next       #考虑删除的是head的情况  

4

4)使用迭代的方法,依次保存下一个节点–>让这个节点指向上一个节点–>继续遍历下一个保存的节点。

pre=None
while head:
        Next=head.next
        head.next=pre
        pre=head
        head=Next
    return pre

5

5)采用递归的方法,每次比较链表最开始的两个节点,并将较小的一个值接在后面,然后依次比较后面的节点。

if not l1:
    return l2
if not l2:
    return l1
if l1.val>l2.val:
    a,b=l2,l1
else:
    a,b=l1,l2
a.next=self.sort(a.next,b)
return a

思考:如果是对两个已排序的数组合并呢?要求空间复杂度为O(1)

在合并两个数组时,如果从前往后幅值每个数字(字符)则需要重复移动数字(字符)多次,那么我们可以考虑从后往前幅值,这样就能减少移动的次数,从而提高效率。

6

6)当两个单链表有公共结点之后,它们后面的所有结点都是一样的。因此如果我们可以从最后面的数据开始比较,找到最后一个相同的节点就是第一个公共结点,可以使用栈来存储两个链表的节点来实现,可是这样需要O(M+N)的空间复杂度。不能从头开始比较的一个重要原因是:两个链表的长度不一样。因此可以先遍历两个链表得到他们的长度,就能知道哪个链表更长,以及长的链表比短的链表长多少;在第二次遍历的时候,先让长的链表多走若干步,接着同时在两个链表上遍历,找到的第一个相同的节点就是他们的第一个公共结点。

class Solution(object):
    def getIntersectionNode(self, headA, headB):
        if not headA or not headB:
            return None
        a,b=headA,headB
        while a is not b:
            a=a.next if a else headB  #注意这里是if a而不是a.next
            b=b.next if b else headA
        return a

为了考虑两者之间没有公共结点的情况,所以a=b=None的情况在第二次遍历时也存在。

7

7)从一个新颖的角度出发,如果链表中有环,那么两个人以不同的速度在环中跑,总有一天会遇到,如果直到跑得快指向空了还没遇到说明没有环。

class Solution(object):
def hasCycle(self, head):
    if head==None or head.next==None:
        return False
    slow=head.next
    fast=slow.next
    while fast!=None and slow!=None:
        if fast==slow:
            return True
        slow=slow.next
        fast=fast.next.next if fast.next else None
    return False

扩展:如何找到环的入口呢?
首先我们已经找到位于环中的某一个节点,也就是当slow=fast时指向的节点,然后根据这个节点我们可以得到环的长度n,方法是从这个节点出发,一边继续向前移动一边计数,当再次回到这个节点的时候,就可以得到环的长度n;然后定义两个节点p1,p2为头结点,从头结点开始,p1节点先走n步,然后两个节点同时向前走,相遇的点就是环的入口。

8

8)回文的性质是从前往后读与从后往前读相同,也就是链表数值是对称的,那么可以将链表的前半部分逆转,然后比较这个逆转后的部分与后半部分的数值是否一致。

class Solution(object):
    def isPalindrome(self, head): 
        pre=None
        slow=fast=head
        while fast and fast.next:
            fast=fast.next.next
            Next=head.next
            head.next=pre
            pre=head
            head=Next
        if fast:
            head=head.next
        while pre and pre.val == head.val:
            pre=pre.next
            head=head.next
        if not pre and not head:
            return True
        else:
            return False

注意

1.代码的鲁棒性
要考虑链表的头结点为空,链表只有一个节点或链表长度小于k的情况,防止代码奔溃。

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页