1. 数据结构---链表

一、链表

双指针”法解决链表问题

背景:单链表问题由于顺序遍历的特性,有时候执行一些操作的时候会出现问题看似需要多次遍历才能获取数据。

使用双指针法能在一次遍历中获取更多的数据,也可以节约更多的额外控件。“双指针”就是用一个快指针一个慢指针同时进行单链表的顺序扫描。

如此就可以使用快指针的时间差给慢指针提供更多的操作信息。

下面是两个LeetCode下的习题:

(1)给定一个链表,删除链表的倒数第 个节点并返回头结点。

思路:构建先导指针,快于后续指针n-1步,先导指针指向链表尾部时候,慢指针就指向倒数第n个节点

(2)给定一个链表,判断链表中否有环。

传统思路:找个容器把出现的点存起来,出现重复点就判断成环(缺点很明显:额外内存占用)

双指针思路:快指针比慢指针先走,如果快指针被慢指针追上,说明进入闭环

二、算法

1.链表反转

思路:

1.头插法

2.递归

# -*- coding:utf-8 -*-
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None
class Solution:
    # 带头结点返回ListNode
    def ReverseList(self, pHead):
        if pHead == None or pHead.next == None:
            return pHead
        cur = pHead.next.next
        pHead.next.next = None
        while cur:
            post = cur.next
            cur.next = pHead.next
            pHead.next = cur
            cur = post
        return pHead

    # 不带头结点,要先创建头结点
    def ReverseList1(self, pHead):
        if pHead == None or pHead.next == None:
            return pHead
        p = ListNode(0)
        p.next = pHead
        cur = p.next.next
        p.next.next = None
        while cur:
            post = cur.next
            cur.next = p.next
            p.next = cur
            cur = post
        return p.next

    def ReverseList2(self,pHead):
        '''
        递归,时间复杂度O(N),空间复杂度O(1)
        从后向前反转
        :param pHead:
        :return:
        '''
        if pHead == None or pHead.next == None:
            return pHead

        rhead = self.ReverseList2(pHead.next) #
        pHead.next.next = pHead # 把pHead节点放在尾部
        pHead.next = None
        return rhead

 

2.删除排序链表中重复的节点

题目:一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

# -*- coding:utf-8 -*-
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution:
    def deleteDuplication1(self, pHead):
        '''
        删除重复的,并且保留1个
        :param pHead:
        :return:
        '''
        if pHead == None or pHead.next == None: # 空或者只有一个节点
            return
        first,end = pHead,pHead.next
        while end != None:
            while first.val == end.val:
                end = end.next
            first.next = end
            first = first.next
            end = end.next
        return pHead

    def deleteDuplication2(self, pHead):
        '''
        删除重复的,不保留
        :param pHead:
        :return:
        '''
        if pHead == None or pHead.next == None: # 空或者只有一个节点
            return pHead
        dummy = ListNode(None)
        dummy.next = pHead
        first,end = dummy,dummy.next
        flag = 0
        while end != None:
            while end.next and (end.val == end.next.val):
                end = end.next
                flag = 1
            if flag == 1: # 有重复
                first.next = end.next # 删除重复的所有,end指向重复的最后一个
                flag = 0
            else: # 无重复
                first = first.next
            end = end.next
        return dummy.next
# 1,2,3,3,4,4,5
listnode1 = ListNode(1)
listnode2 = ListNode(2)
listnode3 = ListNode(3)
listnode4 = ListNode(3)
listnode5 = ListNode(4)
listnode6 = ListNode(4)
listnode7 = ListNode(5)

listnode1.next = listnode2
listnode2.next = listnode3
listnode3.next = listnode4
listnode4.next = listnode5
listnode5.next = listnode6
listnode6.next = listnode7


solution = Solution()
res = solution.deleteDuplication2(listnode1)
while res != None:
    print(res.val)
    res = res.next

  

3.两个链表的第一个公共节点

两个单链表相交分为三种情况:

  (1)一个有环,一个没环,两个链表不可能相交

  (2)两个都没有环,求相交点或者不相交

  (3)两个都有环,求相交点或者不相交

思路1:利用两个栈分布记录两个链表,然后一起弹出栈顶,比较是否相等,如果相等,就继续一起弹出,直到不相等为止,返回最后一次弹出的节点,即为第一个公共节点,时间复杂度O(m+n),空间复杂度O(m+n)

