之前在leetcode上面找了一些链表的题做完了,但是隔一段时间感觉又忘得差不多了。
果然学习还是需要不停的输出和回顾。
数据结构——链表
链表中的每个元素实际上是一个单独的对象,而所有对象都通过每个元素中的引用字段链接在一起。
单链表
双链表
单链表中的每个结点不仅包含值,还包含链接到下一个结点的引用字段
。通过这种方式,单链表将所有结点按顺序组织起来。
C++中节点的典型定义:
struct SinglyListNode {
int val;
SinglyListNode *next;
SinglyListNode(int x) : val(x), next(NULL) {}
};
python中节点定义
class Node:
def __init__(self, data, next=None):
self.data = data
self.next = next
与数组不同,我们无法在常量时间内访问单链表中的随机元素。 如果我们想要获得第 i 个元素,我们必须从头结点逐个遍历。 我们按索引来访问元素平均要花费 O(N) 时间,其中 N 是链表的长度。
设计链表
首先需要设计节点
class Node:
def __init__(self,val=0,next=None):
self.val=val
self.next=next
然后设计链表
重要的是链表的头是哪个节点,以及链表的的尺寸(判断索引是否超出了范围)
class mylinkednote:
def __init__(self,head=None,size=0)
self.head=None
self.size=size
def get(self,index:int)->int:
if index<0 or index>size-1:
return -1
cur=self.head
for i in range(index):
cur=cur.next
return cur.val
def addathead(self,val:int)->None:
self.head=Node(val,next=self.head)
self.size+=1
def addattail(self,val:int)->None:
if size=0:
self.head=Node(val)
else:
cur=self.head
for i in range(size):
cur=cur.next
cur.next=Node(val)
self.size+=1
def addatindex(self,val:int,index:int)->None:
if index<0 or index>size-1:
return
elif index==0:
self.addathead(val)
else:
cur=self.head
for i in range(index):
cur=cur.next
cur.next=Node(val,cur.next)
self.size+=1
def deleteatindex(self,index:val)->None:
if index<0 or index>size-1:
return
elif index==0:
self.head=self.head.next
else:
cur=self.head
for i in range(index):
cur=cur.next
cur.next=cur.next.next
self.size-=1
1.检查是否有环形链表
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
思路:如果有环,快节点和慢节点一起出发一定会遇上
注意:要保证fast还有下两个节点
def hascycle(self,head)->bool:
if not head or not head.next:
return False
fast=head.next
slow=head
while slow!=fast:
if not fast.next or not fast:
return False
fast=fast.next.next
slow=slow.next
return True
2.给出环形链表开始循环的地方
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
思路:在他们第一次相遇之后,把其中一个节点放回出发点,他们再次相遇的地方就是循环开始点
def detectcycle(self,head)->:
#先排除异常
if not head or not head.next:
return
#寻找第一次相遇点
fast,slow=head
while True:
fast=fast.next.next
slow=slow.next
if not fast or fast.next:
return
if fast==slow:
break
fast=head
while fast!=slow:
fast,slow=fast.next,slow.next
return fast
3.相交链表
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
思路:如果两条链相交的话,在一个节点走完一条链之后接着走另外一条链他们一定会相遇
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
pA, pB = headA, headB
while pA != pB:
# 如果节点pA到达链表末尾,则将其移动到链表headB的头节点
# 否则,移动到下一个节点
pA = pA.next if pA else headB
# 如果节点pB到达链表末尾,则将其移动到链表headA的头节点
# 否则,移动到下一个节点
pB = pB.next if pB else headA
# 返回相交节点,如果不存在相交节点,最终pA和pB都会等于None
return pA
4.删除链表的第倒数第n个节点
思路:双指针法
陷阱:倒数第n个节点可能是head,因此虚拟一个dummy节点,让slow从dummy出发
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
dummy = ListNode(0, head)
first = head
second = dummy
for i in range(n):
first = first.next
while first:
first = first.next
second = second.next
second.next = second.next.next
return dummy.next
双指针问题C++模板
// Initialize slow & fast pointers
ListNode* slow = head;
ListNode* fast = head;
/**
* Change this condition to fit specific problem.
* Attention: remember to avoid null-pointer error
**/
while (slow && fast && fast->next) {
slow = slow->next; // move slow pointer one step each time
fast = fast->next->next; // move fast pointer two steps each time
if (slow == fast) { // change this condition to fit specific problem
return true;
}
}
return false; // change return value to fit specific problem
5.反转链表
这里涉及到递归,先mark一下
6.删除链表元素
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
注意:
像这种头节点有可能被删掉的情况要创建虚拟头节点简化边界处理
dummy=Node(next=head)
7.奇偶链表
给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。
第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。
请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。
你必须在 O(1) 的额外空间复杂度和 O(n) 的时间复杂度下解决这个问题。
思路,奇偶头节点分别从第一个和第二个节点出发,每次移动两个节点,最后连接上。
class Solution:
def oddEvenList(self, head: ListNode) -> ListNode:
if not head:
return head
evenHead = head.next
odd, even = head, evenHead
while even and even.next:
odd.next = even.next
odd = odd.next
even.next = odd.next
even = even.next
odd.next = evenHead
return head
8.回文链表
给你一个单链表的头节点 head
,请你判断该链表是否为回文链表。如果是,返回 true
;否则,返回 false
。
思路:把前一半链表翻转过来和后一半链表对比,怎么寻找到一半呢,就是用快慢指针法,快的一次走俩,慢的一次走一个
涉及到递推,先空着
9.双链表
与单链表的区别:单链表只能从前向后,双链表既可以向前,也可以向后
写法上的区别:
1️⃣节点定义里除了定义val,next,还有prev
2️⃣链表初始化里除了head,size,还有tail,而且还要把head和tail联系起来
3️⃣插入和删除的时候看离前边近还是后边近决定往前推还是往后推
class Listnode:
def __init__(self,val):
self.val=val
self.next=None
self.prev=None
class MyLinkedList:
def __init__(self):
self.size=0
self.head,self.tail=Listnode(0),Listnode(0)
self.head.next=self.tail
self.tail.prev=self.head
def get(self, index: int) -> int:
if index<0 or index>=self.size:
return-1
elif index <= self.size // 2:
cur=self.head
for _ in range(index+1):
cur=cur.next
else:
cur=self.tail
for _ in range (self.size-index):
cur=cur.prev
return cur.val
def addAtHead(self, val: int) -> None:
self.addAtIndex(0,val)
def addAtTail(self, val: int) -> None:
self.addAtIndex(self.size,val)
def addAtIndex(self, index: int, val: int) -> None:
if index>self.size:
return
if index <= self.size // 2:
pred=self.head
for _ in range(index):
pred=pred.next
succ=pred.next
else:
succ=self.tail
for _ in range(self.size-index):
succ=succ.prev
pred=succ.prev
self.size+=1
to_add=Listnode(val)
to_add.prev=pred
to_add.next=succ
pred.next=to_add
succ.prev=to_add
def deleteAtIndex(self, index: int) -> None:
if index < 0 or index >= self.size:
return
if index < self.size - index:
pred = self.head
for _ in range(index):
pred = pred.next
succ = pred.next.next
else:
succ = self.tail
for _ in range(self.size - index - 1):
succ = succ.prev
pred = succ.prev.prev
self.size -= 1
pred.next = succ
succ.prev = pred
还有一些更难的题,等到日后再解决