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

目录

203.移除链表元素 (不使用虚拟节点)

206.反转链表(双指针)

707.设计链表(使用虚拟节点方法


203.移除链表元素 (不使用虚拟节点)

建议: 本题最关键是要理解 虚拟头结点的使用技巧,这个对链表题目很重要。

题目链接/文章讲解/视频讲解:代码随想录

解题思路:

不使用虚拟节点来做,主要分两种情况去讨论,如下所示分析时一定先判断对哪些节点操作,首先要保证节点不能是空指针才能去对节点操作,再去判断节点的值域是否包含目标值val去进行判断即可

/**
 * 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) {

        //第一种情况:如果头节点中的值域包含target目标值val
        //由于是对头节点操作,所以要保证head不能是空指针,否则对空指针操作就会报错
        while(head != null && head.val == val){
            head = head.next;  //如果链表中就一个节点,也就是头节点还包含目标值val,则直接返回null值了
        }
        //第二种情况:如果头节点中的值域不包含target目标值val,所以head节点需要设置节点保存,第二种情况头节点就是head,因此head节点要保持不动;如果不设置节点变量保存,由于需要不断的遍历链表找到包含val值的节点并删除,所以head也是在不断向后移动的
        由于是对current节点,以及current节点指向的next域操作,所以要保证current和current.next不能是空指针,否则对空指针操作就会报错
        ListNode current = head;
        while(current != null && current.next != null){
            if(current.next.val == val){
                current.next = current.next.next;

            }else{
                current = current.next;
            }
        }
        return head;
    }
}

206.反转链表(双指针)

建议先看我的视频讲解,视频讲解中对 反转链表需要注意的点讲的很清晰了,看完之后大家的疑惑基本都解决了。

题目链接/文章讲解/视频讲解:代码随想录

题解思路:

使用双指针,定义一个pre指针和current指针(使得head节点保持不动)进行同时移动,只需要将pre节点跟着current节点不断的往右移动,同时将指向当前节点的pre赋值给current的next域,就可以实现链表的反转,所以一开始需要将pre定义为空指针,在第一次移动之前current的next域指向的就是pre的null值,然后开始移动,注意在每次移动的同时赋值的顺序,需要先用一个临时变量保存好current指向的next域,否则当pre赋值给current.next的时候,current没法再往后移动了,因为current此时已经写死了,只能是next域指向pre值的current的节点了,所以可以先把temp = current.next保存好,然后再把temp值赋给current这样就可以移动current了,同时pre也要向后移动,则直接把当下的current赋值给pre即可!!!(这里看卡哥视频很清楚讲解的其实!!!

/**
 * 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 head = new ListNode();
        ListNode pre = null;
        ListNode current = head;
        while(current != null){ //当current移动到尾部节点,此时next域指向null值时,说明已经到达链表结尾了
            ListNode temp = current.next;
            current.next = pre;  //实现next域反向赋值的操作
            pre = current;  //pre跟着向后移动一位
            current = temp; //current也要跟着往后移动一位
        }
        return pre;

    }
}

707.设计链表(使用虚拟节点方法)

建议: 这是一道考察 链表综合操作的题目,不算容易,可以练一练 使用虚拟头结点

题目链接/文章讲解/视频讲解:代码随想录

题解思路:

1、必踩的坑之一:初始化链表个数,第0个节点初始化的时候还没出生呢,因此初始化链表的个数为0,初始化的时候只有一个dummyNode虚拟节点
2、必踩的坑之二:链表中的头节点的下标视为0,使用一个虚拟节点指向头节点,真实链表中的结构:头结点(节点0)---->节点1--->节点2---->节点3........---->节点n,总共 n+ 1个节点,因此链表中的节点下标是从0开始,那么超过索引范围就会报错,即n < 0 || n > size - 1 时索引无效的!!!
- 难度虽然不大,但是坑就是太多了,一踩一个准!!!方法思路主要看注释,写的很清楚了,还需要关注的点有两个:
1、如何找到第n-1个节点操作,这个方法卡哥给的很棒,对于边界问题,可以代入第0个节点去试试!!!

while(index > 0){
        current = current.next;
        index--;
    } //循环结束后current指向的就是第n-1个节点,只要记住这种方法就好,自己肯定没能力想出来

 2、如何遍历到链表的尾部指针,这个方法卡哥给的也很棒!!!

 `while (current.next != null) {
        current = current.next;
    } //循环结束后一定是next域指向null值的尾部节点,这里while循环是逆向思维,一定要学会多维度思考`

//定义单链表
class ListNode {
    int val;
    ListNode next;

    //无参构造器
    ListNode(){
    }
    
    //有参构造器
    ListNode(int val) {
        this.val=val;
    }

}

class MyLinkedList {
    int size  = 0;  //初始化链表个数,第0个节点初始化的时候还没出生呢,因此初始化链表的个数为0,初始化的时候只有一个dummyNode虚拟节点
    ListNode dummyNode = new ListNode(); //使用虚拟节点,此时虚拟节点模拟就是头节点,来替代真实的头节点的方法对链表进行增删改操作,


    public MyLinkedList() {
        
    }

    /*
    对第n个节点操作时需要明确一下概念:
    1、链表中的头节点的下标视为0,使用一个虚拟节点指向头节点,真实链表中的结构:头结点(节点0)---->节点1--->节点2---->节点3........---->节点n,总共 n+ 1个节点
    2、第0个节点初始化的时候还没出生呢,因此初始化链表的个数为0,初始化的时候只有一个dummyNode虚拟节点
    2、链表中的节点下标是从0开始,那么超过索引范围就会报错,即n < 0 || n > size - 1 时索引无效的!!!
    3、对节点操作的步骤一定是先找到合适的节点,然后才能谈对节点的操作,思路不能乱!!!
    4、一定是要找到第n - 1处的节点才能对第n个节点进行操作next域,如果找到的是第n个节点,那么只能对第n个节点的next域进行操作,这就不是对第n个节点操作了,而是对第n + 1个节点操作next域了!!! 
    5、添加节点操作后一定要将size加1,删除节点操作后一定要将size减1,这点很容易被忽视!!!!
    */

    /*
    ***操作,你个临时变量current定义在类里面当属性,就离谱还是个类似零作用的临时变量,你几个意思啊,每次加载不同方法时不得重新归零
    */

    //一、获取链表中下标为 index 的节点的值,下标为0的节点就是第0个节点,下标为1的节点就是第1个节点
    public int get(int index) {

        ListNode current = dummyNode.next; //踩过的坑!!!!!!!!!!在获取指定Index位置的值时,current一定是从dummyNode.next开始遍历的!!!!

        if(index < 0 || index > size - 1 ){  //踩过的坑!!!!!!!!!!!!!!index > size - 1,这里一定是size - 1,因为初始化的时候size = 0,添加第一个一个节点的时候才下标为0的head节点(此时size = 1且下标为0),依次往后类推:添加第二个节点下标为1(此时size = 2,且下标依次为0、1)......
            return -1;
        }
        //n > 0值时,循环执行条件为真,执行方法体,当n == 0时,不进入循环体。假设对第0个进行操作,则只需要对虚拟节点dummyNode操作即可,因此该while循环能够找到第n - 1个节点的位置
        while(index > 0){ 
            current = current.next;
            index--;
        } //循环结束后current指向的就是第n-1个节点,只要记住这种方法就好,自己肯定没能力想出来
        return current.val;
    }
    
    //二、将一个值为 val 的节点插入到链表中第一个元素之前。根据题意对第1个节点操作,只需要对头节点操作即可,注意函数内部节点赋值的顺序是重点!!(在插入完成后,新节点会成为链表的第一个节点,这是水到渠成的事不用管)
    public void addAtHead(int val) {

        ListNode newNode = new ListNode(val);
        newNode.next = dummyNode.next;
        dummyNode.next = newNode;
        size++;

    }

    //三、将一个值为 val 的节点追加到链表中作为链表的最后一个元素。根据题意对尾部头节点操作,只需要找到尾部节点位置再赋值即可
    public void addAtTail(int val) {

        ListNode current = dummyNode; 
        ListNode newNode = new ListNode(val);
        while (current.next != null) {
            current = current.next;
        } //循环结束后一定是next域指向null值的尾部节点,这里while循环是逆向思维,一定要学会多维度思考
        current.next = newNode;
        size++;
    }
    
    //四、将一个值为 val 的节点插入到链表中下标为 index 的节点之前。(在第n个节点插入节点,还是需要找到第n - 1个节点处的位置对其进行正确赋值操作即可)
    //如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将不会插入到链表中。
    public void addAtIndex(int index, int val) {

        ListNode current = dummyNode;
        ListNode newNode = new ListNode(val);
        if(index > size ){
            return;
        }
        while(index > 0){
            current = current.next;
            index--;
        } //循环体结束后,定位到的一定是第n - 1处的节点,然后对第n - 1处的节点操作即可
        newNode.next = current.next;
        current.next = newNode;
        size++; //注意添加节点后一定要将其size加1操作
        
    }
    
    //五、如果下标有效,则删除链表中下标为 index 的节点。
    public void deleteAtIndex(int index) {

         ListNode current = dummyNode;
        if(index < 0 || index > size - 1 ){  /!!!!!!!!!!!!!!!!!!!!!!!!!
            return;
        }
        while(index > 0){ 
            current = current.next;
            index--;
        }//循环体结束后一定是第n - 1处的节点位置
        current.next = current.next.next;
        size--;
    }
}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList obj = new MyLinkedList(); 
 * int param_1 = obj.get(index);
 * obj.addAtHead(val);
 * obj.addAtTail(val);
 * obj.addAtIndex(index,val);
 * obj.deleteAtIndex(index);
 */

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值