代码随想录算法训练营第三天 | 203.移除链表元素、707.设计链表、206.反转链表
今日学习的文章链接和视频链接
参考代码随想录
自己看到题目的第一想法
今天的题目之前都刷过,所以以为都可以顺利写出来啊ac
自己实现过程中遇到哪些困难
203.移除链表元素中记得使用虚拟头节点,但是以为删除节点后cur要继续向后遍历;707.设计链表除了addAtIndex()都写出来了,addAtIndex()中判断index无效条件中以为index = size无效,其实index = size是有效的,因为插入节点后size会加一;206.反转链表一次过
今日收获,记录一下自己的学习时长
- 在做707.设计链表学会使用leetcode测试用例来debug
- 应打卡6月30日,7月1日补打卡,学习时长2hr
- 6月30日学习了字符串*2,剑指offer05. 替换空格和0151. 反转字符串里的单词 Reverse Word in a String
0203. 移除链表元素 Remove Linked List Elements
1. 题目描述
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点。
示例1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
示例2:
输入:head = [], val = 1
输出:[]
示例3:
输入:head = [7,7,7,7], val = 7
输出:[]
2. 解题思路
- 从头节点
head
开始遍历链表,如果满足条件node.next.val == val
,移除下一个节点就相当于把node.next
指向node.next.next
。 - 对头节点的处理:
- 直接对 原来链表 进行删除处理,如果头节点
head
满足删除条件,需要单独删除头节点,具体操作为把head
指针向后移动一位,并删除原本的头节点 - 设置 虚拟节点: 设置一个虚拟节点
dummyNode
,让虚拟节点指向原本的头节点,即dummyNode.next = head
,然后遍历链表,此时如果头节点符合条件,就可以按原来的逻辑删除,最后返回的是dummyNode.next
- 直接对 原来链表 进行删除处理,如果头节点
2.1. 直接遍历
- 删除头节点操作时,要用循环
while
而不用条件判断if
,因为如果用条件判断,只会判断一次,如果出现list = [7,7,7,7]
删除7
时,只会删除一次 - 删除其他节点时,循环判断条件需要有
cur != None
,因为需要考虑链表为空即list = []
的情况,如果用虚拟节点法则不用,因为开始循环时cur = dummy_node
不为空。 - 时间复杂度:
O(n)
- 空间复杂度:
O(1)
# 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 removeElements(self, head, val):
"""
:type head: ListNode
:type val: int
:rtype: ListNode
"""
# 删除头节点
while head != None and head.val == val: # 注意用while循环而不是if
head = head.next
# 删除其他节点
cur = head
while cur != None and cur.next != None: # 当前cur不为空且cur下一个不为空
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return head
2.2. 设置虚拟节点法
- 时间复杂度:
O(n)
- 空间复杂度:
O(1)
Python
# 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 removeElements(self, head, val):
"""
:type head: ListNode
:type val: int
:rtype: ListNode
"""
dummy_head = ListNode(next = head) # 虚拟头节点,next指向头节点
# 遍历链表,删除符合条件的节点
cur = dummy_head
# 出循环条件,cur.next == None,此时cur是最后一个元素
while cur.next != None:
if cur.next.val == val: # 满足条件
cur.next = cur.next.next # 指向next的下一个节点
else:
cur = cur.next # 继续遍历
# 返回头节点,头节点是虚拟节点的下一个节点
return dummy_head.next
Java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
// 虚拟头节点
ListNode dummy = new ListNode(0, head);
// 遍历
ListNode cur = dummy;
while (cur.next != null) {
if (cur.next.val == val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return dummy.next;
}
}
0707. 设计链表 Design Linked List
1. 题目描述
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:val
和 next
。val
是当前节点的值,next
是指向下一个节点的指针/引用。
如果是双向链表,则还需要属性 prev
以指示链表中的上一个节点。假设链表中的所有节点下标从 0
开始。
实现 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
的节点。
示例:
输入
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]
解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2); // 链表变为 1->2->3
myLinkedList.get(1); // 返回 2
myLinkedList.deleteAtIndex(1); // 现在,链表变为 1->3
myLinkedList.get(1); // 返回 3
2. 解题思路
设计链表的五个接口:
- 获取链表第
index
个节点的数值 - 在链表的最前面插入一个节点
- 在链表的最后面插入一个节点
- 在链表第
index
个节点前面插入一个节点 - 删除链表的第
index
个节点
链表操作的两个方式:
- 直接使用原来的链表进行操作
- 设置链表虚拟头节点
注意:
- 自己定义链表节点
ListNode
类 - 遍历是从虚拟节点
dummy_node
开始还是从虚拟节点下一个节点dummy_node.next
开始。对于addAtIndex
和deleteAtIndex
来说,遍历从dummy_node
开始,当特殊情况index = 0
,此时相当于插入/删除头节点,cur = dummy_node
后,循环不会执行,插入/删除cur.next
,即真正的头节点;对于get
来说,遍历从dummy_node.next
开始,此时cur = dummy_node.next
是真正的头节点。 - 插入/删除节点后,记得更新链表的长度
size
。 - 时间复杂度:涉及
index
相关的算法和尾部插入为O(n)
,头元素插入为O(1)
- 空间复杂度:
O(1)
3. 算法实现
3.1 单向链表 + 虚拟头节点
Python
# ListNode链表节点类
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
# 定义虚拟头节点
self.dummy_head = ListNode() # 此时的head是虚拟头节点,不是真正的头节点
self.size = 0
def get(self, index: int) -> int:
# 先判断index是否有效,无效返回-1
if index >= self.size or index < 0:
return -1
cur = self.dummy_head.next # 此时的cur是真正的头节点
for i in range(index): # 遍历链表
cur = cur.next
return cur.val
def addAtHead(self, val: int) -> None:
# 定义新的头节点,指向原本的头节点,即dummy_node的下一个
cur = ListNode(val, self.dummy_head.next)
self.dummy_head.next = cur
self.size += 1
def addAtTail(self, val: int) -> None:
# 遍历数组找到尾节点
cur = self.dummy_head
# 出界条件为cur.next == None,此时cur为尾巴节点
while cur.next != None:
cur = cur.next
# 插入新尾巴节点
cur.next = ListNode(val)
self.size += 1
def addAtIndex(self, index: int, val: int) -> None:
# 先判断index是否有效
if index < 0 or index > self.size:
return
# 遍历链表,找到目标节点
# 从dummy_head开始遍历,如果index = 0, 相当于插入头节点
# 如果index = size,相当于插入尾节点
cur = self.dummy_head
for i in range(index):
cur = cur.next
cur.next = ListNode(val, cur.next)
self.size += 1
def deleteAtIndex(self, index: int) -> None:
# 先判断index
if index < 0 or index >= self.size:
return
# 从dummy_node开始遍历
cur = self.dummy_head
for i in range(index):
cur = cur.next
cur.next = cur.next.next
self.size -= 1
Java
class ListNode {
int val;
ListNode next;
// 构造函数
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
class MyLinkedList {
ListNode dummyhead;
int size;
public MyLinkedList() {
this.dummyhead = new ListNode(0, null);
this.size = 0;
}
public int get(int index) {
// index 无效
if (index < 0 || index >= this.size) {
return -1;
}
// 遍历
ListNode cur = this.dummyhead.next;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur.val;
}
public void addAtHead(int val) {
this.dummyhead.next = new ListNode(val, this.dummyhead.next);
this.size++;
}
public void addAtTail(int val) {
// 遍历
ListNode cur = this.dummyhead;
while (cur.next != null) {
cur = cur.next;
}
cur.next = new ListNode(val, null);
this.size++;
}
public void addAtIndex(int index, int val) {
// index 无效
// index = this.size 可以插入节点,相当于addAtTail
if (index < 0 || index > this.size) {
return;
}
// 遍历
// 找到index-1的节点
ListNode cur = this.dummyhead;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.next = new ListNode(val, cur.next);
this.size++;
}
public void deleteAtIndex(int index) {
// index 无效
if (index < 0 || index >= size) {
return;
}
// 遍历到index-1的节点
ListNode cur = this.dummyhead;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.next = cur.next.next;
this.size--;
}
}
0206. 反转链表 Reverse Linked List
1. 题目描述
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例2:
输入:head = [1,2]
输出:[2,1]
示例3:
输入:head = []
输出:[]
2. 解题思路
- 如果定义一个新的链表,浪费内存空间,所以在原本链表内改变指针
next
的方向,直接反转链表
链表双指针法
- 定义指针
pre
指向None
(因为pre
初始化指向head
之前),cur
指向head
- 遍历列表,注意遍历条件为
while cur
而不是while cur.next
,因为如果是后者,原本列表最后一个节点不会遍历到,也不会反转 - 在循环中,先定义临时指针
temp
指向cur.next
,存放cur
原本指向的下一个节点。注意temp
不能指向cur
,因为传递的是引用,当后面反转时cur.next
改变,temp.next
也会改变。 - 反转指针,即让
cur.next
指向pre
,然后pre
向前移动,即pre = cur
。继续遍历链表,此时cur = temp
即cur
指向原本的下一个节点。 - 返回整个链表,即返回
pre
。注意不是返回cur
,因为遍历完后,cur == None
。
# 链表双指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
# 定义cur, pre
pre = None
cur = head
# 遍历链表
while cur: # 如果 cur.next,原本最后一个节点不会反转
temp = cur.next # 保存当前节点
cur.next = pre # 反转
pre = cur
cur = temp
return pre # 返回pre,如果返回cur,最后cur == None
- 时间复杂度:
O(n)
- 空间复杂度:
O(1)
Java:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
// 双指针迭代法
ListNode pre = null;
ListNode cur = head;
ListNode temp;
while (cur != null) {
temp = cur.next;
// 交换位置
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
}