力扣刷题之链表(1) | 203. 移除链表元素、707. 设计链表、206. 反转链表

Leetcode 203. 移除链表元素

题目链接

思路

由于链表的结构特殊性,删除头部结点和删除中间或尾部节点的操作有所不同。
当删除链表的中间或尾部节点时,我们直接将该结点的前一个结点的next指向该结点的下一个结点。而删除链表的头部结点时,则需要让头部结点指向该结点的下一个结点,从而作为新的头结点。
如此看来,删除结点时,还需要判断该点是否是头结点,下面介绍虚拟头结点方法,可以做到统一删除。
也就是在头部结点之前,再加入一个DummyHead。此时再删除头结点时,只需要将虚拟头结点直接指向头结点

代码(Java)

写法1:分情况(是否为头结点)

时间复杂度 O(n)
空间复杂度 O(1)

/**
 * 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;
        }

        //链表为null,直接返回头结点
        if (head==null){
            return head;
        }

        ListNode pre = head;
        ListNode cur = head.next;
        while (cur!=null){
            if (cur.val==val){
                pre.next=cur.next;
            }else {
                pre=cur;
            }
            cur=cur.next;
        }
        return head;
    }
}

写法2:虚拟头结点

时间复杂度 O(n)
空间复杂度 O(1)

/**
 * 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) {
        //当头结点为空时,直接返回头结点
        if (head==null)
            return head;

        ListNode dummyHead = new ListNode(-1,head);//添加虚拟头结点,next指向原本链表的头结点
        ListNode pre = dummyHead;
        ListNode cur = head;
        while (cur!=null){
            if(cur.val==val){
                pre.next=cur.next;
            }else {
                pre=cur;
            }
            cur=cur.next;
        }
        return dummyHead.next;
    }
}

代码随想录链接

下面是代码随想录的文章链接与视频链接,帮助大家更好地理解这道题。
文章
视频

Leetcode 707. 设计链表

题目链接

思路

这道题帮助大家更好地巩固链表的学习,基于203的移除链表元素,可以更全面地学习链表的各种操作。这里采用虚拟头结点的方式解题,在链表题中常用。

代码(Java)

//单链表
class ListNode{
    int val;
    ListNode next;
    ListNode(){}
    ListNode(int val){
        this.val=val;
    }
}
class MyLinkedList {
    int size;
    ListNode head;//虚拟头结点

    public MyLinkedList() {
        size=0;
        head=new ListNode(0);
    }

    public int get(int index) {
        if (index<0||index>=size)
            return -1;
        ListNode cur=head;
        //包含一个虚拟头节点,所以查找第index+1个节点
        for (int i=0;i<index+1;i++){
            cur=cur.next;
        }
        return cur.val;
    }

    public void addAtHead(int val) {
        addAtIndex(0,val);
    }

    public void addAtTail(int val) {
        addAtIndex(size,val);
    }

    public void addAtIndex(int index, int val) {
        if (index>size){
            return;//如果index大于链表的长度,则返回空
        }
        if (index<0){
            index=0;//若果index小于0,则插入到链表头结点之前,作为新的头结点
        }
        size++;

        ListNode pre = head;
        for (int i=0;i<index;i++){//拿到插入位置之前的结点
            pre=pre.next;
        }
        ListNode temp= new ListNode(val);//定义一个新结点存放插入值
        temp.next=pre.next;
        pre.next=temp;
    }

    public void deleteAtIndex(int index) {
        if (index<0 || index>size-1){
            return;//如果index小于0或者大于等于链表的长度,则返回空
        }
        size--;
        if (index==0){
            head=head.next;
            return;
        }
        
        ListNode pre=head;
        for (int i=0;i<index;i++){//拿到删除位置之前的结点
            pre=pre.next;
        }
        pre.next=pre.next.next;//让删除位置之前的结点指向删除位置的下一个结点
    }
}

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

Leetcode 206. 反转链表

题目链接

思路

这道题需要将链表反转,即将原来每个结点的next指向该结点的前一个结点
所以很容易想到双指针的解法,一个指针cur用来遍历所有的链表结点,另一个指针pre用来指向该结点的前一个结点
递归写法的整体思想和双指针解法相同,建议结合双指针写法来理解。

难点

初始化两个指针时,cur指针定义为headpre指针定位为null

  • 当遍历链表时,终止条件为cur==null,所以while()中应填入cur!=null。
  • 当进入循环时,需要重新定义pre、cur指针,此时cur.next指向pre,如何再移动cur指针向后呢?所以还需要再添加一个临时指针temp,用来指向cur的下一个结点
  • 先移动cur还是先移动pre?
    在让cur.next指向pre之前,将cur.next保存为temp指针,随后让cur.next指向pre。上述的过程完成了一个结点的反转,此时两个指针都需向后移动一位。如果先移动cur指针,也就是让cur=temp,那么pre指针无法移动到原来的cur指针。所以,应该先移动pre指针

代码(Java)

写法1:双指针法

/**
 * 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 cur = head;
        ListNode pre = null;
        while (cur!=null){
            ListNode temp = cur.next;//临时指针存放cur.next,为了方便cur指针的移动
            cur.next=pre;
            pre=cur;
            cur=temp;
        }
        return pre;
    }
}

写法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 {
     private ListNode reverse(ListNode cur,ListNode pre){
        if (cur==null){
            return pre;
        }
        ListNode temp = cur.next;
        cur.next=pre;
        return reverse(temp,cur);
    }

    public ListNode reverseList(ListNode head) {
       return reverse(head,null);
    }
}

代码随想录链接

下面是代码随想录的文章链接与视频链接,帮助大家更好地理解这道题。
文章
视频

总结

今天的三道题前两道的重点都在于巩固链表的基础操作,体会到了虚拟头结点dummy head的“香”,添加后对于删除链表结点可以统一处理。最后一题的反转链表的temp指针设计很巧妙,双指针解法在多种数据结构中都有应用,其重点还是在于理解两个指针各自的任务是什么,建议多画草图,一步步理解。另外虽然递归的写法看着很简洁,但也是基于双指针法的思想来的,直接看晦涩难懂,但是写出双指针解法后,递归写法也能轻松写出。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Fuego91

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

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

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

打赏作者

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

抵扣说明:

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

余额充值