LeetCode 剑指Offer 数据结构之 链表 总结 Part1

LeetCode 剑指Offer 数据结构之 链表 总结 Part1

链表类的题目往往涉及到双指针(快慢),辅助栈,哈希表,递归等算法
剑指Offer中涉及到6道题,文本进行总结:

链表的类定义:

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None
        # 有时候还有不止一个链接 此时可以类比为二叉树,图等结构

第一题:
https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/solution/

剑指 Offer 52. 两个链表的第一个公共节点

最经典的双指针相遇解法:
原理是设长度不同的两个链表长度分别为L1+C、L2+C, C为公共部分的长度,两个指针同步迁移,第一个指针走L1+C 再去走L2(必然走到C),第二个指针走L2+C,再去走L1(必然走到C).返回对面链表是为了确保都走过L1+L2+C。

链表题中常见的指针迁移:

node1 = head# 先定义指针(指向某个节点)
node = node.next # 指针迁移

题解1:

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        node1, node2 = headA, headB
        
        while node1 != node2:
            node1 = node1.next if node1 else headB
            node2 = node2.next if node2 else headA

        return node1

时间复杂度:O(M+N)
空间复杂度:O(1)

第二题:
https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/

剑指 Offer 35. 复杂链表的复制

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

class Node:
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
        self.val = int(x)
        self.next = next
        self.random = random# 随机会指向某个节点,类似图

要做到复制,必须新建数据结构,然后遍历原数据结构进行构建
本题类似于图的复制
1)图的遍历DFS,BFS等
2)图的构建or链表的构建(都要维护一个哈希表,记录节点数据(非指针),当遍历到新的节点时查表,如果没有遇到则放入,如果有则说明图中几点已经被遍历到过,或者链表有环)

算法:深度优先搜索
1)从头结点 head 开始拷贝;
2)由于一个结点可能被多个指针指到,因此如果该结点已被拷贝,则不需要重复拷贝;
3)如果还没拷贝该结点,则创建一个新的结点进行拷贝,并将拷贝过的结点保存在哈希表中;
4)使用递归拷贝所有的 next 结点,再递归拷贝所有的 random 结点。

class Solution:    
    def copyRandomList(self, head: 'Node') -> 'Node':
    	# 维护的哈希表key为原始链表,value为新copy的链表
        def dfs(head):
            if not head: 
            	return None# 递归的终止出口
            if head in visited:# 递归向一个全局哈希表中写数据
                return visited[head]# 如果已经有了,则直接返回value
            # 创建新结点
            copy = Node(head.val, None, None)
            visited[head] = copy# 做copy
            # 类似二叉树的遍历
            # 先next走到底,然后从倒数第一个节点的random开始走
            copy.next = dfs(head.next)# copy.next就是head.next的所有
            copy.random = dfs(head.random)# copy.random就是head.random的所有
            return copy
        visited = {}# 全局哈希表
        return dfs(head)

算法:广度优先搜索
创建哈希表保存已拷贝结点,格式 {原结点:拷贝结点}
创建队列,并将头结点入队;
当队列不为空时,弹出一个结点,如果该结点的 next 结点未被拷贝过,则拷贝 next 结点并加入队列;
同理,如果该结点的 random 结点未被拷贝过,则拷贝 random 结点并加入队列;

    def copyRandomList(self, head):
        '''
        :param head:
        :return:
        '''
        visited = {}
        def bfs(head):
            if not head:
                return head
            clone = Node(head.val, None, None)  # 创建新结点
            queue = []
            queue.append(head)
            visited[head] = clone
            while queue:
                # 还没复制过的下方节点
                tmp = queue.pop()
                # 类似二叉树,左子树,右子树
                if tmp.next and tmp.next not in visited:
                    visited[tmp.next] = Node(tmp.next.val, [], [])
                    queue.append(tmp.next)
                if tmp.random and tmp.random not in visited:
                    visited[tmp.random] = Node(tmp.random.val, [], [])
                    queue.append(tmp.random)
                visited[tmp].next = visited.get(tmp.next)
                visited[tmp].random = visited.get(tmp.random)
            return clone
        return bfs(head)

算法:优化的迭代法
迭代解法:对于一个结点,分别拷贝此结点、next 指针指向的结点、random 指针指向的结点, 然后进行下一个结点…通过哈希表判断,如果遇到已经出现的结点,那么我们不用拷贝该结点,只需将 next 或 random 指针指向该结点即可
此时哈希表最终仍然需要O(N)的空间
考虑将哈希表优化为一个变量:
将链表进行拓展,在每个链表结点的旁边拷贝,比如 A->B->C 变成 A->A’->B->B’->C->C’,然后将拷贝的结点分离出来变成 A->B->C和A’->B’->C’,最后返回 A’->B’->C’

class Solution:
    def copyRandomList(self, head: 'Node') -> 'Node':
        if not head: 
        	return head
        cur = head
        while cur:
            new_node = Node(cur.val,None,None)   # 克隆新结点
            new_node.next = cur.next
            cur.next = new_node   # 克隆新结点在cur 后面
            cur = new_node.next   # 移动到下一个要克隆的点
        cur = head

        while cur:  # 链接random
            cur.next.random = cur.random.next if cur.random else None
            cur = cur.next.next

        cur_old_list = head # 将两个链表分开
        cur_new_list = head.next
        new_head = head.next
        while cur_old_list:
            cur_old_list.next = cur_old_list.next.next
            cur_new_list.next = cur_new_list.next.next if cur_new_list.next else None
            cur_old_list = cur_old_list.next
            cur_new_list = cur_new_list.next
        return new_head
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值