代码随想录刷题记录:链表篇

前言

本篇为代码随想录的第二篇,链表篇。

203. 移除链表元素

题目描述:
在这里插入图片描述
思路:
LeetCode这个题还是有很多陷阱的,我之前写过类似的一遍就过了而这次栽了很多坑。
首先我们要知道要删除的值可能有很多个,其次要知道头结点也可能是我们要删除的节点,也有可能在头结点之后几个节点都是我们要删除的,最后要记得我们的最后一个节点也可能是我们要删除的。
综上,代码如下,注释很详尽了:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val){
        //首先考虑头结点有可能为要删除的节点的情况,循环解决
        while(head != null && head.val == val){
            head = head.next;
        }
        //头结点为空情况判断
        if(head == null) return head;
        //创建工作指针来代替头指针进行遍历
        ListNode temp = head;
        //遍历链表
        while(true){
            //因为LeetCode的链表题目头结点可能为空
            //所以如果之前先不判断head==null的情况的话
            //下面的程序就会报空指针异常
            //退出循环条件,遍历结束退出
            if(temp.next == null) break;
            //我们要遍历到该删除节点的前一个节点,然后才能执行删除操作
            //下面之所以要写成这样if-else的形式而不是这种:
            // if(temp.next.val == val) {
            //     temp.next = temp.next.next;
            // } 
            //temp = temp.next;
            //是因为如果我们删除的节点是最后一个的话
            //temp指针是无法继续后移的,所以会报空指针异常
            if(temp.next.val == val) {
                temp.next = temp.next.next;
            }else temp = temp.next;
        }
        //退出循环,返回头结点
        return head;
    }
}

707. 设计链表

题目描述:
在这里插入图片描述
思路:
这个题太恶心了我觉得,其实设计个链表什么的并不难,但是我根本不知道LeetCode想要我设计成什么样的呀吐了,我直接抄了题解,能看懂就行了我觉得,就是水题,只是我不知道LeetCode想要啥样的链表。

public class ListNode {
  int val;
  ListNode next;
  ListNode(int x) { val = x; }
}

class MyLinkedList {
  int size;
  ListNode head;  // sentinel node as pseudo-head
  public MyLinkedList() {
    size = 0;
    head = new ListNode(0);
  }

  /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
  public int get(int index) {
    // if index is invalid
    if (index < 0 || index >= size) return -1;

    ListNode curr = head;
    // index steps needed 
    // to move from sentinel node to wanted index
    for(int i = 0; i < index + 1; ++i) curr = curr.next;
    return curr.val;
  }

  /** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
  public void addAtHead(int val) {
    addAtIndex(0, val);
  }

  /** Append a node of value val to the last element of the linked list. */
  public void addAtTail(int val) {
    addAtIndex(size, val);
  }

  /** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
  public void addAtIndex(int index, int val) {
    // If index is greater than the length, 
    // the node will not be inserted.
    if (index > size) return;

    // [so weird] If index is negative, 
    // the node will be inserted at the head of the list.
    if (index < 0) index = 0;

    ++size;
    // find predecessor of the node to be added
    ListNode pred = head;
    for(int i = 0; i < index; ++i) pred = pred.next;

    // node to be added
    ListNode toAdd = new ListNode(val);
    // insertion itself
    toAdd.next = pred.next;
    pred.next = toAdd;
  }

  /** Delete the index-th node in the linked list, if the index is valid. */
  public void deleteAtIndex(int index) {
    // if the index is invalid, do nothing
    if (index < 0 || index >= size) return;

    size--;
    // find predecessor of the node to be deleted
    ListNode pred = head;
    for(int i = 0; i < index; ++i) pred = pred.next;

    // delete pred.next 
    pred.next = pred.next.next;
  }
}

206. 反转链表

题目描述:
在这里插入图片描述
思路:
水题,最简单的一种用一个栈来装着然后让栈弹出来一一存入旧的链表中即可。
在笔试的时候最好这样写,因为快而且无脑,但是如果是在面试的时候,那么最好用一些高级的算法,高级的算法一般都不会需要额外的空间消耗,意思就是我们必须在链表上面进行操作,而我之前在数组篇总结过,线性数据结构的高级操作操作一般都是双指针操作。
双指针操作就是创建两个指针,然后一前一后,每一次移动都改变一下当次节点的指向顺序(本来是从前往后,因为题目是要反转链表,那么就改为从后往前即可),我说的有点抽象,注释看不懂的话也可以看代码随想录上面的双指针思路分析,它有动图会好懂很多。

