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

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

今日学习的文章链接和视频链接

参考代码随想录

自己看到题目的第一想法

今天的题目之前都刷过,所以以为都可以顺利写出来啊ac

自己实现过程中遇到哪些困难

203.移除链表元素中记得使用虚拟头节点,但是以为删除节点后cur要继续向后遍历;707.设计链表除了addAtIndex()都写出来了,addAtIndex()中判断index无效条件中以为index = size无效,其实index = size是有效的,因为插入节点后size会加一;206.反转链表一次过

今日收获,记录一下自己的学习时长

  • 在做707.设计链表学会使用leetcode测试用例来debug
  • 应打卡6月30日,7月1日补打卡,学习时长2hr
  • 6月30日学习了字符串*2,剑指offer05. 替换空格和0151. 反转字符串里的单词 Reverse Word in a String

0203. 移除链表元素 Remove Linked List Elements

Leetcode 题目链接

1. 题目描述

给你一个链表的头节点 head 和一个整数 val,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

示例1:

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例2:

输入:head = [], val = 1
输出:[]

示例3:

输入:head = [7,7,7,7], val = 7
输出:[]

2. 解题思路

  • 从头节点head开始遍历链表,如果满足条件node.next.val == val,移除下一个节点就相当于把node.next指向node.next.next
  • 对头节点的处理:
    • 直接对 原来链表 进行删除处理,如果头节点head满足删除条件,需要单独删除头节点,具体操作为把head指针向后移动一位,并删除原本的头节点
    • 设置 虚拟节点: 设置一个虚拟节点dummyNode,让虚拟节点指向原本的头节点,即dummyNode.next = head,然后遍历链表,此时如果头节点符合条件,就可以按原来的逻辑删除,最后返回的是dummyNode.next

2.1. 直接遍历

  • 删除头节点操作时,要用循环while而不用条件判断if,因为如果用条件判断,只会判断一次,如果出现list = [7,7,7,7]删除7时,只会删除一次
  • 删除其他节点时,循环判断条件需要有cur != None,因为需要考虑链表为空即list = []的情况,如果用虚拟节点法则不用,因为开始循环时cur = dummy_node不为空。
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution(object):
    def removeElements(self, head, val):
        """
        :type head: ListNode
        :type val: int
        :rtype: ListNode
        """

        # 删除头节点
        while head != None and head.val == val: # 注意用while循环而不是if
            head = head.next
        
        # 删除其他节点
        cur = head
        while cur != None and cur.next != None: # 当前cur不为空且cur下一个不为空
            if cur.next.val == val:
                cur.next = cur.next.next
            else:
                cur = cur.next
        
        return head

2.2. 设置虚拟节点法

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

Python

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution(object):
    def removeElements(self, head, val):
        """
        :type head: ListNode
        :type val: int
        :rtype: ListNode
        """
        
        dummy_head = ListNode(next = head) # 虚拟头节点,next指向头节点 

        # 遍历链表,删除符合条件的节点
        cur = dummy_head
        
        # 出循环条件,cur.next == None,此时cur是最后一个元素
        while cur.next != None:
            if cur.next.val == val: # 满足条件
                cur.next = cur.next.next # 指向next的下一个节点
            else:
                cur = cur.next # 继续遍历
        
        # 返回头节点,头节点是虚拟节点的下一个节点
        return dummy_head.next

Java

