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

文章讲述了链表基础操作,包括移除链表元素时的错误分析与修复,链表设计中的伪头节点概念,以及反转链表的双指针法和递归实现。作者反思了编程过程中的问题和学习心得。

题目和思路:

链表基础:

复习定义链表结点:

class ListNode:
    def __init__(self, val, next=None):
        self.val = val
        self.next = None

203.移除链表元素 

循环遍历链表,每次循环需要查看next的val值,如果等于目标值,将当前节点的next指向再后面一个节点,跳过当前节点的next。由于每次查看的下一个节点的目标值,所以定义虚拟头节点,用于防止头节点的val值等于目标值。最终返回虚拟头节点的next即可。代码如下:

class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
        flag = ListNode(next = head)
        cur = flag

        while cur.next:
            if cur.next.val == val:
                cur.next = cur.next.next
            else: cur = cur.next

        return flag.next

实际写代码过程中,犯了如下错误:

while cur.next:
    if cur.next.val == val:
        cur.next = cur.next.next
    cur = cur.next

 这样写错误的原因是,不管删没删cur.next,cur都会向后移动一次,那么假如cur.next被删除,cur会直接跳到cur.next.next而不会管cur.next.next是否是目标值也不会检查它是不是None,会漏删、以及链表尾部是目标值时会报NoneType没有next方法的错误。所以必须只有不删cur.next的时候才能往后移动cur,删除了就原地呆着等下一轮循环。

707.设计链表

很糟心,代码如下:

class ListNode:
    def __init__(self, val = 0, next = None):
        self.val = val
        self.next = next

class MyLinkedList:

    def __init__(self):
        self.head = ListNode() #这是一个虚拟头节点

    def get(self, index: int) -> int:
        if index < 0: return -1
        cur = self.head.next
        cnt = 0
        while cur:
            if cnt == index:
                return cur.val
            else:
                cur = cur.next
                cnt += 1
        return -1

    def addAtHead(self, val: int) -> None:
        node = ListNode(val)
        node.next = self.head.next
        self.head.next = node

    def addAtTail(self, val: int) -> None:
        cur = self.head
        while cur.next:
            cur = cur.next
        cur.next = ListNode(val)

    def addAtIndex(self, index: int, val: int) -> None:
        node = ListNode(val)
        cnt = 0
        cur = self.head

        while cur.next:
            if cnt == index:
                node.next = cur.next
                cur.next = node
                break
            else:
                cur = cur.next
                cnt += 1
        
        if cnt == index:
            cur.next = node

    def deleteAtIndex(self, index: int) -> None:
        cur = self.head
        cnt = 0
        while cur.next:
            if cnt == index:
                cur.next = cur.next.next
                break
            else:
                cur = cur.next
                cnt += 1

第一遍完全错误的点是,把初始化的head当成了头节点,结果直接头部插入节点直接会变成1->0(head),然后把head赋给了1,整一个理解错误。

第二遍错的点是在插入环节,node插在cur和cur.next之间,我写成了node.next =cur.next.next, cur.next = node.next,整段垮掉。

第三遍错在delete,本题跟上题有区别,本题删除的是index处的值,没有在if条件下删除后面加break跳出循环,导致把后面全删完了...

这里贴一段比较好的代码,下次可以参考:

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

class MyLinkedList:
    def __init__(self):
        self.size = 0
        self.head = ListNode(0)  # Sentinel node as pseudo-head

    def get(self, index):
        if index < 0 or index >= self.size:
            return -1
        current = self.head
        for _ in range(index + 1):
            current = current.next
        return current.val

    def addAtHead(self, val):
        self.head.next = ListNode(val, self.head.next)
        self.size += 1

    def addAtTail(self, val):
        self.size += 1
        new_node = ListNode(val)
        current = self.head
        while current.next:
            current = current.next
        current.next = new_node

    def addAtIndex(self, index, val):
        if index > self.size:
            return
        index = max(0, index)
        self.size += 1
        pred = self.head
        for _ in range(index):
            pred = pred.next
        to_add = ListNode(val)
        to_add.next = pred.next
        pred.next = to_add

    def deleteAtIndex(self, index):
        if index < 0 or index >= self.size:
            return
        self.size -= 1
        pred = self.head
        for _ in range(index):
            pred = pred.next
        pred.next = pred.next.next

for循环的好处是到位置就停,然后开始操作!学!

206. 反转链表

1、双指针法,左指针指向左边反转好的内容,右指针指向还没反转的部分。假如右指针指到None,迭代终止了,返回左指针即可。如果右指针不为None,那么先记录一下cur.next,记为temp。然后将右指针指向的节点连接到左指针指向的节点,cur.next = prev,然后把左指针移动到cur,cur移动到temp进行下一轮迭代。代码如下:

class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        prev, cur = None, head
        while cur:
            temp = cur.next
            cur.next = prev
            prev = cur
            cur = temp
        return prev

2、递归法一(尾递归):根据上述双指针法写的递归,需要新建一个函数,reverse,传入左指针和右指针的指向,原主函数用于初始化。递归终止的情况就是右指针指向None,左指针就指向了反转好的头节点,递归结束反转完毕。

一般情况下,也就是从头开始,把右指针的next保存一下,把右指针指向左指针节点,完成了局部反转,返回调用自身,相当于把左指针和右指针向右移了一下,只不过是用cur和temp表示。

