Leetcode 203.移除链表元素
题目链接:Leetcode 203.移除链表元素
题目描述:删除链表中等于给定值 val 的所有节点。
思路:
1.将删除元素的上一个元素指向删除元素的下一个元素
2.释放内存
不过因为单链表的特殊性,只能指向下一个节点,刚刚删除的是链表的中第二个,和第四个节点,那么如果删除的是头结点又该怎么办呢?
这里就涉及如下链表操作的两种方式:
- 直接使用原来的链表来进行删除操作。
- 设置一个虚拟头结点在进行删除操作。
先看第一种情况:
由于原来的链表头节点为第一个元素,所以删除该元素只需head=head->next,之后释放该节点即可。
第一种方式代码:(不带虚拟头节点)
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//删除头节点
while (head != nullptr && head->val == val) {
ListNode* temp = head;
head = head->next;
delete temp;
}
//删除非头结点
ListNode* cur = head;
while (cur != nullptr && cur->next != nullptr) {
if (cur->next->val == val) {
ListNode* temp = cur->next;
cur->next = cur->next->next;
delete temp;
} else {
cur = cur->next;
}
}
return head;
}
};
- 时间复杂度: O(n)
- 空间复杂度: O(1)
由于头节点的删除和其他节点的删除操作有所不同,因此需要判断。那可不可以将操作统一呢?
答案是可以的,既然头节点和其他节点不同点在于头节点之前没有节点,那是不是可以创建一个虚拟头节点放到头节点之前,这样原链表的所有节点就都可以按照统一的方式进行移除了。
第二种方式代码:(带虚拟头节点)
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyHead = new ListNode(0); //建立虚拟头节点
dummyHead->next = head;
ListNode* cur = dummyHead;
while (cur->next != NULL) {
if (cur->next->val == val) //如果找到删除结点的前一个结点
{
ListNode* temp = cur->next; //保存被删除的节点
cur->next = cur->next->next;
delete temp;
} else //如果没找到
{
cur = cur->next;
}
}
head = dummyHead->next; //虚拟头节点的next即原链表的头节点
delete dummyHead;
return head;
}
};
- 时间复杂度: O(n)
- 空间复杂度: O(1)
Leetcode 707.设计链表
题目链接:Leetcode 707.设计链表
题目描述:
在链表中实现这些功能:
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
这道题算是练习单链表和双链表最好的一道题了,包含了链表的三种添加方式,获取某个节点元素的值,删除某个节点。有人可能会说,没有修改操作怎么能算好题呢?实则不然,修改元素是不是只需要:(1)查找到某个节点(2)修改该节点元素的值
(1)这道题已经包括了,(2)就是一行赋值操作。没必要新增一个函数了。
思路:推荐看这个视频,讲的很好:帮你把链表操作学个通透!
我分别用单链表和双链表实现了以上操作,其中双链表很易错!
单链表:
class MyLinkedList {
public:
//定义链表结构体
struct LinkList {
int val;
LinkList* next;
LinkList(int val) : val(val), next(nullptr) {}
};
//初始化链表
MyLinkedList() {
_dummyhead = new LinkList(0);
_size = 0;
}
//获取第index个节点数据
int get(int index) {
//如果下标无效则返回-1
//先判断index是否大于_size-,再判断index是否小于
if (index > (_size - 1) || index < 0) {
return -1;
}
LinkList* cur = _dummyhead->next;
while (index--) {
cur = cur->next;
}
return cur->val;
}
//头插法
void addAtHead(int val) {
LinkList* newNode = new LinkList(val);
newNode->next = _dummyhead->next;
_dummyhead->next = newNode;
_size++;
}
//尾插法
void addAtTail(int val) {
LinkList* newNode = new LinkList(val);
LinkList* cur = _dummyhead;
while (cur->next != nullptr) {
cur = cur->next;
}
cur->next = newNode;
_size++;
}
//中间插入
void addAtIndex(int index, int val) {
if (index > _size) {
return;
}
if (index < 0) {
index = 0;
}
LinkList* newNode = new LinkList(val);
LinkList* cur = _dummyhead;
while (index--) {
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
_size++;
}
//删除第index个节点
void deleteAtIndex(int index) {
if (index >= _size || index < 0) {
return;
}
LinkList* cur = _dummyhead;
while (index--) {
cur = cur->next;
}
LinkList* temp = cur->next;
cur->next = cur->next->next;
delete temp;
temp = nullptr;
_size--;
}
//打印链表
void printLinkList() {
LinkList* cur = _dummyhead;
while (cur->next != nullptr) {
cout << cur->next->val << " ";
cur = cur->next;
}
cout << endl;
}
private:
int _size;
LinkList* _dummyhead;
};
双链表:
class MyLinkedList {
public:
//定义链表结构体
struct LinkList {
int val;
LinkList* next;
LinkList* prev;
LinkList(int val) : val(val), next(nullptr), prev(nullptr) {}
};
//初始化链表
MyLinkedList() {
_dummyhead = new LinkList(0);
_size = 0;
}
//获取第index个节点数据
int get(int index) {
//如果下标无效则返回-1
//先判断index是否大于_size-,再判断index是否小于
if (index > (_size - 1) || index < 0) {
return -1;
}
LinkList* cur = _dummyhead->next;
while (index--) {
cur = cur->next;
}
return cur->val;
}
//头插法
void addAtHead(int val) {
LinkList* newNode = new LinkList(val);
newNode->prev = _dummyhead;
newNode->next = _dummyhead->next;
//这是为了防止newNode->next为空,如果为空,则不用进行该操作
//后面两个加“注意”的判断也是这个原因
//调试好久才发现。。。
if (newNode->next != nullptr) {
newNode->next->prev = newNode;
}
_dummyhead->next = newNode;
_size++;
}
//尾插法
void addAtTail(int val) {
LinkList* newNode = new LinkList(val);
LinkList* cur = _dummyhead;
while (cur->next != nullptr) {
cur = cur->next;
}
newNode->next = cur->next;
newNode->prev = cur;
cur->next = newNode;
_size++;
}
//中间插入
void addAtIndex(int index, int val) {
if (index > _size) {
return;
}
if (index < 0) {
index = 0;
}
LinkList* newNode = new LinkList(val);
LinkList* cur = _dummyhead;
while (index--) {
cur = cur->next;
}
newNode->next = cur->next;
newNode->prev = cur;
//这里注意
if (newNode->next != nullptr) {
newNode->next->prev = newNode;
}
cur->next = newNode;
_size++;
}
//删除第index个节点
void deleteAtIndex(int index) {
if (index >= _size || index < 0) {
return;
}
LinkList* cur = _dummyhead;
while (index--) {
cur = cur->next;
}
LinkList* temp = cur->next;
cur->next = cur->next->next;
//这里注意
if (temp->next != nullptr) {
temp->next->prev = temp->prev;
}
delete temp;
temp = nullptr;
_size--;
}
//打印链表
void printLinkList() {
LinkList* cur = _dummyhead;
while (cur->next != nullptr) {
cout << cur->next->val << " ";
cur = cur->next;
}
cout << endl;
}
private:
int _size;
LinkList* _dummyhead;
};
- 时间复杂度: 涉及
index
的相关操作为 O(index), 其余为 O(1) - 空间复杂度: O(n)
Leetcode 206.反转链表
题目描述:反转一个单链表。
思路:一开始打算创建一个新链表从后向前赋值,但是写了很长时间,看题解发现完全没必要。
改变原来链表的指针指向不就行了吗。这里又用到之前学过的双指针法了。
首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。
然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。
接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。
最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点。
第一种写法:循环迭代
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* temp;//临时保存节点
ListNode* cur=head;
ListNode* pre=nullptr;
while(cur){
temp=cur->next;//保存下一个节点
cur->next=pre;//指针翻转
//更新指针
pre=cur;
cur=temp;
}
return pre;
}
};
- 时间复杂度: O(n)
- 空间复杂度: O(1)
众所周知,循环和递归经常如影随形,其本质原因是同种思路的不同写法。因此本题也可以用递归实现。
第二种写法:递归(从前向后翻转)
class Solution {
public:
ListNode* reverse(ListNode* pre, ListNode* cur) {
if (cur == NULL) {
return pre;
}
ListNode* temp = cur->next;
cur->next = pre;
// 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
// pre = cur;
// cur = temp;
return reverse(cur, temp);
}
ListNode* reverseList(ListNode* head) {
// 和双指针法初始化是一样的逻辑
// ListNode* cur = head;
// ListNode* pre = NULL;
return reverse(NULL, head);
}
};
第三种写法:递归(从后向前翻转)
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 边缘条件判断
if (head == NULL)
return NULL;
if (head->next == NULL)
return head;
// 递归调用,翻转第二个节点开始往后的链表
ListNode* last = reverseList(head->next);
// 翻转头节点与第二个节点的指向
head->next->next = head;
// 此时的 head 节点为尾节点,next 需要指向 NULL
head->next = NULL;
return last;
}
};
- 时间复杂度: O(n), 要递归处理链表的每个节点
- 空间复杂度: O(n), 递归调用了 n 层栈空间
总结:今天的题目很好的锻炼了我对链表操作的掌握,也发现了之前忽略掉的小细节(对双链表操作时要注意该节点后面是否还有节点,如果没有,则newNode->next->prev该操作不合法)
另外,如果本文有任何错误,请在评论区或私信指出,让我们共同进步!