代码随想录算法训练营第3天|203.移除链表元素、707.设计链表、206.反转链表

LeetCode203

2025-01-24 10:50:39 星期五

题目描述:力扣203
文档讲解:代码随想录(programmercarl)203.移除链表元素
视频讲解:《代码随想录》算法视频公开课:链表基础操作| LeetCode:203.移除链表元素

代码随想录视频内容简记

原链表删除元素

删除链表中间的某个元素,这样的方式就需要分别对头结点不是头结点的情况进行讨论

梳理

  1. 如果头结点的value是target值,则删除头结点

  2. 如果中间结点的value是target值,删除中间结点

  3. 返回头指针head

大致代码内容

  1. 第一种情况,target是头结点的值while (head != NULL && head -> val == target)则执行head = head -> next。另外就是这里之所以要用while就是为了防止出现[1,1,1,1]诸如此类的情况

  2. 第二种情况,target是中间结点的值。首先需要定义一个current指针指向头结点。也就是cur = head


`while (cur != NULL && cur -> next != NULL){`
	if (cur -> next -> val == target){
		cur -> next = cur -> next -> next;
	}
}
  1. 最后该删的都删完了return head

使用虚拟头结点

使用虚拟头结点的好处就是处理规则是统一的,就不用再分情况了

梳理

  1. 首先定义虚拟头结点

  2. 中间循环遍历进行删除

  3. 返回虚拟头结点指向的下一个结点。注意这里一定是下一个结点,不是初始的head

大致代码内容

  1. ListNode* dummyHead = new ListNode(0);

  2. 中间的循环遍历如下,注意中间仍让要定义一个新的临时指针用来指代cur = dummyHead


while (cur -> next != NULL ) {
	if (cur -> next -> val == target){
		cur -> next = cur -> next -> next;
	}
}
  1. 返回return dummyHead -> next;

LeetCode测试

原链表删除元素

代码比较好通过,就是要分清楚分别删除头结点和中间结点使用不同的条件判断
这里还有一个小坑,就是如果要在代码中放delete结点的部分,ListNode* cur = head这行判断就必须放在删除完头结点部分的下面,否则有可能就把cur定义的head删掉了

点击查看代码
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        while (head != NULL && head->val == val){
            ListNode* tmp = head;
            head = tmp->next;
            delete tmp;
        }
        ListNode* cur = head;
        while (cur != NULL && cur->next != NULL){
            if (cur->next->val == val){
                ListNode* tmp = cur->next;
                cur->next = tmp->next;
                delete tmp;
            }
            else {
                cur = cur->next;
            }
        }
        return head;
    }
};

使用虚拟头结点

这个就是要注意仍然要额外引入一个cur指针,最后返回dummyHead -> next就可以

点击查看代码
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummyHead = new ListNode(0);
        ListNode* cur;
        dummyHead -> next = head;
        cur = dummyHead;
        while (cur -> next != NULL){
            if (cur -> next -> val == val){
                ListNode* tmp = cur -> next;
                cur -> next = tmp -> next;
                delete tmp;
            }
            else cur = cur -> next; 
        }
        return dummyHead -> next;
    }
};

LeetCode707

题目描述:力扣707
文档讲解:代码随想录(programmercarl)707.设计链表
视频讲解:《代码随想录》算法视频公开课:帮你把链表操作学个通透!LeetCode:707.设计链表

代码随想录视频内容简记

在函数编写的时候统一用虚拟头结点的方式,方便进行增删查改

初始化链表

这部分就是两个工作

  1. 直接新建一个dummyHead指针。我想这里长度为0的话,就默认dummyHead为第一个结点了,值域为什么不是head而是dummyHead,我感觉仅仅只是写法的问题,其实并没有dummyHead和Head同时存在

  2. size赋0

获取第n个结点的值

  1. 使用cur = dummyHead->next;指向虚拟头结点的下一个位置

  2. 循环如下


while (n--) {
	cur = cur->next;
}

在头部插入结点

  1. 新建一个结点,ListNode* newNode = new ListNode(val);cur = newNode;

  2. newHead = cur->next;cur->next = newHead;注意这个顺序,

  1. 最后size++

在尾部插入结点

  1. 新建一个结点,ListNode* newNode = new ListNode(0);这里默认指向的下一个就是NULLcur = dummyHead;

  2. 循环如下


while (cur->next != NULL) {
	cur = cur->next;
}
cur->next = newNode;
  1. 最后size++

在第n个结点前插入结点

  1. 首先和第一个不一样的地方在cur = dummyHead

  2. 新开辟一个结点

  3. 循环如下,关于这里的条件判断是否正确,k哥说之要带入一个极端的情况就ok了,也就是代入n=0试试。是没问题的


