• 代码随想录Day03 | 链表基础1 LeetCode T203 移除链表元素 T707设计链表 T206 反转链表


    本题思路和解答主要来源于:

    代码随想录 (programmercarl.com)

    LeetCode T203 移除链表元素

    题目链接:203. 移除链表元素 - 力扣(LeetCode)

    首先我们回顾一下单向链表,每个链表有一个指针域和一个数据域,在内存中是呈现不连续排列的,对比之前的数组,链表的查找的O(n)的是时间复杂度,因为链表需要一个一个的从头向后找,数组则是O(1)的时间复杂度,对于增添和删除元素,链表则只需要将待删除元素的前一个元素的指针指向后一个元素,是O(1)的时间复杂度,而对于数组则需要移动后面若干个元素,是O(n)的时间复杂度.

    综上,我们得到了一条结论,在需要频繁查找而不需要频繁添加元素的集合里使用数组,反之使用链表. 

    思路1:  直接删除元素

    这里有两个经典的思路,一是分头节点和后面的节点来讨论,我们先说这种思路,首先处理头节点的val是我们查找的val,我们一定是使用while循环而不是if语句,因为这个查找是持续进行下去的,如果我的链表是1->1->1->1,且查找值也是1的话那么只有一个if语句就不能解决问题.

    注:一定要记得判断节点不为空
     

    1. while (head != null && head.val == val) {
    2. head = head.next;
    3. }

    然后我们判断一下头结点是否为空,如果是,直接返回头结点,不是我们就继续往后走,注意,我们要定义两个指针来解决移除元素的工作,一个pre指针是记录当前指针的前一个节点,cur指针是临时创建出来保证head不被修改的,然后我们判断只要cur指针是否为空,如果不是空指针且值相等我们就进行移除操作,不是空指针且值不等我们就进行前进操作.最后返回head即可.

    完整实现代码: 

    1. while (head != null && head.val == val) {
    2. head = head.next;
    3. }
    4. if (head == null) {
    5. return head;
    6. }
    7. ListNode pre = head;
    8. ListNode cur = head.next;
    9. while (cur != null) {
    10. if (cur.val == val) {
    11. pre.next = cur.next;
    12. } else {
    13. pre = cur;
    14. }
    15. cur = cur.next;
    16. }
    17. return head;

    思路2:使用虚拟头结点 

     这里我们还可以使用虚拟头结点,目的是统一移除操作,避免了额外来处理我们的头结点,我们也可以认为是常说的哨兵节点,这样我们可以统一从头结点出发,首先判断空链表,如果是就直接返回头节点,下面我们创建虚拟头结点dummy,让它的next指向头结点,创建一个cur节点指向head,pre指向dummy,只要cur不是空指针,我们就开始判断值,后面的操作同上,最后返回dummy的下一个节点.

    实现代码 

    1. public ListNode removeElements(ListNode head, int val) {
    2. if (head == null) {
    3. return head;
    4. }
    5. // 因为删除可能涉及到头节点,所以设置dummy节点,统一操作
    6. ListNode dummy = new ListNode(-1, head);
    7. ListNode pre = dummy;
    8. ListNode cur = head;
    9. while (cur != null) {
    10. if (cur.val == val) {
    11. pre.next = cur.next;
    12. } else {
    13. pre = cur;
    14. }
    15. cur = cur.next;
    16. }
    17. return dummy.next;
    18. }

    LeetCode T707: 设计链表

    链接:707. 设计链表 - 力扣(LeetCode)

    其实就是设计链表的增删查的一些基础接口,这里我们仍然延续上文的虚拟头节点方式进行书写代码.我们一个功能一个功能来实现.

    1. 定义单链表

    1. class ListNode {
    2. int val;
    3. ListNode next;
    4. ListNode(){}
    5. ListNode(int val) {
    6. this.val=val;
    7. }
    8. }

    2.初始化链表 

    初始化之前,我们先定义两个变量:链表长度,链表的虚拟头结点.

    1. int size;
    2. ListNode dummyHead;
    1. public MyLinkedList() {
    2. size = 0;
    3. dummyHead = new ListNode(0) ;
    4. }

    3.获取index下标的元素 

    首先,判定index的合法性,如果index>size-1,那么直接返回-1,然后定义cur指针,指向虚拟头结点dummyHead,进行index+1次循环,找到目标元素,返回他的数据.至于为什么是index+1次,这里我们考虑极端情况,index = 0,这里我们需要进行一次循环,即cur = cur.next;

    1. public int get(int index) {
    2. if(index>size-1)
    3. {
    4. return -1;
    5. }
    6. ListNode cur = dummyHead;
    7. for(int i = 0;i<=index;i++)
    8. {
    9. cur = cur.next;
    10. }
    11. return cur.val;
    12. }

    4.在链表中间插入元素

    因为题目要求有在链表末尾和头部插入元素,这里我们先实现在链表中间插入元素,后面前两个需求就迎刃而解了.

    这里我们想再节点a和节点b中间加上一个节点c一定不能先让a节点的指针指向c节点,这样我们的b节点就找不到了,所以要让c节点先指向b节点,再用a节点指向c节点. 

    1. public void addAtIndex(int index, int val) {
    2. if(index>size)
    3. {
    4. return;
    5. }
    6. size++;
    7. ListNode pre = dummyHead;
    8. for(int i = 0;i
    9. {
    10. pre = pre.next;
    11. }
    12. ListNode toAdd = new ListNode(val);
    13. toAdd.next = pre.next;
    14. pre.next = toAdd;
    15. }

     LeetCode T206 反转链表

    链接:206. 反转链表 - 力扣(LeetCode)

    思路1:双指针写法 

    实质上我们就是让这个链表的指针对调一个方向

    这里我们prev就是最后尾指针指向的NULL,所以初始化为NULL,这里temp是为了保存cur指针的下一个元素,不然在cur指针反转后变成尾指针之后就找不到cur原本指向的元素了. ,然后循环的终止条件就是cur不等于空指针,因为原来的尾节点指向空指针,当cur等于空指针的时候,prev就是我们所求的反转后的头指针.

    1. // 双指针
    2. class Solution {
    3. public ListNode reverseList(ListNode head) {
    4. ListNode prev = null;
    5. ListNode cur = head;
    6. ListNode temp = null;
    7. while (cur != null) {
    8. temp = cur.next;// 保存下一个节点
    9. cur.next = prev;
    10. prev = cur;
    11. cur = temp;
    12. }
    13. return prev;
    14. }
    15. }

    思路2: 递归写法

    递归写法实际上是双指针写法的一种延伸

    1. class Solution {
    2. public ListNode reverseList(ListNode head) {
    3. return reverse(null, head);
    4. }
    5. private ListNode reverse(ListNode prev, ListNode cur) {
    6. if (cur == null) {
    7. return prev;
    8. }
    9. ListNode temp = null;
    10. temp = cur.next;// 先保存下一个节点
    11. cur.next = prev;// 反转
    12. // 更新prev、cur位置
    13. // prev = cur;
    14. // cur = temp;
    15. return reverse(cur, temp);
    16. }
    17. }

    总结:

    在进行循环分析的时候,一定要设置好足够的节点先保存待会会消失的节点,可以给一定的极端的例子来验证代码的可行性,考虑好空指针的判断和结束条件即可.

  • 相关阅读:
    Redis实战案例及问题分析之分布式锁解决优惠券秒杀场景集群并发下的安全问题
    Python使用腾讯云SDK实现对象存储(上传文件、创建桶)
    总结嵌入式C语言难点(2部分)
    vue3 打印局部网页、网页下载为图片、下载为pdf-自动分页,几行代码搞定
    机器视觉运动控制一体机应用例程|U盘视觉定位激光打标解决方案
    Csharp中表达式树
    Win7系统如何在线进行重装?在线一键重装Win7方法
    2023年全网最新 Windows10 安装 JDK17以及JDK1.8
    Docker实用篇
    REDIS7.X哨兵模式
  • 原文地址:https://blog.csdn.net/qiuqiushuibx/article/details/133178773