标明来源,题目及答题灵感均来自:https://leetcode.cn/problems/add-two-numbers/description/
这个题是有关链表的,我先记录一下知识点,不然我不会做。
一、什么是链表
链表的通俗理解:链表是一种非连续、非顺序的线性顺序结构,和我们熟悉的数组是亲兄弟,都是存一组有序的数据,但是存储方式完全不同。
数组:所有元素在内存中是连续挨在一起的,比如[1,2,3,4]内存地址是连续的;
链表:所有元素(节点)在内存中是分散存储的,每个节点之间靠指针相互连接,像一串珍珠项链,珍珠=节点,绳子=指针。
链表的最小单位:节点(Node),节点是链表的核心,链表的所有数据都储存在节点里。
python
# 单向链表的节点定义
class ListNode:
def __init__(self, val=0, next=None):
self.val = val # 存放的【数据/值】,比如数字 2、5、9
self.next = next # 【指针】,存放下一个节点的地址,指向链表的下一个节点
节点的两个核心属性:
self.val是当前节点存的实际数据,可以是数字、字符等;
self.next是当前节点的后继指针,指向下一个节点的内存地址,通过它能找到下一个元素,如果next=None,那么这个节点就是最后一个节点。
二、链表的核心分类
1、单链表
每个节点只有一个指针next,只能从头节点向尾结点的方向遍历,是所有链表的基础。
2、虚拟头节点
为什么需要虚拟头节点,因为单链表的头节点是入口,如果直接操作头节点,会有两个致命问题:
(1)链表为空时,头节点不存在,操作会报错;(2)拼接新链表时,需要单独处理第一个节点,逻辑变复杂。
虚拟头节点的定义:创建一个空值的节点,让这个空节点的next指向链表的真实头节点,这个空节点就是虚拟头节点。
核心特点:(1)只是一个占位符,不储存有效数据;(2)保持位置不动;(3)用一个移动指针绑定虚拟头节点,专门负责往后拼接或遍历;(4)最终结果取虚拟头节点.next,就是正常的链表头节点。
python
dummy = ListNode() # 创建虚拟头节点,值默认0
curr = dummy # 移动指针绑定虚拟头节点
# ...中间拼接节点的逻辑...
return dummy.next # 返回真实头节点
(三)单链表的6个核心基本操作
1、遍历列表
从头节点开始,一直往后走,直到指针为None
python
def traverse(head):
curr = head # 定义指针,从头节点开始
while curr: # 只要指针不为空,就继续遍历
print(curr.val) # 访问当前节点的值
curr = curr.next # 指针后移,关键步骤!
2、获取链表的长度(借助计数器)
python
def get_length(head):
count = 0
curr = head
while curr:
count +=1
curr = curr.next
return count
3、查找链表中是否存在某个值
python
def find_target(head, target):
curr = head
while curr:
if curr.val == target:
return True
curr = curr.next
return False
4、在链表尾部新增节点(让最后一个节点的next指向新节点)
python
def add_at_tail(head, val):
new_node = ListNode(val) # 创建新节点
if not head: # 边界:链表为空时,新节点就是头节点
return new_node
curr = head
while curr.next: # 遍历到最后一个节点(curr.next=None时停止)
curr = curr.next
curr.next = new_node # 拼接新节点
return head
5、删除链表中指定值的节点(就是直接跳过)
python
def remove_node(head, val):
dummy = ListNode() # 用虚拟头节点,避免头节点是目标节点的边界问题
dummy.next = head
curr = dummy
while curr.next:
if curr.next.val == val:
curr.next = curr.next.next # 跳过目标节点,完成删除
break
curr = curr.next
return dummy.next
6、反转链表
python
def reverse_list(head):
pre = None # 前驱节点初始为空
curr = head # 当前节点从头节点开始
while curr:
temp = curr.next # 临时保存下一个节点,防止丢失
curr.next = pre # 反转指针:当前节点指向前驱节点
pre = curr # 前驱节点后移
curr = temp # 当前节点后移
return pre # 最终pre就是反转后的头节点
好了,接下来做一个例题:
题目:给你两个非空的链表,表示两个非负的整数。它们每位数字都是逆序存储的,并且每位数字只能储存一位数字。请你将两个数相加,并以相同形式返回一个和的链表。你可以假设除了数字0之外,它们不会以0开头。
python
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
# 当前指针,结果链表:创建空的虚拟头节点,双指针指向它
result = curr = ListNode()
# 进位项:加法满10进1,初始值0
remainder = 0
# 循环条件:只要l1没遍历完 或者 l2没遍历完,就继续相加
while l1 or l2 :
# 取l1当前值,空则补0
x = l1.val if l1 else 0
# 取l2当前值,空则补0
y = l2.val if l2 else 0
# 计算当前位总和 = l1值 + l2值 + 上一位的进位
total = x + y + remainder
# 当前位存入【余数】,满10只留个位
curr.next = ListNode(total%10)
# 更新进位:满10进1,不满则为0
remainder = total//10
# 防止空链表调用.next报错:非空才后移指针
if l1 : l1 = l1.next
if l2 : l2 = l2.next
# 结果链表指针后移,准备存下一位计算结果
curr = curr.next
# 循环结束后如果还有进位,单独新增一个节点存进位1
if remainder : curr.next = ListNode(remainder)
# 返回真正的结果链表头节点
return result.next
ok,解放,明天继续。