leetcode-explore-learn-数据结构-链表5
- 1.小结
- 2.例题
- 2.1合并两个有序链表
- 思路1:迭代
- 思路2:递归
- 2.2 两数相加
- 2.3 扁平化多级双向链表
- 2.4 复制带随机指针的链表
- 2.5 旋转链表
本系列博文为leetcode-explore-learn子栏目学习笔记,如有不详之处,请参考leetcode官网:https://leetcode-cn.com/explore/learn/card/linked-list/
所有例题的编程语言为python
1.小结
单链表双链表的相同点:
1.无法在常量时间内随意访问数据
2.能够在o(1)时间内,在给定节点之后完成新节点的添加
3.能够在o(1)的时间内,删除链表的第一节点
区别:删除给定节点
单链表无法由给定节点获取前一个节点,因此在删除给定节点之前必须花费o(n)的时间来找出前一个节点
双链表中,可以使用“prev”字段获取前一个节点,因此能在o(1)的时间内=删除给定节点。
链表的主要优势:删除添加节点十分方便
2.例题
2.1合并两个有序链表
leetcode 21
将两个升序链表合并为一个新的升序链表并返回。
思路1:迭代
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = Noneclass Solution(object):def mergeTwoLists(self, l1, l2):""":type l1: ListNode:type l2: ListNode:rtype: ListNode"""if l1==None or l2==None:return l1 if l1 else l2res_head=ListNode(0)res_curr_node=res_headwhile(l1 and l2):if l1.val<=l2.val:l1_next_node=l1.nextl1.next=Noneres_curr_node.next=l1l1=l1_next_noderes_curr_node=res_curr_node.nextelse:l2_next_node=l2.nextl2.next=Noneres_curr_node.next=l2l2=l2_next_noderes_curr_node=res_curr_node.nextif l1:res_curr_node.next=l1if l2:res_curr_node.next=l2return res_head.next
精简写法:
class Solution(object):def mergeTwoLists(self, l1, l2):if l1==None or l2==None:return l1 if l1 else l2res_head=ListNode(0)res_curr_node=res_headwhile(l1 and l2):if l1.val<=l2.val:res_curr_node.next=l1l1=l1.nextelse:res_curr_node.next=l2l2=l2.nextres_curr_node=res_curr_node.nextres_curr_node.next=l1 if l1 else l2 return res_head.next
思路2:递归
递归最重要的是递归出口
class Solution(object):def mergeTwoLists(self, l1, l2):""":type l1: ListNode:type l2: ListNode:rtype: ListNode"""if l1==None:return l2elif l2==None:return l1elif l1.val<=l2.val:l1.next=self.mergeTwoLists(l1.next,l2)return l1 # 由顶向下时,已经决定了第一个节点是谁,递归得到最底端时,直接将长链表的剩余部分链接回已经排序好的链表中。else:l2.next=self.mergeTwoLists(l1,l2.next)return l2
2.2 两数相加
给出两个 非空的链表 来表示两个 非负整数。其中,他们各自的位数是按照 逆序 方式存储的,并且每个节点只能存储一位 数字。
如果,我们将这两个数相加起来,返回一个新的链表表示它们的和
可以假设除了数字0之外,两个数字都不会以0开头
思路:逆序存储简化了问题,直接遍历两个链表的对位元素,相加后维护一个进位标志flag。
冗余
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = Noneclass Solution(object):def addTwoNumbers(self, l1, l2):""":type l1: ListNode:type l2: ListNode:rtype: ListNode"""flag=0res_head=ListNode(0)res_cur_node=res_headwhile(l1 and l2):sum_num=l1.val+l2.val+flagflag=sum_num//10res_cur_node.next=ListNode(sum_num%10)res_cur_node=res_cur_node.nextl1=l1.nextl2=l2.nextwhile(l1):#print(l1.val)sum_num=l1.val+flagflag=sum_num//10res_cur_node.next=ListNode(sum_num%10)res_cur_node=res_cur_node.nextl1=l1.nextwhile(l2):#print(l2.val)sum_num=l2.val+flagflag=sum_num//10res_cur_node.next=ListNode(sum_num%10)res_cur_node=res_cur_node.nextl2=l2.nextif flag:res_cur_node.next=ListNode(flag)return res_head.next
精简
class Solution(object):def addTwoNumbers(self, l1, l2):""":type l1: ListNode:type l2: ListNode:rtype: ListNode"""carry=0res_head=ListNode(0)res_cur_node=res_headwhile(l1 or l2 or carry):if l1:carry+=l1.vall1=l1.nextif l2:carry+=l2.vall2=l2.nextcarry,val=divmod(carry,10)res_cur_node.next=ListNode(val)res_cur_node=res_cur_node.nextreturn res_head.next
2.3 扁平化多级双向链表
多级双向链表中,除了指向下一个节点和前一个节点的指针外,它还有一个子链表指针,可能指单独的双向链表。这些子链表也可能有一个或多个自己的子项。一次类推,生成多级数据结构 。
给出位于链表第一级的头节点,请扁平化链表,使所有的节点出现子啊单级双链表中。
直觉:采用递归的方式求解,可每个节点该如何处理呢:
新建一个res_head,res_head下一个节点应该为curr_node.child,curr_node.next,
官方思路递归:扁平化处理可以看作对二叉树进行先序遍历,child作为二叉树中的指向座子树的left指针,next可以看作二叉树中的right指针。
难点:深度优先遍历到了末尾该如何让扁平化操作?
flatten_dfs(prev, curr)接收两个指针作为函数参数,并返回扁平化链表中的尾部指针。curr指向需要扁平化的子列表,prev指向curr元素的前一个元素。
在dfs函数中首先要建立prev和curr的双向链接
然后对左子树进行操作dfs(curr,cuur.child)其将返回扁平化子列表的尾部元素,再调用dfs(tail,curr.next)对右子树进行操作。注意点:
1.在进行左子树操作时,需要先保存curr.next的信息
2.在扁平化child指针所指向的列表之后,应该删除child指针
"""
# Definition for a Node.
class Node(object):def __init__(self, val, prev, next, child):self.val = valself.prev = prevself.next = nextself.child = child
"""class Solution(object):def flatten(self, head):""":type head: Node:rtype: Node"""if head==None:return headdummy_head=Node(None,None,head,None)self.flatten_dfs(dummy_head,head)dummy_head.next.prev=Nonereturn dummy_head.nextdef flatten_dfs(self,prev,curr):if curr==None:return prevcurr.prev=prevprev.next=currtempNext=curr.nexttail=self.flatten_dfs(curr,curr.child)curr.child=Nonereturn self.flatten_dfs(tail,tempNext)
官方思路迭代:
2.4 复制带随机指针的链表
给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点
编程实现这个链表的深度拷贝
难点:新建节点,很简单,随机节点该如何指向?
先遍历一遍形成一个单链表,再遍历一遍原链表找到带随机指针的节点,在单链表中找到对应的节点,难的是对应节点怎么找,因为,指针存的是地址,在新链表中并不知道该指向哪一个?解决方案–维护一张map映射表,每个节点对应的新节点,因此便可以找到对应的节点。
"""
# Definition for a Node.
class Node:def __init__(self, x, next=None, random=None):self.val = int(x)self.next = nextself.random = random
"""class Solution(object):def copyRandomList(self, head):""":type head: Node:rtype: Node"""if head==None:return NonevisitedHash={}res_head=Node(0)res_cur_node=res_headcur_node=headwhile(cur_node):node=Node(cur_node.val,None,None)res_cur_node.next=nodevisitedHash[cur_node]=noderes_cur_node=res_cur_node.nextcur_node=cur_node.nextcur_node=headres_cur_node=res_head.nextwhile(cur_node):if cur_node.random:node=visitedHash[cur_node.random]res_cur_node.random=nodecur_node=cur_node.nextres_cur_node=res_cur_node.nextreturn res_head.next
参考官网给题解:递归
带随机指针的链表可以看作一张图,要做的是遍历整张图,并拷贝它。拷贝的意思是每当遇到一个新的未访问过的节点,需创造新的节点。在回溯的过程中记录访问过的节点,否则因为随机指针存在,会导致死循环。
"""
# Definition for a Node.
class Node:def __init__(self, x, next=None, random=None):self.val = int(x)self.next = nextself.random = random
"""class Solution(object):def __init__(self):self.visitedHash={}def copyRandomList(self, head):""":type head: Node:rtype: Node"""if head==None:return Noneif head in self.visitedHash:return self.visitedHash[head]node=Node(head.val,None,None)self.visitedHash[head]=nodenode.next=self.copyRandomList(head.next)node.random=self.copyRandomList(head.random)return node
2.5 旋转链表
给定一个链表,将链表的每个节点向右移动k个位置,其中k是非负数。
思路1:和旋转数组一样,每次右移一位,移动K次即可
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = Noneclass Solution(object):def rotateRight(self, head, k):""":type head: ListNode:type k: int:rtype: ListNode"""if head==None or head.next==None:return headl=0cur_node=headwhile(cur_node):l+=1cur_node=cur_node.nextk=k%ldef rotate_one_step(head):pre_pre_node=Nonepre_node=headcur_node=head.nextwhile(cur_node):pre_pre_node=pre_nodepre_node=cur_nodecur_node=cur_node.nextpre_pre_node.next=Nonepre_node.next=headreturn pre_nodewhile(k>0):head=rotate_one_step(head)k-=1return head
思路2: 移动k次,就是将倒数的k个节点相对顺序不变的移动到链表的头部。所以从链表的head开始,往后找到L-k个节点,将后半部分移动到链表的前半部分。
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = Noneclass Solution(object):def rotateRight(self, head, k):""":type head: ListNode:type k: int:rtype: ListNode"""if head==None or head.next==None:return headl=0cur_node=headwhile(cur_node):l+=1cur_node=cur_node.nextk=k%l# 首尾相连pre_node=Nonecur_node=headwhile(cur_node):pre_node=cur_nodecur_node=cur_node.nextpre_node.next=head# 寻找切割点cur_node=headfor _ in range(1,l-k): #[0,l-k-1]cur_node=cur_node.next# 确定的新的头部new_head=cur_node.nextcur_node.next=Nonereturn new_head