思路2:headA的长度为A+C(公共长度),headB的长度为B+C,所以当headA走完A+C时,再转过来走B;headB走完B+C时,再转过来走A; 当两个节点都走了A+B+C时,两个节点相遇,返回该节点 时间复杂度O(m+n),空间复杂度O(1)

class Solution:
    def FindFirstCommonNode(self, pHead1, pHead2):
        p1 = pHead1
        p2 = pHead2
        while (p1 != p2):
            if p1 == None:
                p1 = pHead2
            else:
                p1 = p1.next
            if p2 == None:
                p2 = pHead1
            else:
                p2 = p2.next
        return p1

  

4.从尾到头打印链表

题目:输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

# -*- coding:utf-8 -*-
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution:
    def __init__(self):
        self.arr = []

    def printListFromTailToHead(self, listNode):
        '''
        思路: 用栈存储遍历过的节点
        缺点:当链表非常长的时候,就会导致栈溢出
        :param listNode:
        :return:
        '''
        result = []
        if listNode is None:
            return []
        while listNode:
            result.append(listNode.val)
            listNode = listNode.next
        return result[::-1]

    def printListFromTailToHead2(self,listNode):
        '''
        思路:递归的本质是栈
        :param listNode:
        :return:
        '''
        if listNode == None:
            return []
        if listNode.next != None:
            self.printListFromTailToHead2(listNode.next)
        # print(listNode.val,end=' ')
        self.arr.append(listNode.val)
        return self.arr

L1 = ListNode(1)
L2 = ListNode(2)
L3 = ListNode(3)
L4 = ListNode(4)
L5 = ListNode(5)
L6 = ListNode(6)

# head.next = L1
L1.next = L2
L2.next = L3
L3.next = L4
L4.next = L5
L5.next = L6

S = Solution()
res = S.printListFromTailToHead2(L1)
print(res)

  

5.合并两个有序链表

时间复杂度O(N),空间复杂度O(1)

思路:

  1)首先将两个链表第一个值比较小的链表作为头结点,

  2)然后依次遍历两个链表的值,比较大小

  3)再把次链表的直接接到主链表上

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution:
    def mergeTwoLists(self, l1, l2):
        if l1 is None and l2 is None:
            return None
        if l1 is None:
            return l2
        if l2 is None:
            return l1

        head = None
        ptr1 = None
        ptr2 = None
        if l1.val < l2.val:
            head = l1
            ptr1 = l1
            ptr2 = l2
        else:
            head = l2
            ptr1 = l2
            ptr2 = l1

        while ptr1.next is not None and ptr2 is not None:
            if ptr1.val <= ptr2.val and ptr1.next.val >= ptr2.val:
                temp = ptr1.next
                ptr1.next = ptr2
                ptr2 = ptr2.next
                ptr1.next.next = temp

            ptr1 = ptr1.next

        if ptr1.next is None:
            ptr1.next = ptr2
        return head

    # 递归
    def merge2LinkList(self,l1,l2):
        if l1 ==  None:
            return l2
        if l2 == None:
            return l1
        if l1.val < l2.val:
            l1.next = self.merge2LinkList(l1.next,l2)
            return l1
        else:
            l2.next = self.merge2LinkList(l1,l2.next)
            return l2

  

6.复杂链表的复制

题目:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

思路1:第一步复制主链节点;第二步复制设置每个节点的m_pSibling节点 时间复杂度O(N2)

思路2:第一步复制主链节点,把<N,N'>配对信息放到哈希表中;第二步设置复制链表上的每个节点的m_pSibling,时间复杂度O(N),空间复杂度O(N)

思路3:第一步复制主链节点,把N'链接到N的后面;第二步设置复制出来的节点的m_pSibling;第三步长链表拆分成两个两个链表,空间复杂度O(1),时间复杂度O(N)

