代码随想录-Day03

203. 移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
在这里插入图片描述

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return head;
        }
        head.next = removeElements(head.next, val);
        return head.val == val ? head.next : head;
    }
}

这段代码是用Java编写的,它定义了一个名为Solution的类,其中包含一个方法removeElements。这个方法用于从一个链表中移除所有值为val的节点。链表节点的定义(尽管没有直接展示,但根据上下文可以推断)通常如下:

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

现在让我们详细解析removeElements方法的工作原理:

方法签名

public ListNode removeElements(ListNode head, int val)
  • ListNode head: 这是链表的头节点。
  • int val: 要从链表中移除的元素值。

方法逻辑

  1. 基本情况检查:

    if (head == null) {
        return head;
    }
    

    如果链表为空(头节点为null),则直接返回null,无需处理。

  2. 递归移除后续节点中的目标值:

    head.next = removeElements(head.next, val);
    

    这行代码递归地对链表的下一个节点调用removeElements方法,目的是先处理链表的剩余部分,逐步向上传递处理后的链表头。

  3. 处理当前节点:

    return head.val == val ? head.next : head;
    
    • 如果当前节点的值head.val等于要移除的值val,则返回head.next,实际上就是跳过了当前节点,实现了移除操作。
    • 如果当前节点的值不等于val,则保持当前节点不变,直接返回head

递归过程解释

这个方法采用递归的方式,从链表的末尾开始向前处理每个节点,通过修改指针实现节点的移除(而非真正意义上的“删除”)。当递归调用返回到最上层时,整个链表中所有值为val的节点都被“跳跃”过去,从而实现了从链表中移除这些节点的目的。

这种方法的优点是逻辑简洁,递归天然处理了链表的遍历。但需要注意,如果链表非常长,可能会导致较大的栈空间消耗。

为了将 removeElements 方法改成符合ACM竞赛模式,我们需要调整代码以使用标准输入和输出,并且定义 ListNode 类。以下是修改后的代码:

import java.util.Scanner;

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

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 读取链表的元素个数
        int n = scanner.nextInt();

        // 读取链表的元素
        ListNode dummy = new ListNode(0);
        ListNode current = dummy;
        for (int i = 0; i < n; i++) {
            current.next = new ListNode(scanner.nextInt());
            current = current.next;
        }

        // 读取要移除的元素的值
        int val = scanner.nextInt();

        // 创建Solution对象并调用removeElements方法
        Solution solution = new Solution();
        ListNode result = solution.removeElements(dummy.next, val);

        // 输出结果链表
        current = result;
        while (current != null) {
            System.out.print(current.val + " ");
            current = current.next;
        }
        System.out.println();
    }
}

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return head;
        }
        head.next = removeElements(head.next, val);
        return head.val == val ? head.next : head;
    }
}

说明

  1. 输入格式

    • 第一行输入一个整数 n,表示链表的元素个数。
    • 接下来的一行输入 n 个整数,表示链表的元素。
    • 最后一行输入一个整数 val,表示要移除的元素的值。
  2. 输出格式

    • 输出链表中移除所有值为 val 的元素后的链表,每个元素之间用空格隔开。

使用示例

假设输入如下:

5
1 2 6 3 4
6

程序会输出:

1 2 3 4 

因为链表 [1, 2, 6, 3, 4] 中移除所有值为 6 的元素后,结果链表为 [1, 2, 3, 4]

你可以将这段代码保存到一个文件中,比如 Main.java,然后在命令行中使用 javac Main.java 进行编译,使用 java Main 运行,并在运行时通过标准输入提供数据。

707. 设计链表

你可以选择使用单链表或者双链表,设计并实现自己的链表。

单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。

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;
        for (int i = 0; i <= index; 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 = Math.max(0, index);
        size++;
        ListNode pred = head;
        for (int i = 0; i < index; i++) {
            pred = pred.next;
        }
        ListNode toAdd = new ListNode(val);
        toAdd.next = pred.next;
        pred.next = toAdd;
    }

    public void deleteAtIndex(int index) {
        if (index < 0 || index >= size) {
            return;
        }
        size--;
        ListNode pred = head;
        for (int i = 0; i < index; i++) {
            pred = pred.next;
        }
        pred.next = pred.next.next;
    }
}

