代码随想录-day5-链表(1)
从今天开始,我们将进入链表的学习,里面的知识很多。
1、LeetCode 203 移除链表元素
题目分析:
移除元素(删除结点)是链表基本操作中最常见的一种操作。
一般流程就是:定位到需要删除的结点的上一个结点,这里为了表示方便,我们记作pre,删除的操作就是一句话pre->next = pre->next->next。当然,对于c++而言,我们还需要将需要删除的结点删除进行内存的释放。
这就牵涉到一个麻烦的点:如果删除的是头结点,但是头结点之前没有结点,怎么办?
方案1:单独处理头结点
方案2:设置虚拟头结点dummyHead,让dummyHead的下一个结点指向head,就可以对左右元素进行统一的操作了。 这种需要处理某个结点,需要前面结点进行辅助的情况,都可以设置dummyHead来进行操作。特别是题目中涉及删除的情况。本题就是一道典型的设计删除,可以使用虚拟头结点的情况的题目。
题目解答:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyHead = new ListNode(0, head); // 设置虚拟头结点,并且下一个结点时head
ListNode* cur = dummyHead; // 当前的工作结点
while (cur->next) {
if (cur->next->val == val) {
ListNode* temp = cur->next; // 存起来,等会儿释放
cur->next = cur->next->next; // 删除操作
delete temp;
}
else {
cur = cur->next;
}
}
ListNode* ans = dummyHead->next;
delete dummyHead; // 虚拟的头结点也需要删除
return ans;
}
};
解答如上,非常简单。
有一个问题,这个循环里里面的条件,到底是cur != nullptr 还是cur->next != nullptr呢?
我们只要关注我们需要操作的就行了!
本题,一开始cur是虚拟结点,那么cur->next就是我们需要操作的结点,所以while中的条件就是cur->next。
如果我们为了计算链表的长度:
int calculateLinkLen(ListNode* head) {
ListNode* cur = head;
int len = 0;
while (cur) {
cur = cur->next;
len++;
}
return len;
}
这里cur一开始指向的是head,也就是我们需要操作的数,那么循环的判断条件就是cur。
另外,我们有时候指针需要移动两位,一样的道理,此时循环里面的就是
cur && cur->next, 注意这里顺序不能颠倒,否则当cur为nullptr,cur->next就会报错。
2、LeetCode 707 设计链表
题目分析:
本题非常重要,它考察了我们对链表最最最基本的几种操作,需要反复写,反复熟悉,这是基础中的基础。
题目解答:
class myListNode {
public:
int val;
myListNode* next;
myListNode(int x): val(x), next(nullptr) {};
};
class MyLinkedList {
public:
MyLinkedList() {
size = 0;
head = nullptr;
}
int get(int index) {
if (index < 0 || index > size - 1) {
return -1; // 如果索引无效,返回-1
}
int pos = 0;
myListNode* cur = head;
while (cur) {
if (pos == index) {
return cur->val;
}
else {
cur = cur->next;
}
}
return -1;
}
void addAtHead(int val) {
myListNode* newNode = new myListNode(val);
myListNode* oldNode = head;
newNode->next = oldNode;
head = newNode;
size++;
}
void addAtTail(int val) {
myListNode* newTail = new myListNode(val);
myListNode* dummyHead = new myListNode(0);
dummyHead->next = head; // 设置一个虚拟头结点
myListNode* cur = dummyHead;
while (cur->next) {
cur = cur->next;
}
// 此时的cur指向的是最后一个结点
cur->next = newTail;
newTail->next = nullptr;
head = dummyHead->next;
delete dummyHead;
size++;
}
void addAtIndex(int index, int val) {
if (index < 0) {
addAtHead(val); // 小于0,插入头部结点
}
else if (index > size - 1) {
return; // 大于链表长度,不会插入结点
}
else {
int pos = 0;
// 设置虚拟头结点
myListNode* dummyHead = new myListNode(0);
dummyHead->next = head;
myListNode* cur = dummyHead;
myListNode* newNode = new myListNode(val);
while (cur->next) {
if (pos == index) {
// 此时cur就是需要插入的元素的上一个元素
myListNode* node = cur->next;
cur->next = newNode;
newNode->next = node;
size++;
// 虚拟结点的删除
head = dummyHead->next;
delete dummyHead;
break;
}
else {
cur = cur->next;
pos++;
}
}
}
}
void deleteAtIndex(int index) {
if (index < 0 || index > size - 1) {
return; // 索引无效
}
myListNode* dummyHead = new myListNode(0); // 设置虚拟头结点
dummyHead->next = head;
int pos = 0;
myListNode* cur = dummyHead;
while (cur->next) {
if (pos == index) {
// 此时cur指向的是需要删除的结点的上一个结点
myListNode* temp = cur->next;
cur->next = cur->next->next;
delete temp;
head = dummyHead->next;
delete dummyHead;
size--;
break; // 满足条件跳出循环
}
else {
cur = cur->next;
pos++;
}
}
}
private:
int size; // 链表的长度
myListNode* head; // 链表的头结点
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList* obj = new MyLinkedList();
* int param_1 = obj->get(index);
* obj->addAtHead(val);
* obj->addAtTail(val);
* obj->addAtIndex(index,val);
* obj->deleteAtIndex(index);
*/
3、LeetCode 206 反转链表
题目分析:
反转链表是典型的使用双指针进行操作的题目,就是那种需要遍历整个链表的题目就是很多时候用到的都是双指针。
反转过后,以前的头结点肯定作为最后一个结点,指向nullptr,所以我们要一开始就弄一个虚拟结点,让它指向nullptr。
我们定义两个指针,fast指针负责遍历链表,slow负责存结果,最终返回slow即可。
题目解答:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* fast = head;
ListNode* slow = nullptr; // 因为反转后的最后一个结点是nullptr,所以开始就让slow指向nullptr
while (fast) {
ListNode* temp = fast->next; // 保存fast的下一个结点
fast->next = slow; // 反转过后,slow就是fast的next
slow = fast; // 结点更新
fast = temp;
}
return slow;
}
};
4、LeetCode 24 反转链表
题目分析:
本题一开始思考还是有点儿困难,本题使用了虚拟头结点的方法后简单很多了。
本题还是满足虚拟头结点的使用原则,需要操作cur的next以及next->next。如果没有虚拟头结点的话,这里的交换不太容易实现。
题目解答:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0, head); // 虚拟头结点
ListNode* cur = dummyHead;
while (cur->next && cur->next->next ) { // 由于需要考虑后面的两个结点,那么需要进行这样的判定
ListNode* temp1 = cur->next; // 存储cur的next
ListNode* temp2 = cur->next->next->next; // 存储下下下个结点
cur->next = cur->next->next; // 结点更新,这里一定要想明白
cur->next->next = temp1;
temp1->next = temp2;
cur = temp1; // 当前工作结点的更新
}
ListNode* ans = dummyHead->next; // 虚拟头结点释放
delete dummyHead;
return ans;
}
};
注意虚拟头结点的使用,当遇到困难了,想想双指针法,以及虚拟头结点的方法,可以解决很多问题。