class Solution:
    def Clone(self, pHead):
        '''
        思路3
        :param pHead:
        :return:
        '''
        self.CloneNodes(pHead)
        self.ConnectSiblingNodes(pHead)
        return self.ReconnectNodes(pHead)

    def CloneNodes(self,pHead):
        '''
        复制每个节点,并插在被复制节点的后面
        :param pHead:
        :return:
        '''
        p = pHead
        while p:
            newNode = RandomListNode(None)
            newNode.label = p.label
            newNode.random = None # 不保存随机下一跳
            newNode.next = p.next
            p.next = newNode
            p = newNode.next
        return pHead

    def ConnectSiblingNodes(self,pHead):
        '''
        设置复制出来的节点的m_pSibling
        :param pHead:
        :return:
        '''
        p = pHead
        while p:
            newNode = p.next
            if p.random != None:
                newNode.random = p.random.next
            p = newNode.next
        return pHead

    def ReconnectNodes(self,pHead):
        '''
        拆分两个链表,把奇数位置的节点用next链接起来就是原始链表,把偶数位置的节点用next链接起来就是复制链表
        :param pHead:
        :return:
        '''
        p = pHead
        pClonedHead = None
        pClonedNode = None

        if p:
            pClonedHead = pClonedNode = p.next
            p.next = pClonedNode.next
            p = p.next

        while p:
            pClonedNode.next = p.next
            pClonedNode = pClonedNode.next
            p.next = pClonedNode.next
            p = p.next

        return pClonedHead



L1 = RandomListNode(1)
L2 = RandomListNode(2)
L3 = RandomListNode(3)
L4 = RandomListNode(4)

L1.next = L2
L2.next = L3
L3.next = L4

L1.random = L3
L2.random = L1
s = Solution()
res = s.Clone(L1)
while res:
    print(res.label)
    if res.random != None:
        print(res.random)
    res = res.next

  

7.链表中倒数第k个节点

思路:快慢指针,第一个指针先走k步,然后第二个指针开始走,直到第一个指针下一跳为空,第二个指针所在的位置就是倒数第k个节点。相当于制造了一个k长度的尺子,把尺子从头往后移动,当尺子的右端与链表的末尾对齐的时候,尺子左端所在的结点就是倒数第k个结点

class Solution:
    def FindKthToTail(self, head, k):
        if head == None or k <= 0:
            return None
        slow = fast = head
        while k > 1:
            if fast.next != None:
                fast = fast.next
                k -= 1
            else:
                return None
        while fast.next != None:
            slow = slow.next
            fast = fast.next
        return slow

  

8.链表中环的入口节点

思路:

  1)先找快慢指针相遇的节点,这个节点肯定在环中;

  2)然后通过这个节点,找到环的长度;

  3)环的长度已知,可以设置快指针从头结点出发,先走环的长度的距离,再用慢指针,从头结点开始走,同时,快指针也开始走,两个指针相遇的地方就是环的入口位置

    def MeetingNode(self, head):#有头结点
        '''
        :param head:
        :return: 返回快慢指针相遇的节点
        '''
        if head == None or head.next == None:
            return None
        slow = fast = head
        try:
            while slow.next and fast.next.next:
                slow = slow.next
                fast = fast.next.next
                if slow == fast:
                    return slow
        except Exception as e:
            pass
        return None

    def EntryNodeOfLoop(self,pHead):#没有头结点
        '''
        :param pHead:
        :return:
        '''
        meetingNode = self.MeetingNode(pHead)
        # print('meetingNode',meetingNode.val)
        if meetingNode == None:
            return None

        # 得到环中节点的数目
        pNode1 = meetingNode
        count = 0
        while pNode1.next != meetingNode:
            count += 1
            pNode1 = pNode1.next

        # print('count',count)
        pNode2,pNode3 = pHead,pHead
        for i in range(count+1): # 先走count步
            pNode2 = pNode2.next
        # print('pNode2',pNode2.val)
        while pNode2 != pNode3:
            pNode2 = pNode2.next
            pNode3 = pNode3.next
        return pNode2

s = Solution()
# head = ListNode(None)
L1 = ListNode(1)
L2 = ListNode(2)
L3 = ListNode(3)
L4 = ListNode(4)
L5 = ListNode(5)
L6 = ListNode(6)

# head.next = L1
L1.next = L2
L2.next = L3
L3.next = L4
L4.next = L5
L5.next = L6
L6.next = L3

res = s.EntryNodeOfLoop(L1)
if res != None:
    print(res.val)

  

参考:

【1】Java实现单向链表

原文地址:https://www.cnblogs.com/nxf-rabbit75/p/10008297.html