代码随想录第三天链表part01|203.移除链表元素、707.设计链表、206.反转链表
链表理论基础
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链表的结构和类型
链表的头节点(head)(单链表👇)
双链表:每一个节点有两个指针域,既可以向前查询也可以向后查询
循环链表:链表首位相连
链表的存储方式
数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。链表是通过指针域的指针链接在内存中各个节点。
链表的定义
C/C++中定义链表节点的方式
:
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
// 初始化节点
ListNode* head = new ListNode(5);
// 也可使用默认构造函数初始化节点
ListNode* head = new ListNode();
head->val = 5;
python中定义链表节点的方式
:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
链表操作及性能分析
链表操作:删除节点、添加节点
性能分析:与数组的性能对比
插入删除时间复杂度 | 查询时间复杂度 | |
---|---|---|
数组 | O(n) (将所有元素进行修改) | O(1) (可通过下标索引) |
链表 | O(1) (直接修改前一个节点和该节点的指向 | O(n) (需要遍历查询) |
(删除节点 C++需要手动释放内存,java,python有内存回收机制) |
203.移除链表元素
思路:
移除一般节点较好实现,重点考虑移除头节点:head = head.next,实现代码时①判断该节点是不是头节点,②或者使用虚拟头结点可以统一进行删除dummy head
1)原链表删除元素:分情况处理,是头节点和不是头节点
2)使用虚拟头节点:统一处理,一个循环就可以了
1)原链表删除元素
判断要删除的元素是不是头结点
注意: 代码中while 不是 if;新定义一个变量cur指向head进行删除节点,不要对head进行变动
伪代码
:
while(head!=NULL && haed->val == target) # 删除头节点的情况
head = head->next
cur = head
while(cur != NULL && cur->next != NULL)
if(cur->next->val == target)
cut->next = cur->next->next
else
cur = cur->next
return head
python代码
:
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
while head and head.val == val:
head = head.next
cur = head
while cur and cur.next:
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return head
2)使用虚拟头节点
思路
:
在头节点前添加一个dummyhead,之后可以用while循环统一进行处理,最后返回dummyhead.next
伪代码
:
dummyhead = new()
dummyhead->next = head
cur = dummyhead
while(cur != NULL && cur->next != NULL)
if(cur->next->val == target)
cur->next = cur->next->next
else
cur = cur->next
return dummyhead.next
python代码
dummyhead = ListNode() dummuhead.next = head
可简化为:dummyhead = ListNode(next = head)
while cur and cur.next
可简化为:while cur.next
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
dummyhead = ListNode(next = head)
cur = dummyhead
while cur.next:
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return dummyhead.next
时间复杂度是O(n)
707.设计链表
1)获取第n个节点的值
使用虚拟头结点的方法
注意: 边界考虑,while循环中是n,有时候需要改为n-1或者n+1
【这里假设链表中的所有节点下标从 0 开始。】
伪代码
dummyhead = ListNode(next=head)
if index < 0 or index >= self.size:
return
curr = dummyhead.next
while n:
curr = curr.next
n-=1
return curr.val
2)头部插入节点
这里经常出现的错误:顺序问题: 应该先让newnode的下一个节点指向dummyhead的下一个节点,再改变dummyhead下一个的指向,即newnode.next = dummyhead.next在dummyhead.next = newnode之前执行
伪代码
dummyhead = ListNode(next=head)
newnode.next = dummyhead.next
dummyhead.next = newnode
size+=1
3)尾部插入节点
当curr.next为空时,说明已经指向了尾部
注意: curr = dummyhead 而不是 curr = dummyhead.next,因为while中判断了下一个
伪代码
curr = dummyhead
while curr.next != NULL:
curr = curr.next
curr.next = newnode
4)第n个节点前插入节点
curr要指向第n-1个节点,才能在第n个节点前插入,所以curr = dummyhead而不是curr = dummyhead.next
注意: 边界考虑,while循环中是n,当n取0时成立
伪代码
curr = dummyhead
while n:
curr = curr.next
n-=1
newnode.next = curr.next
curr.next = newnode.next
5)删除第n个节点
要对n的合法性进行判断
curr要指向第n-1个节点,才能删掉第n个节点
伪代码
if n<0 or n>size:
return
curr = dummyhead
while n:
curr = curr.next
n-=1
curr.next = curr.next.next
size-=1
注意 ①class类的写法,代码中没有dummyhead.next = head,因为在初始化类时先定义了dummyhead,所以它是第一个节点
②对n的合法性进行判断 index<0 or index >= self.size: 不满足情况 ≥,因为链表中的所有节点下标从 0 开始,size=最后一个节点的下标+1
③curr = self.dummyhead.next 还是 curr = self.dummyhead
if index < 0 or index >= self.size: 还是 if index < 0 or index > self.size:
完整python代码
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.dummyhead = ListNode()
self.size = 0
def get(self, index: int) -> int:
if index < 0 or index >= self.size:
return -1
curr = self.dummyhead.next
while index:
curr = curr.next
index -= 1
return curr.val
def addAtHead(self, val: int) -> None:
newnode = ListNode(val=val, next=self.dummyhead.next)
self.dummyhead.next = newnode
self.size += 1
def addAtTail(self, val: int) -> None:
curr = self.dummyhead
while curr.next:
curr = curr.next
newnode = ListNode(val=val)
curr.next = newnode
self.size += 1
def addAtIndex(self, index: int, val: int) -> None:
if index < 0 or index > self.size:
return
curr = self.dummyhead
while index:
curr = curr.next
index -= 1
newnode = ListNode(val, curr.next)
curr.next = newnode
self.size += 1
def deleteAtIndex(self, index: int) -> None:
if index < 0 or index >= self.size:
return
curr = self.dummyhead
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.反转链表【面试易考题】
双指针、递归两种解法
1)双指针解法
思路
pre curr两个指针,将这两个指针改变方向,就是一步一步进行反转,这里需要额外定义一个temp指针,保存下一个
伪代码
curr = head
pre = NULL
while curr:
temp = curr.next
curr.next = pre
pre = curr # 先移动pre再移动curr
curr = temp
return pre
python代码
注意while中的边界条件需要仔细考虑,while curr而不是while curr.next,因为最后一个是空指针,temp = curr.next
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
curr = head
pre = None
while curr:
temp = curr.next
curr.next = pre
pre = curr
curr = temp
return pre
# 更简洁版本
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode(float("inf"))
while head:
dummy.next, head.next, head = head, dummy.next, head.next
return dummy.next
时间复杂度:O(n)
空间复杂度:O(1)
2)递归解法
思路
pre curr两个指针,将这两个指针改变方向,就是一步一步进行反转,这里需要额外定义一个temp指针,保存下一个
伪代码
reverse(cur, pre){
if(curr == NULL)
return pre
temp = curr.next
curr.next = pre
reverse(temp, cur)
}
reverselist(head){
return reverse(head,NULL)
}
python代码
class Solution:
def reverse(self, curr:ListNode, pre:ListNode):
if curr == None:
return pre
temp = curr.next
curr.next = pre
return self.reverse(temp, curr)
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
return self.reverse(head, None)
时间复杂度:O(n)
空间复杂度:O(n)