本文声明:本文参考【代码随想录】的文章,思路以及代码。基于他的文章(希望大家直接去看原作者的优秀文章【代码随想录】,这篇博客仅为了记录自己的学习过程),形成了自己的,希望总结成自己的经验与知识,于是发表了这篇博客,如有不妥的地方欢迎大家指正。
链表的理论基础
1,链表的定义
链表是通过指针串联起来的线性结构,每一个结点分为两个部分:数据域和指针域(用于指向下一个结点的指针)
链表的定义定义代码如下:
// 单链表 C++
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
#python
class ListNode:
def __init__(self, val, next=None):
self.val = val
self.next = next
2,链表的分类
角度一:有虚拟头结点 和 无虚拟头结点。 Tips:很多时候Leetcode都是提供无虚拟头结点的链表,此时可以考虑自己创造一个虚拟头结点(利用构造函数创建)
角度二:单连表,双链表,循环链表, 循环双链表等 (理论知识扎实即可,一般用不到)、
3,链表特性:
存储方式:数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。链表是通过指针域的指针链接在内存中各个节点。所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
4,链表与数组的差异!!!
待补充完善
链表的解题模板总结
主要还是的区分开数组与链表两种存储结构以及差异。!!!易错点创建指针指向某一个结点,这里仅多了一个指向该结点的指针,没有多出一个结点哦(自己做题迷糊了两次)。解题模板与专题一大差不大,主要是熟悉链表这一存储方法和数据类型的操作方法,这部分专题多练,一定得多多多练习。
1,设计链表
本题可多刷。设计并实现自己的链表:(很多时候会做题不行,基础的扎实)。实现 MyLinkedList
类:
MyLinkedList()
初始化MyLinkedList
对象。int get(int index)
获取链表中下标为index
的节点的值。如果下标无效,则返回-1
。void addAtHead(int val)
将一个值为val
的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。void addAtTail(int val)
将一个值为val
的节点追加到链表中作为链表的最后一个元素。void addAtIndex(int index, int val)
将一个值为val
的节点插入到链表中下标为index
的节点之前。如果index
等于链表的长度,那么该节点会被追加到链表的末尾。如果index
比长度更大,该节点将 不会插入 到链表中。void deleteAtIndex(int index)
如果下标有效,则删除链表中下标为index
的节点。
(版本一)单链表法
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.dummy_head = ListNode()
self.size = 0
def get(self, index: int) -> int:
if index < 0 or index >= self.size:
return -1
current = self.dummy_head.next
for i in range(index):
current = current.next
return current.val
def addAtHead(self, val: int) -> None:
self.dummy_head.next = ListNode(val, self.dummy_head.next)
self.size += 1
def addAtTail(self, val: int) -> None:
current = self.dummy_head
while current.next:
current = current.next
current.next = ListNode(val)
self.size += 1
def addAtIndex(self, index: int, val: int) -> None:
if index < 0 or index > self.size:
return
current = self.dummy_head
for i in range(index):
current = current.next
current.next = ListNode(val, current.next)
self.size += 1
def deleteAtIndex(self, index: int) -> None:
if index < 0 or index >= self.size:
return
current = self.dummy_head
for i in range(index):
current = current.next
current.next = current.next.next
self.size -= 1
2,翻转链表
题目:Leetcode 206翻转链表https://leetcode.cn/problems/reverse-linked-list/description/ 额外补充:建立单连的过程可分为:头插法和尾插法。每次把新节点插入到头节点之后,创建的单链表和数据输入顺序相反。尾插法:每次把新节点链接到链表的尾部,因此需要一个尾指针永远指向链表的尾节点。
思路:有两种方法可实现:
法1断链法:遍历一遍单连表,只需要改变链表结点的next指针的指向,直接将链表反转 ,而不用重新定义一个新的链表。
法2头插法:将链表的每个结点从头开始遍历,对于遍历结点以头插法的形式加以虚拟头结点后面(因为头插法会实现倒序)
class Solution(object):
def reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
#反转链表 两种方法:断链法 和 头插法
if head == None or head.next == None:
return head
#尾部头插法
newhead = head
while newhead.next:
newhead = newhead.next
p = head
while p != None and p != newhead:
r = p.next #防止断链
p.next = newhead.next
newhead.next = p
p = r
return newhead
"""
#断链法 正确写法
pre = None
p = head
while p:
r = p.next #防止断链
p.next = pre
pre = p
p = r
return pre
自己写的有问题:
pre = head
p = head
while p.next:
r = p.next
r.next = pre #这里错啦 会导致转圈圈
pre = r
p = p.next #这两处配合,闭环啦
return pre
混淆了指针与节点:(重大经验教训)
认为r = p.next 此时就会右两个节点,其实不然只是加了一个指向那个节点的指针r
除非你创建一个新结点是吧才会都一个节点node
"""
3,两两交换链表的结点
题目:Leetcode 24 两两交换链表中的结点https://leetcode.cn/problems/swap-nodes-in-pairs/description/ 没什么思路,这个题直接硬码。自己对链表掌握还有待提升,这个题写的很复杂
注意如何创造头结点:调用构造方法(很多时候添加一个虚拟头结点,方便一万倍)
dummy_head = ListNode(next = head)
class Solution(object):
def swapPairs(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
#大佬写的,自己没写出来(基础语法不过关怕)
dummyHead = ListNode(next = head)
current = dummyHead
# 必须有cur的下一个和下下个才能交换,否则说明已经交换结束了
#若为单数, 5直接跟在3后面即可,无需改变:2 1 4 3 5
while current.next and current.next.next:
temp = current.next
temp1 = current.next.next.next #防止断链
current.next = current.next.next
current.next.next = temp #上一行推进了一个next
temp.next = temp1
current = current.next.next
return dummyHead.next #自己写的要是有一个虚拟头结点,能省好几个指针
"""
自己写的
O(n) , S(n)有点大,但都是系数大,量级还好
#先拆成两个表 再合并
#本题没得技巧,全是感情
if head == None or head.next == None:
return head
head1 = head
head2 = head.next
r1 = head1
r2 = head2
p = head2.next
flag = 1
while p:
if flag == 1:
r1.next = p
r1 = p
flag = 0
elif flag == 0:
r2.next = p
r2 = p
flag = 1
p = p.next
if r2.next :
#单数节点
r2.next = None
else:
#双数节点 最后3还是指向4的
r1.next = None #防止乱串啦
head = head2
while True:
if head2:
r = head2.next
head2.next = head1
head2 = r
else:
break
if head1:
r = head1.next
head1.next = head2
pre = head1
head1 = r
else:
break
#若为双数可以直接while结束就返回,若为单数还需要如下:
if head2 == None:
pre.next = head1
return head
"""
4,删除链表的倒数第N个结点
题目:Leetcode 19删除链表倒数第N个1结点https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/ 思路分析:该题为典型的快慢指针(双指针法):fast指针先走N步,之后fast指针和slow指针同步移动,直至fast遍历结束。Tip:y有些小细节需要注意,是否添加头结点,倒数第N个结点(slow与fast之间的距离),遍历结束的条件(fast == None, 还是 fast.next == None),以及删除diN个结点则slow最后应该停留正倒数第N+1个结点。
class Solution(object):
def removeNthFromEnd(self, head, n):
"""
:type head: ListNode
:type n: int
:rtype: ListNode
"""
#双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,
#直到fast指向链表末尾。删掉slow所指向的节点就可以了。
#自己写的,第二版:带虚拟头结点
dummyHead = ListNode(next = head)
fast = dummyHead
slow = dummyHead
while fast.next and n > 0: #可以n >= 0 :让fast走n+1步, 下面的同步一点,while fast:
fast = fast.next
n -= 1
if n > 0: return None #n > len(head)
else:
while fast.next:
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return dummyHead.next
5,环形链表
题目:Leetcode 142 环形链表2 中等难度https://leetcode.cn/problems/linked-list-cycle-ii/description/ 这个题正常做好难,到目前为止自己还没有理解清楚。但是用哈希法来说就几行代码,之间附代码啦:
class Solution(object):
def detectCycle(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
"""
法一:从思路入手
主要考察两知识点:
判断链表是否环
如果有环,如何找到这个环的入口
问题一:判断链表是否有环:
使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,
fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,
说明这个链表有环。 (一定可以相遇, 不会存在错过的情况)
问题er:有环,如何找到这个环的入口
没看懂......
"""
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# If there is a cycle, the slow and fast pointers will eventually meet
if slow == fast:
# Move one of the pointers back to the start of the list
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
# If there is no cycle, return None
return None
"""
#哈希表法
if head == None :
return None
hasNode = set()
p = head
while p :
if p in hasNode:
return p
hasNode.add(p)
p = p.next
return None
"""