class ListNode {
    int val;
    ListNode next;

    public ListNode(int val) {
        this.val = val;
    }
}

这段代码定义了一个自定义的链表类MyLinkedList,它是基于单链表结构实现的,其中包含了一些基本操作方法,如添加、删除、获取链表中的元素等。同时定义了一个内部类ListNode,用于表示链表中的每一个节点。下面是这个代码的详细解释:

类定义

  • MyLinkedList

    • size:记录链表中的元素个数。
    • head:虚拟头节点,其值设为0,不存储实际数据,便于插入和删除操作的统一处理。
  • ListNode(内部类):

    • val:节点存储的值。
    • next:指向下一个节点的引用。

方法说明

  • 构造函数 MyLinkedList
    初始化链表,设置size为0,创建一个值为0的虚拟头节点。

  • get(int index)
    根据索引获取链表中的元素。若索引越界,返回-1;否则遍历至指定位置返回节点值。

  • addAtHead(int val)
    在链表头部添加元素。调用addAtIndex(0, val)实现。

  • addAtTail(int val)
    在链表尾部添加元素。调用addAtIndex(size, val)实现。

  • addAtIndex(int index, int val)
    在指定位置插入元素。首先判断插入位置是否合法,然后更新链表大小,找到插入位置的前驱节点,创建新节点并插入链表中。

  • deleteAtIndex(int index)
    删除指定位置的元素。检查索引合法性,然后减小链表大小,找到待删除节点的前驱节点,调整指针跳过待删除节点。

特点

  • 使用虚拟头节点简化边界条件处理,尤其是在插入和删除操作中。
  • 提供了基础的链表操作,包括查询、插入和删除,满足基本的数据结构需求。
  • 通过索引进行操作,时间复杂度主要取决于索引值,平均情况下为O(n),其中n为链表长度。

206. 反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
在这里插入图片描述

方法一:迭代

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }
}

这段代码是用Java编写的,它实现了一个名为Solution的类,该类中有一个公共方法reverseList。这个方法的功能是反转一个给定的单链表。链表的节点定义(尽管没有直接在代码中展示,但可以根据上下文推断)通常如下:

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

现在,让我们详细解析reverseList方法的工作原理:

方法签名

public ListNode reverseList(ListNode head)
  • ListNode head:这是链表的头节点。

方法逻辑

  1. 初始化指针:

    ListNode prev = null;
    ListNode curr = head;
    
    • prev 初始化为 null,它将用于保存当前节点的前一个节点。
    • curr 初始化为 head,即当前处理的节点。
  2. 循环反转链表:

    while (curr != null) {
        ListNode next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    
    • 在当前节点 curr 不为 null 的情况下执行循环。
    • next 临时存储 curr 的下一个节点,因为在改变 curr.next 之前需要保留这个信息。
    • 然后,将 currnext 指针指向前一个节点 prev,实现了单链表节点间的指向反转。
    • 更新 prevcurr,使它们分别向前移动一位,继续处理下一个节点,直到链表末尾。
  3. 返回反转后的头节点:

    return prev;
    

    当循环结束时,prev 指向了原链表的最后一个节点,因为原链表的头节点变成了新链表的尾节点,所以返回 prev 作为新链表的头节点。

总结

这个方法通过一次遍历实现了链表的反转,时间复杂度为O(n),其中n是链表的长度。它只使用了常数级别的额外空间,因此空间复杂度为O(1)。这种方法简洁高效,是反转链表的经典解法之一。

为了将 reverseList 方法改成符合ACM竞赛模式,我们需要调整代码以使用标准输入和输出,并定义 ListNode 类。以下是修改后的代码:

import java.util.Scanner;

class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
    }
}

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 读取链表的元素个数
        int n = scanner.nextInt();

        // 创建链表并读取链表元素
        ListNode dummy = new ListNode(0);
        ListNode current = dummy;
        for (int i = 0; i < n; i++) {
            current.next = new ListNode(scanner.nextInt());
            current = current.next;
        }

        // 创建Solution对象并调用reverseList方法
        Solution solution = new Solution();
        ListNode result = solution.reverseList(dummy.next);

        // 输出结果链表
        current = result;
        while (current != null) {
            System.out.print(current.val + " ");
            current = current.next;
        }
        System.out.println();
    }
}

