题目链接/文章讲解/视频讲解::代码随想录
自己想法:之前在网上看到过前驱指针的做法,就用前驱指针写了一次,但是只过了两个测试点,
ListNode*p=head;
ListNode*q=head;//用来指向p节点的前驱
while(p!=NULL){
if(p->val==val){
if(p==head){
head=p->next;//将头指针往后面移动
delete p;
p=head;
}
else{
q->next=p->next;
delete p;
p=q;
}
}
q=p;//将前驱节点往前面移动
p=p->next;
}
return head;
该代码在头结点那里会产生错误,如果一个链表中头结点就是需要删除的节点,那么p会被赋值为头结点,之后再被赋值为头结点的下一个节点,那么如果这个新的头结点又是需要被删除的节点,这个节点就没有被删除的机会,因为已经被移动到头结点的下一个节点了。考虑可以使用while语句来实现。
正解:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
// 删除头结点
while (head != NULL && head->val == val) { // 注意这里不是if
ListNode* tmp = head;
head = head->next;
delete tmp;
}
// 删除非头结点
ListNode* cur = head;
while (cur != NULL && cur->next!= NULL) {
if (cur->next->val == val) {
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
} else {
cur = cur->next;
}
}
return head;
}
};
特别注意:while循环的条件一定要保障cur!=null,如果他为空就直接不用考虑cur->next是否为空就是说cur 是链尾直接退出循环,否则会因为操作空指针报错。并且保证cur->next !=null,如果其为空就是下面会操作到空指针也会报错。
但是如果使用虚拟头结点:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
dummyHead->next = head; // 将虚拟头结点指向head,这样方面后面做删除操作
ListNode* cur = dummyHead;
while (cur->next != NULL) {
if(cur->next->val == val) {
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
} else {
cur = cur->next;
}
}
head = dummyHead->next;
delete dummyHead;
return head;
}
};
由于初始的时候为头结点初始化了,其一定不为空,所以这个while循环中就只需要判断需要删除的节点是否为空,使用 虚拟头节点,cur 第一次赋值 cur = dummyHead 的时候,已经确定 cur != NULL,然后假设第一次没有删除节点,就直接是 cur = cur -> next(往后移了一个节点)这个时候不用判断 cur 是否为 NULL,因为在上一次已经通过 while 判断了。
学习之后:学到了虚拟头结点的方法,可以将删除节点是头结点和非头结点的情况统一起来
题目链接/文章讲解/视频讲解:代码随想录
class MyLinkedList {
public:
struct LinkedNode{
int val;
LinkedNode*next;
LinkedNode(int val):val(val),next(nullptr){}//构造函数
};//定义结构体
MyLinkedList() {
dummyhead=new LinkedNode(0);
_size=0;//初始化
}
int get(int index) {
if(index>(_size-1)||index<0)//下标非法:为负数或者是超过链表长度
return -1;
LinkedNode*p=dummyhead->next;
while(index--){
p=p->next;
}
return p->val;
}
void addAtHead(int val) {
LinkedNode*p=dummyhead->next;
LinkedNode*newnode=new LinkedNode(val);
newnode->next=p;
dummyhead->next=newnode;
_size++;//注意链表的长度要时时更新
}
void addAtTail(int val) {
LinkedNode*p=dummyhead;
while(p->next!=nullptr){//遍历到尾节点的时候就要停止了
p=p->next;
}
LinkedNode*newnode=new LinkedNode(val);
p->next=newnode;
_size++;
}
void addAtIndex(int index, int val) {
if(index>_size)//记得特殊处理
return ;//没有这个位置直接返回
if (index<0)
index=0;//如果是负数,就是在第一个节点前面插入新结点
LinkedNode*p=dummyhead;
while(index--){
p=p->next;//移动到第index节点的前一个节点
}
LinkedNode*newnode=new LinkedNode(val);
newnode->next=p->next;
p->next=newnode;
_size++;//记得更新链表长度
}
void deleteAtIndex(int index) {
if(index>=_size||index<0)
return ;
LinkedNode*p=dummyhead;
while(index--){
p=p->next;
}
//delete命令指示释放了tmp指针原本所指的那部分内存,
//被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
//如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
//如果之后的程序不小心使用了tmp,会指向难以预想的内存空间
LinkedNode*temp=p->next;//必须要用一个临时指针来暂时存储需要删除的节点否则会报错
p->next=p->next->next;
delete temp;
temp=NULL;
//特别注意:
p->next=p->next->next;
delete p->next;
p->next=NULL;
是错的,我之前就是这样写的,但是注意到,在第一行我们改变了p的指针域的指向,这个时候原本的要删除的元素是已经不能直接找到了,还认为p->next就是要删除的节点就是错误的,这个时候delete p->next删除的是需要删除节点的下一个节点,并且还将其指向空,会使后面出现操作空指针异常。
_size--;
}
private:
int _size;
LinkedNode* dummyhead;
};
注意点:1.用while循环来找到第n个节点的时候,边界应该怎么确定:可以带入极端条件,比如我要删除的是第0个节点,然后就带入循环看得出的指针指向的值是否是第0个节点的前一个节点。
2.我们在插入节点的时候一定要注意顺序,如果先将要插入位置的前一个节点的next赋值给这个新结点,那么我们就会找不到插入节点之前的前一个节点的下一个节点的地址。就不能将新结点的next赋值给下一个节点。当然我们也可以先将前一个节点的下一个节点的地址用一个临时的指针先记录下来。
题目链接/文章讲解/视频讲解:代码随想录
自己想法:没有用一个temp指针将断链之后的下一个节点的地址先记录下来导致他一直在两个节点之间循环
ListNode* reverseList(ListNode* head) {
ListNode* p = head;
ListNode* q;
int n = 0;//记录节点的个数
while (p != NULL) {
n++;
p = p->next;
}
p = head;
while (n--) {
q = p->next;
q->next = p;//这两个语句导致一直出不了循环,指针用少了,应该用三个指针。
p = p->next;
}
head->next = NULL;
return p;
正确代码:a.双指针
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* temp; // 保存cur的下一个节点
ListNode* cur = head;
ListNode* pre = NULL;
while(cur) {
temp = cur->next; // 保存一下 cur的下一个节点,因为接下来要改变cur->next
cur->next = pre; // 翻转操作
// 更新pre 和 cur指针
pre = cur;
cur = temp;
}
return pre;
}
};
b.递归(对照着上面双指针的写法,填入对应的参数)
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);
}
};