无脑方法:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        //看了左程云的算法课,这种题目
        //笔试时就追求时间复杂度即可,不用在意空间复杂度
        //面试时肯定就是追求空间复杂度
        
        //用一个额外栈空间来装就行
        Stack<Integer> stack = new Stack<>();
        //判断头结点是否为空
        if(head == null) return head;
        //有一个点要注意,LeetCode上的头结点是有值的
        ListNode temp = head;//工作指针temp
        while(temp != null){
            stack.push(temp.val);
            temp = temp.next;
        }
        temp = head;
        while(stack.size() > 0){
            temp.val = stack.pop();
            temp = temp.next;
        }
        return head;
    }
}

双指针方法:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode temp = null;//在后面的指针
        ListNode pre = head;//在前面的指针
        //开始操作
        while(pre != null){ //因为pre是在前面的指针,当它指向null的时候就说明遍历结束了已经
        //现在开始改变指针的指向,由从前往后改为从后往前
        //先用一个临时节点来保存一下即将要改变指向的节点的指向
        //因为我们这里其实是要断开成为两个链表的,pre.next后面已经是一条新链表了
        ListNode t = pre.next;
        //改变pre指向,让其指向前面的节点
        pre.next = temp;
        //让后面的指针总是指向反转链表后的头节点位置
        temp = pre;
        //现在又让pre重新指向原来的方向,继续前进(即第二部分的链表)
        pre = t;
        }
        return temp;
    }
}

24. 两两交换链表中的节点(中难)

题目描述:
在这里插入图片描述
思路:
用虚拟头节点法正常模拟那个过程就行。

双指针法:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
        //虚拟头结点法
        //首先判断特殊情况
        //head为空情况
        if(head == null) return head;
        //head.next为空情况
        if(head.next == null) return head;
        //开始常规判断
        ListNode dummyNode = new ListNode(0); 
        dummyNode.next = head;//虚拟头结点为dummyNode,待会儿用来返回的
        ListNode prev = dummyNode;//而prev为工作指针,此时prev为空,但它的下一个节点连接了head
        while(prev.next != null && prev.next.next != null){
            //用一个临时变量来保存不进行交换的剩余节点组成的链表
            ListNode temp = head.next.next;//先保存一下除了要交换节点之外的节点连接起来的链表
            //下面进行交换
            //虚拟头节点的下一个指向链表的第二个节点
            prev.next = head.next;
            //链表的第二个节点的指针域又指向头节点
            //(等号左边最近的next意义为指针域,等号右边最远的next意义为具体的节点)
            head.next.next = head;
            //现在链表第一个节点就该指向以第三个节点为头节点组成的部分链表了
            head.next = temp;
            //交换结束之后,指针后移
            prev = head;//头结点的工作指针指向原链表头节点(在以虚拟头节点为头结点的链表中实际上head已经成为第二个节点了)
            head = head.next; //head往后移动,重复上面做的事情
        }
        return dummyNode.next;
    }
}

19. 删除链表的倒数第N个节点

题目描述:
在这里插入图片描述
思路分析:
先通过遍历统计链表中的节点个数,然后用个数减去题目给的倒数n值,结果就是正数下来所要删除的节点的位置。
比如节点的数量为5,要删除倒数第2个位置,那么从前往后数就是第3个位置,即5-2=3。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n){
        //先判断特殊情况
        //head为空或者head.next为空且删除倒数第一个节点位置时直接返回null
        if(head == null || (head.next == null && n == 1)) return null;
        ListNode dummyNode = new ListNode(0);//伪头结点
        dummyNode.next = head;
        ListNode temp = dummyNode; //temp工作指针
        int cnt = 0; //计数器,计数节点个数
        if(n == 1){
            while(temp.next.next != null){
                cnt++;
                temp = temp.next;
            }
            temp.next = null;
            return head;
        }else{
            while(temp.next != null){
                cnt++;
                temp = temp.next;
            }
        }
        //开始删除
        //刷新temp
        temp = dummyNode;
        int cnt1 = 0; //计数器,用来判断和n的值是否相同
        while(n != 1 && temp.next != null){
            //如果遇到符合题意的,删除即可        
            if(cnt1 == cnt-n) temp.next = temp.next.next; 
            //索引+1
            cnt1++;
            //指针后移
            temp = temp.next;
        }
        return dummyNode.next;
    }
}