while (n--) {
	cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
  1. 最后size++

删除第n个结点

  1. 同样cur = dummyHead

  2. 新开辟一个结点

  3. 循环如下


while (n--) {
	cur = cur->next;
}
cur->next = cur->next->next;
  1. 最后size--

LeetCode测试

有几点需要注意:

  1. 首先是最下面需要写private,用来定义dummyHead指针和size大小

  2. 在中间函数书写中,题目要求“将一个值为 val 的节点追加到...”,在书写时只需要LinkNode(val)就可以了

  3. 最后一个问题,就是指定索引的函数(插入或者删除)的index索引可以等于链表的长度。
    这个报错找了半天,第33个用例一直卡住。😭😭最后找到啦😄

    问题出在addAtIndex()函数中,题目描述是这样的

如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将不会插入到链表中。

在之前的get()函数,在index索引的合法性判断时,因为我们索引是从0开始的,是不允许等于size的,所以只能写成if (index > size - 1)或者if (index >= size)。但是这样就是一旦出现index为size的情况就会判非法。所以要给条件判断修改if (index > size)

同理,deleteAtIndex()函数也要修改成if (index > size)

完整代码如下

点击查看代码
class MyLinkedList {
public:
    struct LinkNode {
        int val;
        LinkNode* next;
        LinkNode(int val):val(val), next(NULL){}
    };

    MyLinkedList() {
        dummyHead = new LinkNode(0);
        size = 0;
    }
    
    int get(int index) {
        if (index > size - 1 || index < 0) {
            return -1;
        }
        LinkNode* cur = dummyHead->next;
        while (index--) {
            cur = cur->next;
        }
        return cur->val;
    }
    
    void addAtHead(int val) {
        LinkNode* newNode = new LinkNode(val);
        LinkNode* cur = dummyHead;
        newNode->next = cur->next;
        cur->next = newNode;
        // newNode->next = dummyHead->next;
        // dummyHead->next = newNode;
        size++;
    }
    
    void addAtTail(int val) {
        LinkNode* newNode = new LinkNode(val);
        LinkNode* cur = dummyHead;
        while (cur->next != NULL) {
            cur = cur->next;
        }
        cur->next = newNode;
        size++;
    }
    
    void addAtIndex(int index, int val) {
        if (index > size) {
            return;
        }
        LinkNode* newNode = new LinkNode(val);
        LinkNode* cur = dummyHead;
        while (index--) {
            cur = cur->next;
        }
        newNode->next = cur->next;
        cur->next = newNode;
        size++;;
    }
    
    void deleteAtIndex(int index) {
        if (index >= size - 1 || index < 0) return;
        LinkNode* cur = dummyHead;
        while (index--) {
            cur = cur->next;
        }
        LinkNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        size--;
    }

private:
    int size;
    LinkNode* dummyHead;
};

LeetCode206

题目描述:力扣206
文档讲解:代码随想录(programmercarl)206.反转链表
视频讲解:《代码随想录》算法视频公开课:帮你拿下反转链表 | LeetCode:206.反转链表

代码随想录视频内容简记

双指针写法

感觉和数组中的双指针的一般做法还是有区别,不像之前的977.有序数组的平方是双指针的两头像中中间靠拢,也不像27.移除元素两个指针向右边走,也不像209.滑动窗口那样设计两个指针来控制窗口的大小,这里的双指针是precur一前一后紧紧跟随。

思想是一样的,时间复杂度还是O(n),但是具体的作法还是有区别。

梳理

  1. 定义pre指针

  2. 定义temp指针

  3. 循环遍历

大致代码内容

  1. pre = NULL,即反转之后最后的尾结点指向

  2. 之所以要定义temp指针,是因为在上图第一步完成后,cur->next指针发生了变化,所以要提前定义temp = cur->next

  3. while (cur != NULL),这里不是while (cur->next != NULL),注意是当cur指向NULL才停止循环的,cur->next = NULL时还要执行一次

  4. 注意最后return pre

递归写法

k哥说递归的写法和双指针的写法其实是一一对应的,首先要熟练掌握双指针的写法

递归的写法最后的时间复杂度也是O(n),其实看每个元素被操作的次数可以发现,应该是每个元素的指向发生了一次变换,所以操作次数为n,所以时间复杂度为O(n)

梳理

  1. 定义一个reverse()函数,接收两个参数,分别是curpre

  2. 递归的终止条件

  3. 递归中的调用,一定要注意赋值的含义

  4. 主函数中的调用

大致代码内容

  1. reverse(ListNode* cur, ListNode* pre),这里的cur

  2. if (cur == NULL) return pre表示递归终止,返回反转后的pre头结点

  3. 递归函数reverse()中调用,首先应参照双指针的写法,给pre = curcur = temp应该使用reverse(temp, cur),注意这里的解释,是把temp赋给上面的形参变量cur,把cur赋给上面的形参变量pre

  4. 主函数中就直接写reverse(cur, pre)就行

LeetCode测试

双指针写法

点击查看代码
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;
    }
};

递归写法

注意实际上递归的作用就是对双指针指针后移写法的等价替代,所以

cur = tmp;
pre = cur;

这个要省去换成递归

点击查看代码
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* cur = head;
        ListNode* pre = NULL;
        return reverse(cur, pre);
    }
    ListNode* reverse(ListNode* cur, ListNode* pre) {
        ListNode* tmp;
        if (cur == NULL) return pre;
        // 反转
        tmp = cur->next;
        cur->next = pre;
        //cur = tmp;
        //pre = cur;
        // 递归
        return reverse(tmp, cur);
    }
};
posted on   bnbncch  阅读(2324)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示