举个栗子:假设我们有一个链表1->2->3->None,我们要使用这个递归方法来反转它。

  • 初始调用
    • reverse(1, None)
    • cur 指向 1, prev 是 None
  • 递归调用层级 1
    • 在 reverse 函数内部:
      • temp = cur.next 保存了 2
      • cur.next = prev 使得 1 指向 None(断开 1 -> 2,现在 1 -> None)
      • 调用 reverse(2, 1)
  • 递归调用层级 2
    • reverse(2, 1)
    • cur 指向 2, prev 指向 1
    • 在 reverse 函数内部:
      • temp = cur.next 保存了 3
      • cur.next = prev 使得 2 指向 1(断开 2 -> 3,现在 2 -> 1 -> None)
      • 调用 reverse(3, 2)
  • 递归调用层级 3
    • reverse(3, 2)
    • cur 指向 3, prev 指向 2
    • 在 reverse 函数内部:
      • temp = cur.next 保存了 None
      • cur.next = prev 使得 3 指向 2(现在 3 -> 2 -> 1 -> None)
      • 调用 reverse(None, 3)
  • 递归调用层级 4
    • reverse(None, 3)
    • cur 是 None, prev 指向 3
    • 基本情况满足,返回 prev,即 3

class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        return self.reverse(head, None)
    
def reverse(self, cur, prev):
        #基本情况,右指针指向None,直接返回左指针就是已经反转好的链表。
        if cur == None:
            return prev        
        #递归反转
        temp = cur.next
        cur.next = prev
        return self.reverse(temp,cur)

3、递归法二:(头递归):基本情况是当前的链表只有一个节点或者是空,那就直接返回原链表head。递归地反转head之后的节点,返回为reversed_head(这是反转好的头节点,也就是原链表的尾节点!!),这时候head.next是反转后最后一个节点,把他指向head,再给head断开连接,就完成了反转,这时只要继续返回reversed_head即可。

举个栗子:假设我们有一个链表1->2->3->4->None,我们要使用这个递归方法来反转它。

  1. 递归调用层级 1

    • head 指向 1
    • 递归调用 reverseList 以反转 2 -> 3 -> 4 -> None
  2. 递归调用层级 2

    • head 指向 2
    • 递归调用 reverseList 以反转 3 -> 4 -> None
  3. 递归调用层级 3

    • head 指向 3
    • 递归调用 reverseList 以反转 4 -> None
  4. 递归调用层级 4

    • head 指向 4
    • 到达基本情况:head.nextNone,所以返回 head(即 4

  递归返回并反转链接

  1. 返回到递归调用层级 3

    • head3
    • 执行 head.next.next = head(即 4 -> 3
    • 设置 head.next = None(断开 3 -> 4,现在 4 -> 3 -> None
    • 返回新的头节点 4
  2. 返回到递归调用层级 2

    • head2
    • 执行 head.next.next = head(即 3 -> 2
    • 设置 head.next = None(断开 2 -> 3,现在 4 -> 3 -> 2 -> None
    • 返回新的头节点 4
  3. 返回到递归调用层级 1

    • head1
    • 执行 head.next.next = head(即 2 -> 1
    • 设置 head.next = None(断开 1 -> 2,现在 4 -> 3 -> 2 -> 1 -> None
    • 返回新的头节点 4
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        # 基本情况:空链表或只有一个节点
        if head is None or head.next is None:
            return head

        # 递归反转除了头节点外的子链表
        reversed_head = self.reverseList(head.next)

        # 将原始头节点连接到新反转链表的末尾
        head.next.next = head
        head.next = None

        # 返回新的头节点
        return reversed_head

今日总结:

以前本科时候就很讨厌链表,因为常常出错。但是逃避是没有用哒!尽管今天还是很多错误,但是找出了错误的原因,并且从这些错误中吸取到了教训,下次也许会好一些。

尽管今天没有额外的题目,但是第二题和第三题让我头大。第二题主要是对类和链表的理解有问题,太久没有写代码生疏了很多很多,一遍一遍回滚直到AC,在debug中成长...

第三题双指针方法很容易就写出来了,对两种递归方法一直没有理清头绪。两种方法截然相反,实在很影响理解。第一种尾递归实际就是迭代,第二种头递归是在递归返回的时候进行反转操作,有很大区别,理解head.next.next=head成为关键。

由于提供的引用中未涉及Java实现移除链表元素时main方法中的输入处理的相关内容,下面是一个示例代码展示该场景下main方法的输入处理方式。此示例假设输入是一系列整数表示链表元素,以及一个整数表示要移除元素。 ```java import java.util.Scanner; // 定义链表节点类 class ListNode { int val; ListNode next; ListNode(int x) { val = x; } } public class RemoveLinkedListElements { public static ListNode removeElements(ListNode head, int val) { // 创建一个虚拟头节点 ListNode dummy = new ListNode(0); dummy.next = head; ListNode prev = dummy; ListNode curr = head; while (curr != null) { if (curr.val == val) { prev.next = curr.next; } else { prev = curr; } curr = curr.next; } return dummy.next; } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 读取链表元素,假设输入以空格分隔 System.out.println("请输入链表元素,以空格分隔:"); String[] input = scanner.nextLine().split(" "); ListNode dummy = new ListNode(0); ListNode current = dummy; for (String num : input) { current.next = new ListNode(Integer.parseInt(num)); current = current.next; } ListNode head = dummy.next; // 读取要移除元素 System.out.println("请输入要移除元素:"); int valToRemove = scanner.nextInt(); // 移除元素 ListNode newHead = removeElements(head, valToRemove); // 输出移除元素后的链表 System.out.println("移除元素后的链表:"); current = newHead; while (current != null) { System.out.print(current.val + " "); current = current.next; } scanner.close(); } } ``` 在上述代码中,`main` 方法使用 `Scanner` 类从控制台读取用户输入。首先读取一系列以空格分隔的整数作为链表元素,然后读取一个整数作为要移除元素。接着调用 `removeElements` 方法移除指定元素,并输出移除元素后的链表
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值