算法训练Day3|LeetCode203 移除链表元素、LeetCode707 设计链表、LeetCode206 反转链表

47 阅读6分钟

LeetCode203 移除链表元素

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
输出: []

提示:

  • 列表中的节点数目在范围 [0, 10^4] 内
  • 1 <= Node.val <= 50
  • 0 <= val <= 50

2.算法分析

这是一道考察单链表删除结点操作的题目,没有特别的算法,关键是要了解单链表的逻辑结构。删除单链表结点的核心是:找到待删除结点的前一个结点

此外,为了统一操作,我将使用虚拟头结点(有时也被称为空头结点)。

3.解题代码

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func removeElements(head *ListNode, val int) *ListNode {
    dummy_head := new(ListNode) // 虚拟头结点
    dummy_head.Next = head
    cur := dummy_head // 用于操作的临时指针
    // 当cur.Next为空指针时,cur就是链表中的最后一个结点
    for cur.Next != nil {
        // 在删除结点后,cur需要保持在原位,以处理连续出现多个目标结点的情形
        if cur.Next.Val == val {
            cur.Next = cur.Next.Next
        } else {
            cur = cur.Next
        }
    }
    return dummy_head.Next // 返回实际的头指针
}

LeetCode707 设计链表

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

提示:

  • 0 <= index, val <= 1000
  • 请不要使用内置的 LinkedList 库。
  • 调用 getaddAtHeadaddAtTailaddAtIndex 和 deleteAtIndex 的次数不超过 2000 。

2.算法分析

这道题同样考察的是链表的基本操作,都不难,只需要注意以下几点即可:

  1. 按索引获取结点值需要找到索引对应的结点;
  2. 尾部添加结点需要将临时指针初始化指向虚拟头结点,因为链表可能为空,此时直接在虚拟头结点后插入新结点;
  3. 按索引添加或删除元素需要找到索引对应结点的前一个结点,也正因如此,需要将临时指针初始化指向虚拟头结点;
  4. 按索引添加元素时,索引可以恰好等于链表的长度,这相当于在链表尾部添加新结点。

3.解题代码

type MyLinkedList struct {
    dummy_head *ListNode
    size int
}

func Constructor() MyLinkedList {
    return MyLinkedList{dummy_head: new(ListNode), size: 0}
}

func (this *MyLinkedList) Get(index int) int {
    if index < 0 || index > this.size - 1 {
        return -1
    }
    cur := this.dummy_head.Next
    for n := 0; n < index; n++ {
        cur = cur.Next
    }
    return cur.Val
}

func (this *MyLinkedList) AddAtHead(val int) {
    new_node := &ListNode{Val: val}
    new_node.Next = this.dummy_head.Next
    this.dummy_head.Next = new_node
    this.size++
}

func (this *MyLinkedList) AddAtTail(val int) {
    new_node := &ListNode{Val: val}
    cur := this.dummy_head
    for cur.Next != nil {
        cur = cur.Next
    }
    cur.Next = new_node
    this.size++
}

func (this *MyLinkedList) AddAtIndex(index int, val int) {
    if index < 0 || index > this.size {
        return
    }
    new_node := &ListNode{Val: val}
    cur := this.dummy_head
    for n := 0; n < index; n++ {
        cur = cur.Next
    }
    new_node.Next = cur.Next
    cur.Next = new_node
    this.size++
}

func (this *MyLinkedList) DeleteAtIndex(index int) {
    if index < 0 || index > this.size - 1 {
        return
    }
    cur := this.dummy_head
    for n := 0; n < index; n++ {
        cur = cur.Next
    }
    cur.Next = cur.Next.Next
    this.size--
}

LeetCode206 反转链表

1.题目描述

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

示例 1:

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

示例 2:

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

示例 3:

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

提示:

  • 链表中节点的数目范围是 [0, 5000]
  • -5000 <= Node.val <= 5000

进阶: 链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?

2.算法分析

由于这道题不要求必须使用原位算法,因此可以创建一个新链表并使用循环在头部插入结点的方式来实现链表的反转,称之为头插法。

如果需要实现原位反转,则可以考虑使用双指针法或递归法。

双指针法: 实际上,实现链表的反转,只需要让链表中所有结点的Next指向反转即可,因此,可以用cur指针指向当前结点,用pre指针指向cur的前一个结点,通过cur.Next = pre就可以实现指向的反转。不过需要注意的是,由于cur.Next = precur的下一个位置会丢失,因此需要用一个临时指针来保存cur的下一位置,以使得cur能正常遍历链表。

image.png

递归法: 递归法的思想和双指针法是一样的,都是直接反转所有结点的Next的指向,只是将双指针法中循环遍历反转链表的过程用递归调用来实现。

3.解题代码

头插法

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func reverseList(head *ListNode) *ListNode {
    if head == nil {
        return head // 当head为空指针时,链表为空,不需要反转
    } else {
        dummy_head := new(ListNode) // 虚拟头结点
        cur := head // 用于遍历原链表的临时指针
        for cur.Next != nil {
            node := &ListNode{Val: cur.Val}
            node.Next = dummy_head.Next
            dummy_head.Next = node
            cur = cur.Next
        }
        // 处理余下的最后一个结点
        last_node := &ListNode{Val: cur.Val}
        last_node.Next = dummy_head.Next
        dummy_head.Next = last_node
        return dummy_head.Next // 返回反转后的实际头结点
    }
}

双指针法

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func reverseList(head *ListNode) *ListNode {
    cur := head
    var pre *ListNode = nil // 由于head反转后是尾结点,因此head反转后指向的是空指针
    // 当cur为空指针时,pre恰好指向反转后链表的头结点,此时反转结束
    for cur != nil {
        temp := cur.Next // 用临时指针保存cur的下一个位置
        cur.Next = pre // 反转Next的指向
        // 移动两个指针,注意必须先移动pre,否则cur的当前位置会丢失
        pre = cur
        cur = temp
    }
    return pre // 反转结束后,pre指向的是反转后链表的头结点
}

递归法

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */

func reverse(cur *ListNode, pre *ListNode) *ListNode {
    // 当cur为空指针时,pre指向反转后链表的头结点,反转结束
    if cur == nil {
        return pre
    }
    temp := cur.Next // 用临时指针保存cur的下一位置
    cur.Next = pre // 反转Next的指向
    return reverse(temp, cur) // 将cur和pre的下一位置传给下一层函数
}

func reverseList(head *ListNode) *ListNode {
    // cur的初始值为head,pre的初始值为空指针
    return reverse(head, (*ListNode)(nil))
}