文章目录
链表理论基础
理论链接
虽然学习过链表,但是还是要记录一下没有学过的细节
链表的种类
链表节点的定义:
接下来说一说链表的定义。
链表节点的定义,很多同学在面试的时候都写不好。
这是因为平时在刷leetcode的时候,链表的节点都默认定义好了,直接用就行了,所以同学们都没有注意到链表的节点是如何定义的。
而在面试的时候,一旦要自己手写链表,就写的错漏百出。
文章里面给了c++的构造函数;python的节点的构造函数应该会简单点;给出我学习的python基础课程里面的链表节点的定义【具体见我的onenote–2202405数据结构笔记本day01线性表】
课程中介绍了链式表的连续和非连续存储
# 创建节点类
class Node:
"""
思路 : *自定义类视为节点类,类中的属性为数据内容
*写一个next属性,用来和下一个 节点建立关系
"""
def __init__(self,val,next = None):
"""
val: 有用数据
next: 下一个节点引用
"""
self.val = val
self.next = next
节点的操作【复习一下】
性能分析
数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。
链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。
具体分析比较可见于python课程的pdf
203.移除链表元素
删除链表元素
如果不使用虚拟头节点,删除列表元素需分情况:
移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点。
所以头结点如何移除呢,其实只要将头结点向后移动一位就可以,这样就从链表中移除了一个头结点。
- 头节点:head指向head.next
- 非头节点:cur的next指向cur.next.next
如果使用虚拟头节点,就不需要分类了,更加直接
思路
方法一:不使用虚拟头节点
分类讨论:
🧨注意一开始是while而不是if
🎃自己写遇到的小问题
- 注意:==这个题目传入的是链表的head节点。。。。表述感觉不清楚
- python里面是none,c++里面是null,,,,
- 注意一开始判断头节点的情况下,需要使用while而不是if;因为可能出现从头开始就需要删除好几次的情况,如下
- 注意:node不为空的判断
# Definition for singly-linked list.
class ListNode(object):
def __init__(self, val=0, next=None):
self.val = val
self.next = next
#传入的是写好的链表的head
class Solution(object):
def removeElements(self, head, val):
"""
:type head: ListNode
:type val: int
:rtype: ListNode
"""
#==这个题目传入的是链表的head。。。。表述感觉不清楚
#首先是头节点
while head!=None and head.val == val: #这里是while,要注意!!
#要判断是否为空指针,否则没有val且考虑到最后一个节点的情况
head = head.next
curr = head
if curr != None:
print(head.val)
while curr != None and curr.next != None :
if curr.next.val == val:#记得是比较val。。。。
curr.next = curr.next.next
else:
curr = curr.next
return head
方法二 使用虚拟头节点
思路:设置一个虚拟头节点,原链表的所有节点可以按照统一的方式进行移除了。
🎀注意:
返回的是虚拟头节点的下一个而不是head,因为原先的head有可能被删掉了
class Solution(object):
def removeElements(self, head, val):
"""
:type head: ListNode
:type val: int
:rtype: ListNode
"""
dammy_head = ListNode(next=head)
curr = dammy_head #注意current指针指向的是虚拟头节点,而不是它的next
while curr.next != None:
if curr.next.val == val:
curr.next = curr.next.next
else:
curr = curr.next
return dammy_head.next
707.设计链表
建议: 这是一道考察 链表综合操作的题目,不算容易,可以练一练 使用虚拟头结点
思路
注意事项:
- 首先要注意题目中说下标从0开始,所以index应该是从0到size-1;
- 需要定义一个额外的curr指针来遍历而不是head,因为head不能动,最后要返回的是head
获取第n个节点的值
首先curr指向dammy-node 的下一个节点;
判断语句写while(n),然后使用n-1来迭代;【🎀Tip:记住,用需要查找的n递减来作为判断循环条件】
while里面是写n还是n+1,n-1,考虑极端情况,如果n为0,while里面应该是不执行,直接返回curr指向的head的值,所以while里面是n;
头部插入节点
注意插入节点的顺序:先将新节点指向dammy-node 的下一个,再将dammy-node的下一个指向新的节点;顺序倒了的话就会把dammy-next的节点丢了
尾部插入节点
第n个节点之前插入节点
curr指针一定要指向第n个节点之前的那个节点,才能操作
curr一开始指向dammy-node节点【每个小任务curr的起始位置都不一样】
删除第n个节点
第n个节点应该是curr的next,这样才可以把第n个节点去除掉;【与尾部插入节点同理】
curr一开始应该指向dammy-node
我的总结
关于curr一开始是指向dammy-node还是dammy-node.next,取决于最后在操作是curr是指在待操作节点上,还是待操作节点前一个上;目前只有两种情况
- 返回index对应的值的时候:curr最后应该指在index节点上,所以初始位置在dammy-node 的下一个节点
- 插入:一定是在要知道待插入位置的前面一个节点,所以curr初始位置在dammy-node上
代码
注意事项
- 需要自己补充ListNode类,此外,MyLinked类需要自己初始化
- 需要注意代码的容错性:也就是要包括输错的情况,例如,delete的index超过链表长度了
- 具体操作为:设置链表长度属性,每次操作之前判断
- del和add的操作记得size的变化
- 记住下面的这个例子:
"""
全部都是我自己写的,==写一天debug好久💀
"""
class Node(object):
def __init__(self,val=0,next=None):
self.val = val
self.next = next
class MyLinkedList(object):
def __init__(self):
# self.head =Node()
self.dammy_head = Node()
self.size = 0
def get(self, index):
"""
:type index: int
:rtype: int
"""
if index < 0 or index >= self.size:
return -1
curr = self.dammy_head.next
while index:#index>0
curr = curr.next
index -= 1
return curr.val
def addAtHead(self, val):
"""
:type val: int
:rtype: None
"""
new_node = Node(val,self.dammy_head.next)
self.dammy_head.next = new_node
#记得size+1
self.size += 1
def addAtTail(self, val):
"""
:type val: int
:rtype: None
"""
curr = self.dammy_head
new_node = Node(val,None)
while curr.next != None:
curr = curr.next
curr.next = new_node
self.size += 1
def addAtIndex(self, index, val):
"""
:type index: int
:type val: int
:rtype: None
注意题目里面说的是插入到index这个节点之前
"""
if index < 0 or index > self.size:
return
curr = self.dammy_head
while index:
curr = curr.next
index -= 1
curr.next = Node(val,curr.next)
self.size += 1
def deleteAtIndex(self, index):
"""
:type index: int
:rtype: None
"""
if index < 0 or index > self.size-1: #注意这里是self.size-1
return
curr = self.dammy_head
while index:
curr = curr.next
index -= 1
curr.next = curr.next.next
self.size -= 1
# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)
206.反转链表
题目链接
讲解链接
是面试里面常常考的第一道基础题:考察数据结构
思路
完全可以新建一个链表,但是面试官会问你满足空间复杂度为O(1)要怎么写
方法一 双指针法
时间复杂度: O(n) 空间复杂度: O(1)
定义一个前面的指针pre来方便反转
- 起始位置:首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。
- 遍历结束是,cur指向了Null了
- 转换的中间过程:先移动pre,再移动curr:顺序不能换
首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。
class Solution(object):
def reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
pre,curr = None,head
while curr != None:
tmp = curr.next
curr.next = pre
pre = curr
curr = tmp
return pre
方法二 递归写法
时间复杂度: O(n), 要递归处理链表的每个节点
空间复杂度: O(n), 递归调用了 n 层栈空间
递归思路与双指针一样,代码更加简洁,但是比较晦涩难懂
- 这里额外调用了一个reverse函数来递归
自己写的错误点
- 调用函数忘了加self.开头,总是报错找不到reverse函数
- 递归忘了写return
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def reverse(self,curr,pre):
if curr == None:#判断已经递归到最后了(结束的判断和双指针法是相同的)
return pre
#如果没有到最后的话就进行反转
tmp = curr.next
curr.next = pre
# pre = curr
# curr = tmp
return self.reverse(tmp,curr)#少了return
def reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
return self.reverse(head,None) #要写self.这都能错
总结
花的时间还是长了,花了四个小时。。。。。希望自己下次记笔记快一点。。。