/**
 * 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) {
        // 虚拟头节点
        ListNode dummy = new ListNode(0, head);

        // 遍历
        ListNode cur = dummy;

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

0707. 设计链表 Design Linked List

Leetcode 题目链接

1. 题目描述

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

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

如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。

实现 MyLinkedList 类:

  • MyLinkedList() 初始化 MyLinkedList 对象。
  • int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1
  • void addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
  • void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
  • void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
  • void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。

示例:

输入
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]

解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2);    // 链表变为 1->2->3
myLinkedList.get(1);              // 返回 2
myLinkedList.deleteAtIndex(1);    // 现在,链表变为 1->3
myLinkedList.get(1);              // 返回 3

2. 解题思路

设计链表的五个接口:

  • 获取链表第index个节点的数值
  • 在链表的最前面插入一个节点
  • 在链表的最后面插入一个节点
  • 在链表第index个节点前面插入一个节点
  • 删除链表的第index个节点

链表操作的两个方式:

  • 直接使用原来的链表进行操作
  • 设置链表虚拟头节点

注意:

  • 自己定义链表节点ListNode
  • 遍历是从虚拟节点dummy_node开始还是从虚拟节点下一个节点dummy_node.next开始。对于addAtIndexdeleteAtIndex来说,遍历从dummy_node开始,当特殊情况index = 0,此时相当于插入/删除头节点,cur = dummy_node后,循环不会执行,插入/删除cur.next,即真正的头节点;对于get来说,遍历从dummy_node.next开始,此时cur = dummy_node.next是真正的头节点。
  • 插入/删除节点后,记得更新链表的长度size
  • 时间复杂度:涉及index相关的算法和尾部插入为O(n),头元素插入为O(1)
  • 空间复杂度:O(1)

3. 算法实现

3.1 单向链表 + 虚拟头节点

Python

# ListNode链表节点类
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

class MyLinkedList:

    def __init__(self):
        # 定义虚拟头节点
        self.dummy_head = ListNode() # 此时的head是虚拟头节点,不是真正的头节点
        self.size = 0

    def get(self, index: int) -> int:
        # 先判断index是否有效,无效返回-1
        if index >= self.size or index < 0:
            return -1
        
        cur = self.dummy_head.next # 此时的cur是真正的头节点
        for i in range(index): # 遍历链表
            cur = cur.next
        
        return cur.val

    def addAtHead(self, val: int) -> None:
        # 定义新的头节点,指向原本的头节点,即dummy_node的下一个
        cur = ListNode(val, self.dummy_head.next)
        
        self.dummy_head.next = cur
        self.size += 1


    def addAtTail(self, val: int) -> None:
        # 遍历数组找到尾节点
        cur = self.dummy_head
        
        # 出界条件为cur.next == None,此时cur为尾巴节点
        while cur.next != None:
            cur = cur.next

        # 插入新尾巴节点
        cur.next = ListNode(val)

        self.size += 1

    def addAtIndex(self, index: int, val: int) -> None:
        # 先判断index是否有效
        if index < 0 or index > self.size:
            return 
        
        # 遍历链表,找到目标节点
        # 从dummy_head开始遍历,如果index = 0, 相当于插入头节点
        # 如果index = size,相当于插入尾节点
        cur = self.dummy_head
        for i in range(index): 
            cur = cur.next
        cur.next = ListNode(val, cur.next)
        
        self.size += 1

    def deleteAtIndex(self, index: int) -> None:
        # 先判断index
        if index < 0 or index >= self.size:
            return
        
        # 从dummy_node开始遍历
        cur = self.dummy_head
        for i in range(index): 
            cur = cur.next
        cur.next = cur.next.next

        self.size -= 1

Java

class ListNode {
    int val;
    ListNode next;

    // 构造函数
    public ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

class MyLinkedList {
    ListNode dummyhead;
    int size;

    public MyLinkedList() {
        this.dummyhead = new ListNode(0, null);
        this.size = 0;
    }
    
    public int get(int index) {
        // index 无效
        if (index < 0 || index >= this.size) {
            return -1;
        }

        // 遍历
        ListNode cur = this.dummyhead.next;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        return cur.val;
    }
    
    public void addAtHead(int val) {
        this.dummyhead.next = new ListNode(val, this.dummyhead.next);
        this.size++;
    }
    
    public void addAtTail(int val) {
        // 遍历
        ListNode cur = this.dummyhead;
        while (cur.next != null) {
            cur = cur.next;
        }
        cur.next = new ListNode(val, null);
        this.size++;
    }
    
    public void addAtIndex(int index, int val) {
        // index 无效
        // index = this.size 可以插入节点,相当于addAtTail
        if (index < 0 || index > this.size) {
            return;
        }

        // 遍历
        // 找到index-1的节点
        ListNode cur = this.dummyhead;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        cur.next = new ListNode(val, cur.next);
        this.size++;
    }
    
    public void deleteAtIndex(int index) {
        // index 无效
        if (index < 0 || index >= size) {
            return;
        }

        // 遍历到index-1的节点
        ListNode cur = this.dummyhead;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        cur.next = cur.next.next;
        this.size--;
    }
}

0206. 反转链表 Reverse Linked List

Leetcode 题目链接

1. 题目描述

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例1:

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例2:

输入:head = [1,2]
输出:[2,1]

示例3:

输入:head = []
输出:[]

2. 解题思路

  • 如果定义一个新的链表,浪费内存空间,所以在原本链表内改变指针next的方向,直接反转链表

链表双指针法

  • 定义指针pre指向None(因为pre初始化指向head之前),cur指向head
  • 遍历列表,注意遍历条件为while cur而不是while cur.next,因为如果是后者,原本列表最后一个节点不会遍历到,也不会反转
  • 在循环中,先定义临时指针temp指向cur.next,存放cur原本指向的下一个节点。注意temp不能指向cur,因为传递的是引用,当后面反转时cur.next改变,temp.next也会改变。
  • 反转指针,即让cur.next指向pre,然后pre向前移动,即pre = cur。继续遍历链表,此时cur = tempcur指向原本的下一个节点。
  • 返回整个链表,即返回pre。注意不是返回cur,因为遍历完后,cur == None
# 链表双指针

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:

        # 定义cur, pre
        pre = None
        cur = head

        # 遍历链表
        while cur: # 如果 cur.next,原本最后一个节点不会反转
            temp = cur.next # 保存当前节点
            cur.next = pre # 反转
            pre = cur
            cur = temp
        
        return pre # 返回pre,如果返回cur,最后cur == None
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

Java:

/**
 * 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 pre = null;
        ListNode cur = head;
        ListNode temp;

        while (cur != null) {
            temp = cur.next;

            // 交换位置
            cur.next = pre;
            pre = cur;
            cur = temp;    
        }

        return pre;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值