上面是我第一次磕磕绊绊写出来的,当时注意力不够集中就是拆西墙补东墙,然后写出来个这么玩意儿,然后发现其实思路好好捋一捋,就可以精简成下面这样。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n){
        //先拿到链表的长度
        int cnt = 1; //头结点本身也是一个节点
        ListNode temp = head;//工作指针
        while(temp.next != null){
            cnt++;
            temp = temp.next;
        }
        //构造虚拟头节点
        ListNode dummyNode = new ListNode(0);//虚拟头节点
        dummyNode.next = head; 
        temp = dummyNode;//temp工作指针
        //以例一为例,n=2,那么我们用cnt-n就等于3
        //正好可以到达我们要删除的节点前面,此时退出循环进行删除即可
        for(int i=0; i<cnt-n;i++){
            temp = temp.next;
        }
        temp.next = temp.next.next;
        return dummyNode.next;
    }
}

还有个双指针法,具体思路在LeetCode上的题解中都写的蛮清楚的,就是有一点我想了很久才理解,就是为什么快指针走到结尾时正好慢指针就能走到开始快指针所走到的位置呢?
我想明白了,以链表长为5举例:快指针先走3步,那么再走2步结束,此时慢指针跟着快指针一起走两步,那么慢指针是正好可以走到要删除的节点的前一个的。
换句话说,如果我要删倒数第2个值,那么我快指针先走2步,然后慢指针跟着快指针走三步结束,那么慢指针就正好停在倒数第2个值的前面一个值呀!
自己想想就清楚了其实。
代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n){
        //先创建虚拟头节点
        ListNode dummyNode = new ListNode(0);
        dummyNode.next = head;
        ListNode fast = dummyNode;
        ListNode slow = dummyNode;
        //现在fast和slow指针都位于虚拟头结点上
        //按照思路中讲的,先让快指针走n步
        for(int i=0;i<n;i++){
            fast = fast.next;
        }
        //然后让两个指针一起走
        while(fast.next != null){
            fast = fast.next;
            slow = slow.next;
        }
        //退出循环时,slow正好位于要删除节点的前一个位置上
        //现在执行删除即可
        slow.next = slow.next.next;
        //返回的头结点是要带值的,就和题目给的一样
        return dummyNode.next;
    }
}

02.07. 链表相交

题目描述:
在这里插入图片描述

思路分析:
这和上面那道题有点像
数学题,速度相同的情况下(即一人走一步)
A走3,B走5,然后A再走5,B再走3
此时A和B都走8,最后一定会相遇
如果有公共节点那么返回公共节点
如果没有公共节点则AB都来到中点就为null

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        //创建两个工作指针
        ListNode tempA = headA;
        ListNode tempB = headB;
        //这和上面那道题有点像
        //数学题,速度相同的情况下(即一人走一步)
        //A走3,B走5,然后A再走5,B再走3
        //此时A和B都走8,最后一定会相遇
        //如果有公共节点那么返回公共节点
        //如果没有公共节点则AB都来到中点就为null
        while(tempA != tempB){
            //如果tempA走完,那么让它开始走tempB
            //否则走一步
            if(tempA == null) tempA = headB;
            else tempA = tempA.next;

            //如果tempB走完,那么让它开始走tempA
            //否则走一步
            if(tempB == null) tempB = headA;
            else tempB = tempB.next;
        }
        return tempA;
    }
}

返回结果tempA或者tempB都行,其实结果都一样。

142. 环形链表 ||

题目描述:
在这里插入图片描述

思路分析:
双指针法,怎么说呢这就是个结论
见过就会,没见过就GG
使用快慢指针
一开始两个指针都位于head节点处
然后快指针一次走两步,慢指针一次走一步
他们一定会在环中相遇,然后让快指针回到head节点处
然后两个速度相同,都每次走一步
然后他们一定会在第一个入环节点处相遇
如果快指针走到null了那就说明为无环链表

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        //判断特殊情况
        if(head == null || head.next == null || head.next.next == null){
            return null;
        }
        //一开始两个指针都位于head节点处
        //但要先走规定的步数(因为下面的循环条件是fast!=null)
        ListNode slow = head.next;
        ListNode fast = head.next.next;
        //然后快指针一次走两步,慢指针一次走一步
        while(fast != slow){//按照上面的速度则他们一定会在环中相遇
        //判断一下fast是否会为null,为null则无环
        //因为fast是快指针,所以它肯定先到null
        if(fast.next == null || fast.next.next == null){
            return null;
        }
            fast = fast.next.next;
            slow = slow.next;
        }
        //然后让快指针回到head节点处
        fast = head;
        //现在让二者同等速度每次走一步运行
        while(fast != slow){
            slow = slow.next;
            fast = fast.next;
        }
        //退出循环时则一定是相遇在入环起始节点的
        return fast;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在地球迷路的怪兽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值