01 链表知识点
1.1 基础知识
-
线性表:零个或多个相同类型的数据元素的有限序列,一对一关系,所含数据元素个数n称为线性表的长度。
-
线性表有两种物理结构(存储结构):顺序存储、链式存储。
- 线性表的顺序存储结构:用一段地址连续的存储单元依次存储线性表的数据元素。
- 顺序存储结构三个属性:起始位置;线性表最大存储容量(数组长度MaxSize);线性表当前长度(length)。
- 线性表的链式存储结构:用一组任意的存储单元存储线性表的数据元素,为了表示数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对ai来说,除了存储本身的信息之外,还要存储一个指示其直接后继元素存储位置的信息。
- 存储数据元素信息的域叫数据域;存储直接后继元素位置的域叫指针域,指针域中存储的信息叫指针或链。
- p结点的数域可以表示为p->data,存放的是p结点的数据元素;指针域可以表示为p->next,存放的是p结点直接后继结点的指针;*L表示链表L的头结点
- 数据域和指针域组成的数据元素ai的存储映像叫结点(Node),n个结点链结成一个链表,即为线性表的链式存储结构。
- 存储数据元素信息的域叫数据域;存储直接后继元素位置的域叫指针域,指针域中存储的信息叫指针或链。
- 线性表的顺序存储结构:用一段地址连续的存储单元依次存储线性表的数据元素。
1.2 顺序存储结构 v.s. 单链表结构
顺序存储结构:
- 查找O(1),插入删除O(n)
- 需预分配存储空间,分大了浪费,分小了易溢出
- 若线性表需频繁查找,很少插入删除,用顺序结构
单链表结构:
- 查找O(n),插入删除在找到插入删除结点指针后O(1)
- 不需预分配存储空间,元素个数不受限
- 若线性表需频繁插入删除,用单链表结构
- 线性表元素个数未知或变化较大时,用单链表
链表只能顺序访问元素,要读取第十个元素,必须先读取前九个元素;顺序存储(数组)可以随机访问元素。
1.3 单链表
若链表中的每个结点只包含一个指针域,叫单链表。
单链表插入删除数据的时间复杂度也为O(n),但对于一次插入删除多个元素也是O(n)(顺序存储每次都是O(n)),因此对于插入删除较频繁的应用,单链表效率更高。
1.4 静态链表
用数组代替指针描述链表,用数组描述的链表叫静态链表。静态链表是给没有指针的高级语言设计的实现单链表能力的方法。
- 优点:插入删除操作时,只需修改游标不需移动元素,改进了顺序存储结构的插入删除操作需移动大量元素的缺点;
- 缺点:没有解决连续存储分配带来的表长难以确定的问题;失去了顺序存储结构随机存取的特性。
1.5 循环链表
将单链表中终端结点的指针端由空指针改为指向头结点,使整个单链表形成一个环,这种头尾相接的单链表叫单循环链表,简称循环链表。
循环链表解决了如何从任一结点出发访问到链表的全部结点的问题。
1.6 双向链表
双向链表是在单链表各结点中,再设置一个指向其前驱结点的指针域(前驱指针*prioir,后继指针*next),双向链表也可以是循环表。
双向链表为某结点的前后结点操作带来方便,可以提升算法的时间性能,但由于每个结点都要记录两份指针,占用了空间,相当于用空间换时间。
注意,双向链表在插入删除时,需要更改两个指针变量,注意更改顺序。
- 插入:1.搞定插入结点的前驱后继 2.搞定后结点的前驱 3.搞定前结点的后继。
- 删除:1.把待删除结点的后结点赋值给前结点的后继 2.把待删除结点的前结点赋值给后结点的前驱。
02 算法题
2.1 两数相加
链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xvw73v/
题目:给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
解题思路: 使用两个指针,开始的时候分别指向两个链表的头节点。用他们相加和的个位数构造一个新的节点(因为一个节点只能存储一位数字),这两个指针每次运算完之后都要同时往后移一步,然后还要使用一个变量carry表示是否有进位。两个指针只要有一个不为空,或者carry不为0就继续循环。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
"""
使用两个指针,开始的时候分别指向两个链表的头节点。用他们相加和的个位数构造一个新的节点(因为一个节点只能存储一位数字),这两个指针每次运算完之后都要同时往后移一步,然后还要使用一个变量carry表示是否有进位。两个指针只要有一个不为空,或者carry不为0就继续循环
"""
dummyNode=ListNode() # 定义一个哑节点,指向新链表头结点
preNode=dummyNode # 定义当前节点的前一个节点
carry=0 # 定义进位变量
while l1 or l2 or carry: #当l1不为空或l2不为空或carry不为零时
sum=carry # 定义两数各位之和变量
# 链表1求和操作
if l1:
sum+=l1.val
l1=l1.next
# 链表2求和操作
if l2:
sum+=l2.val
l2=l2.next
# 创建新节点,preNode的next指针指向新节点
# 因为链表节点只能存储一个数字,对sum取余
preNode.next=ListNode(sum%10)
carry=sum//10 # 进位变量
preNode=preNode.next # preNode后移一位
#返回dummyNode的next指针指向的值,dummyNode指向新链表头结点
return dummyNode.next
2.2 奇偶链表
链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xvdwtj/
题目:给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。
第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。
请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。
你必须在 O(1) 的额外空间复杂度和 O(n) 的时间复杂度下解决这个问题。
解题思路:把奇数节点串到一起,我们称之为奇数链表,偶数节点串到一起,我们称之为偶数链表。最后再把偶数链表挂到奇数链表的后面即可。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def oddEvenList(self, head: Optional[ListNode]) -> Optional[ListNode]:
# 链表长度小于等于1则返回原链表
if head==None or head.next==None:
return head
oddHead=head # 奇数链表头节点
oddCur=oddHead # 奇数链表当前节点(指针)
evenHead=head.next # 偶数链表头节点
evenCur=evenHead # 偶数链表当前节点(指针)
while evenCur and evenCur.next: # 当链表未被遍历完
oddCur.next=oddCur.next.next # 奇数节点串一起
evenCur.next=evenCur.next.next #偶数节点串一起
# 奇偶指针后移一位
oddCur=oddCur.next
evenCur=evenCur.next
# 奇数链表与偶数链表合并,奇数链表末尾指向偶数链表头部
oddCur.next=evenHead
return oddHead
2.3 相交链表
链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xv02ut/
题目:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
解题思路:题目意思是判断两个链表是否相交,如果相交就返回他们的相交的交点,如果不相交就返回null。链表相交指的是指针指向同一位置。
- 思路1:先把第一个链表的节点全部存放到集合set中,然后遍历第二个链表的每一个节点,判断在集合set中是否存在,如果存在就直接返回这个存在的结点。如果遍历完了,在集合set中还没找到,说明他们没有相交
- 思路2:先统计两个链表的长度,如果两个链表的长度不一样,就让链表长的先走,直到两个链表长度一样,这个时候两个链表再同时每次往后移一步,看节点是否一样,如果有相等的,说明这个相等的节点就是两链表的交点,否则如果走完了还没有找到相等的节点,说明他们没有交点,直接返回null即可。以下实现代码采用思路二
- 思路3:双指针见题目链接
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
'''先统计两个链表的长度,如果两个链表的长度不一样,就让链表长的先走,直到两个链表长度一样,这个时候两个链表再同时每次往后移一步,看节点是否一样,如果有相等的,说明这个相等的节点就是两链表的交点,否则如果走完了还没有找到相等的节点,说明他们没有交点,直接返回null即可'''
listA=list()
listB=list()
# 统计链表长度
ahead=headA
bhead=headB # 定义一个新的链表用于统计原链表长度,否则原链表走到末端后为None
while ahead:
listA.append(ahead.val)
ahead=ahead.next
while bhead:
listB.append(bhead.val)
bhead=bhead.next
lenA=len(listA)
lenB=len(listB)
# 较长的链表先走几步直到两链表等长
while lenA!=lenB:
if lenA>lenB:
headA=headA.next
lenA-=1
else:
headB=headB.next
lenB-=1
# 两链表等长后开始比较,走到最后若不相交则headA为空,若相交则跳出循环,headA为交点
while headA!=headB:
headA=headA.next
headB=headB.next
return headA