203.移除链表元素
题目链接:https://leetcode.cn/problems/remove-linked-list-elements/description/
1.直接法
因为之前在王道网课上系统地打过一遍基础,一开始还是很自信的;然后一做题…只能说考研题和力扣题还是有差距的。
先展示一下代码:
ListNode* removeElements(ListNode* head, int val) {
while(head!=NULL&&head->val ==val)
{
ListNode*q =head;
head = head->next;
delete q;
}
ListNode*p=head;
while(p!=NULL&&p->next!=NULL)
{
if(p->next->val==val)
{
ListNode*q =p->next;
p->next = p->next->next;
delete q;
}
else
p=p->next;
}
return head;
}
主要有3个注意点:
-
头结点内是有数据的(这和王道讲的不一样),一旦头结点内的数据满足删除要求,就会像多米诺骨牌一样,一个一个往后删,所以需要用while循环
-
使用C++写该代码时,需要及时清理内存,释放被删除的节点,具体方法是:
ListNode*q = Deleted;
delete q;
Deleted指的是被删除的结点。
-
删除头结点逻辑和删除非头结点的逻辑时不一样的
另外,不推荐大家用双指针来做删除的逻辑,容易乱且容易错(本人的血泪教训哈哈)
2.虚拟头结点
为了让删除头结点和删除非头结点是相同的逻辑,我们可以在原来的头结点前面再加上一个结点,也就是虚拟头结点,具体代码如下:
ListNode* removeElements(ListNode* head, int val) {
ListNode*dummyhead = new ListNode(0);
dummyhead->next = head;
ListNode*p=dummyhead;
while(p->next!=NULL)
{
if(p->next->val==val)
{
p->next=p->next->next;
}
else
p=p->next;
}
return dummyhead->next;
}
虚拟头结点真的是方便又巧妙,唯一 需要注意的是返回值不是dummyhead(这只是虚拟头结点)或者head(因为head结点可能被删除了),而是dummyhead->next;
707.设计链表
题目链接:https://leetcode.cn/problems/design-linked-list/description/
这题就是链表的基本操作,代码比较长。我原来写的没有用虚拟头结点,下面展示的是卡哥用虚拟头结点的代码,有明显的简化:
class MyLinkedList {
public:
// 定义链表节点结构体
struct LinkedNode {
int val;
LinkedNode* next;
LinkedNode(int val):val(val), next(nullptr){}
};
// 初始化链表
MyLinkedList() {
_dummyHead = new LinkedNode(0); // 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点
_size = 0;
}
// 获取到第index个节点数值,如果index是非法数值直接返回-1, 注意index是从0开始的,第0个节点就是头结点
int get(int index) {
if (index > (_size - 1) || index < 0) {
return -1;
}
LinkedNode* cur = _dummyHead->next;
while(index--){ // 如果--index 就会陷入死循环
cur = cur->next;
}
return cur->val;
}
// 在链表最前面插入一个节点,插入完成后,新插入的节点为链表的新的头结点
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);
newNode->next = _dummyHead->next;
_dummyHead->next = newNode;
_size++;
}
// 在链表最后面添加一个节点
void addAtTail(int val) {
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while(cur->next != nullptr){
cur = cur->next;
}
cur->next = newNode;
_size++;
}
// 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果index大于链表的长度,则返回空
// 如果index小于0,则在头部插入节点
void addAtIndex(int index, int val) {
if(index > _size) return;
if(index < 0) index = 0;
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while(index--) {
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
_size++;
}
// 删除第index个节点,如果index 大于等于链表的长度,直接return,注意index是从0开始的
void deleteAtIndex(int index) {
if (index >= _size || index < 0) {
return;
}
LinkedNode* cur = _dummyHead;
while(index--) {
cur = cur ->next;
}
LinkedNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
//delete命令指示释放了tmp指针原本所指的那部分内存,
//被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
//如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
//如果之后的程序不小心使用了tmp,会指向难以预想的内存空间
tmp=nullptr;
_size--;
}
// 打印链表
void printLinkedList() {
LinkedNode* cur = _dummyHead;
while (cur->next != nullptr) {
cout << cur->next->val << " ";
cur = cur->next;
}
cout << endl;
}
private:
int _size;
LinkedNode* _dummyHead;
};
206.反转链表
题目链接:https://leetcode.cn/problems/reverse-linked-list/
1.双指针法
凭借之前王道网课模糊的印象,反转链表的方法大概有头插法、双指针法。首先我考虑了用双指针法,然后没有下文了…在看完了卡哥的讲解之后,我才明白还需要在来一个指针(好家伙,三指针),具体代码如下:
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;
}
分别设置了3个指针,作用各不相同而且都不可或缺(我替大家试过了,缺一个绝对做不出来…)
首先是cur指针,它就是指向我们现在要研究的结点,属于正常操作。
然后是pre指针,要知道,当我们将链表反转时,是需要将当前结点的next指向前一个结点的。那么问题来了,怎么获取前一个结点?答案:提前设置一个pre指针指向cur的前一个结点就行了。
最后是temp指针,跟上面思路相似。当我们把cur->next指向pre的时候,问题来了:cur的下一个结点你怎么表示它?这链表已经断了呀!不急(其实当时我急得很),用temp指针指向cur原来的下一个结点就行了。
其实我们发现,这3个指针很难说我们一下子就能想出来,都是我们思路遇到了问题然后为了解决它而想出来的,所以理解这道题的思考和逻辑过程可能比这个方法本身更重要。
2.递归法
逻辑与双指针一样,感觉就是炫技…
具体代码如下(来自卡哥,我写不出来…):
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);
}