说明

  1. 输入格式

    • 第一行输入一个整数 n,表示链表的元素个数。
    • 接下来的一行输入 n 个整数,表示链表的元素。
  2. 输出格式

    • 输出反转后的链表,每个元素之间用空格隔开。

使用示例

假设输入如下:

5
1 2 3 4 5

程序会输出:

5 4 3 2 1

因为链表 [1, 2, 3, 4, 5] 反转后,结果链表为 [5, 4, 3, 2, 1]

你可以将这段代码保存到一个文件中,比如 Main.java,然后在命令行中使用 javac Main.java 进行编译,使用 java Main 运行,并在运行时通过标准输入提供数据。

方法二:递归

class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode newHead = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}

这段代码同样实现了链表的反转功能,不过采用了递归的方式。这段代码也是在Solution类中定义了一个方法reverseList,其参数为链表的头节点head。下面是这个递归方法的详细解析:

基本情况(Base Case):

if (head == null || head.next == null) {
    return head;
}
  • 当链表为空(head == null)或者链表只有一个节点(head.next == null,即没有next节点)时,直接返回当前的head,因为不需要反转。

递归步骤(Recursive Case):

ListNode newHead = reverseList(head.next);
  • head的下一个节点head.next进行递归调用reverseList,这一步会递归地反转以head.next为头节点的子链表,并返回反转后的新头节点,记为newHead

反转当前节点:

head.next.next = head;
head.next = null;
  • 当递归返回后,将当前节点head的下一个节点的next指针(即head.next.next)指向当前节点head,完成当前节点与其后继节点之间方向的反转。
  • 然后将当前节点headnext指针设为null,断开与原后继节点的连接,这在递归过程中是必要的,因为每个节点在反转过程中都将变成尾节点。

返回结果:

return newHead;
  • 最终返回递归调用后得到的新头节点newHead,当递归层层返回时,就构建出了完全反转的链表。

总结

此递归方法通过将问题分解为越来越小的子问题(每次处理链表的剩余部分)来实现链表的反转,直至达到基本情况。递归解法的时间复杂度同样是O(n),其中n为链表的长度,因为它每个节点都会被访问一次。空间复杂度也是O(n),因为在递归调用栈上会有n层深度,每层调用对应链表的一个节点。尽管递归解法在某些情况下可能不如迭代解法节省空间,但它提供了另一种理解和实现链表反转的优雅方式。

为了将 reverseList 方法改成符合ACM竞赛模式,我们需要调整代码以使用标准输入和输出,并定义 ListNode 类。以下是修改后的代码:

import java.util.Scanner;

class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
    }
}

class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode newHead = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 读取链表的元素个数
        int n = scanner.nextInt();

        // 创建链表并读取链表元素
        ListNode dummy = new ListNode(0);
        ListNode current = dummy;
        for (int i = 0; i < n; i++) {
            current.next = new ListNode(scanner.nextInt());
            current = current.next;
        }

        // 创建Solution对象并调用reverseList方法
        Solution solution = new Solution();
        ListNode result = solution.reverseList(dummy.next);

        // 输出结果链表
        current = result;
        while (current != null) {
            System.out.print(current.val + " ");
            current = current.next;
        }
        System.out.println();
    }
}

说明

  1. 输入格式

    • 第一行输入一个整数 n,表示链表的元素个数。
    • 接下来的一行输入 n 个整数,表示链表的元素。
  2. 输出格式

    • 输出反转后的链表,每个元素之间用空格隔开。

使用示例

假设输入如下:

5
1 2 3 4 5

程序会输出:

5 4 3 2 1

因为链表 [1, 2, 3, 4, 5] 反转后,结果链表为 [5, 4, 3, 2, 1]

你可以将这段代码保存到一个文件中,比如 Main.java,然后在命令行中使用 javac Main.java 进行编译,使用 java Main 运行,并在运行时通过标准输入提供数据。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值