Day3:(链表)|203.移除链表元素 707.设计链表 206.反转链表

本文详细介绍了解决链表问题的三种核心方法:移除指定元素、设计实现链表及反转链表。通过具体实例讲解了双指针法和递归法,并强调了虚拟头节点的应用技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

203.移除链表元素

  • 题目链接203.移除链表元素
  • 解题思路:从头到尾遍历链表,遇到需要删除的元素时将其删除即可
  • 主要难点
    • 设置一个虚拟头节点:使得整个链表中的每一个节点都可以用统一的方式进行遍历和删除,说白了就是给原来的头节点也设置一个前驱,这样每个节点就都有前驱了,头节点不用单独拿出来作为一种特殊情况
    • 最后返回值的设置: 题目要求最后返回新的链表的头节点,在最后返回的时候并不是直接返回cur,而是返回的虚拟头节点的后继节点fake.next。可能是因为cur有可能为null(?)
  • 解题步骤
    • 定义虚拟头节点fake,值设为-1,next节点设为原链表的头节点
    • 定义pre和cur两个用来指示当前位置状态的节点,最开始的时候cur从原链表头节点head开始,则pre从定义的虚拟头节点fake开始
    • 开始循环遍历,终止条件为当前节点cur为null,在遍历过程中若发现了当前节点对应值与要删除的目标值相同,则执行链表删除操作pre.next = cur.next,否则令pre=cur,并将cur向后移一位,直到循环结束
    • 最终返回虚拟头节点的后继节点
  • 解题时间:10min+12min(博客)
  • 代码
/**
 * 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 fake = new ListNode(-1,head);//统一设置一个虚拟头节点,值为-1,后继节点原来的头节点
        ListNode pre = fake;
        ListNode cur = head;
        while(cur != null) {
            if(cur.val == val){
                //需要删除当前节点
                pre.next = cur.next;
            }else{
                pre = cur;
            }
            cur = cur.next;
        }
        return fake.next;
    }
}

707.设计链表

  • 题目链接707.设计链表
  • 解题思路:不涉及太多算法,就是自己实现一个链表的增删获取操作,需要自己定义链表结构
  • 主要难点:也设置一个虚拟头节点,理由同上,用起来方便
  • 解题步骤
    • 获取:判断合法性后,从虚拟头节点开始后移index位,取得当前对应值返回
    • 任意位置插入:几种情况要都考虑到,大于index不插直接return,小于index插头部,等价于index=0,其他情况正常插,从虚拟头节点移动到要插入位置的前一个位置上,新建节点进行插入,记得size++
    • 头插入、尾插入:是任意位置插入的特殊情况,直接调用
    • 删除:判断合法性后,从虚拟头节点开始后移index位,删除节点
  • 解题时间:35min+6min(博客)
  • 代码
class MyLinkedList {
    int size;
    ListNode head;//设置虚拟头节点
    public MyLinkedList() {
        size = 0;
        head = new ListNode(0);
    }
    
    public int get(int index) {
        //获取第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方法实现
        addAtIndex(0,val);
    }
    
    public void addAtTail(int val) {
        //可以复用addAtIndex方法实现
        addAtIndex(size,val);
    }
    
    public void addAtIndex(int index, int val) {
        //index大于链表长度时不插入
        if(index>size){
            return;//链表
        }
        //index小于0在头部插
        if(index<0){
            index=0;
        }
        //index等于链表长度时在末尾加,这部分不用写代码了
        size++;//插完长度加1,先更新长度
        //先移动到index插入位置去
        ListNode pre = head;//从虚拟头节点开始
        for(int i=0;i<index;i++){
            pre = pre.next;//后移,最后移动到要插入的前一个位置上,即在pre的后面插add
        }
        ListNode add = new ListNode(val);//定义新插入的节点
        //把节点放在pre后面
        add.next=pre.next;
        pre.next = add;
    }
    
    public void deleteAtIndex(int index) {
        //先判断索引是否有效
        if(index<0||index>=size){
            return;//啥也不干
        }else{
            //删除节点
            size--;//删完长度减1,先减
            ListNode pre = head;//从虚拟头节点开始
            for(int i=0;i<index;i++){
            pre = pre.next;//后移,删除pre后面的元素
            }
            pre.next=pre.next.next;//删除pre后面的元素
        }

    }
}
class ListNode {
    int val;
    ListNode next;
    ListNode(){}
    ListNode(int val) {
        this.val=val;
    }
}

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

206.反转链表

  • 题目链接206.反转链表
  • 解题思路
    • 方法1:双指针法,逆置每一个指向next的方向,这种方法理解起来比较简单,需要用到两个指针和一个用来保存后项的临时变量,两两一对逆置即可
    • 方法2:递归,
  • 主要难点:双指针,递归
  • 解题步骤:(双指针为例)
    • 新建两个指针,pre指针指向头部前一个null,cur指针指向当前头部head,再新建一个temp用来存放后项
    • 循环执行:先保存cur的下一位到temp,然后将cur的后项逆置转为前项pre,接着cur和pre向右移动,重复上述过程
    • 当cur为空时,即走完整个链表停止
  • 解题时间:10min(双指针)+10min(递归)+5(博客)
  • 代码
    双指针:
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        ListNode temp = null;
        while(cur != null) {
            temp = cur.next;//保存一下下一位
            cur.next=pre;//让当前的指向它前一个
            pre=cur;//pre后移
            cur=temp;//cur后移
        }
        return pre;
    }
}

递归:

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

    private ListNode reverse(ListNode prev, ListNode cur) {
        if (cur == null) {
            return prev;
        }
        ListNode temp = null;
        temp = cur.next;// 先保存下一个节点
        cur.next = prev;// 反转
        // 更新prev、cur位置
        // prev = cur;
        // cur = temp;
        return reverse(cur, temp);
    }
}

总结

今天三道题都是链表题,主要复习链表的定义,一些增删获取基础操作的手动实现,还有就是链表元素的操作。用到比较多的方法是虚拟头节点和双指针法。其中虚拟头节点是链表类题常用的技巧,自己定义一个虚拟的头节点就可以使得整个链表的操作一致,避免出现特殊情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值