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

79 阅读3分钟

前言

工作党太忙了,所以准备从本篇博客开始,本专栏《算法训练营》中的每篇博客,只简单记录一下LeetCode答案(也就是提交通过的代码),当然,如果有时间的话,也会记录一下解题思路

LeetCode题目

203.移除链表元素

题目链接:Remove Linked List Elements - LeetCode

设置虚拟头节点

代码如下:

func removeElements(head *ListNode, val int) *ListNode {
	dummyHead := &ListNode{}
	dummyHead.Next = head
	cur := dummyHead
	for cur.Next != nil {
		if cur.Next.Val == val {
			cur.Next = cur.Next.Next
		} else {
			cur = cur.Next
		}
	}
	return dummyHead.Next
}

直接使用原链表

代码如下:

func removeElements(head *ListNode, val int) *ListNode {
	// 处理头节点
	for head != nil && head.Val == val {
		head = head.Next
	}
	// 处理非头节点
	cur := head
	for cur != nil && cur.Next != nil {
		if cur.Next.Val == val {
			cur.Next = cur.Next.Next
		} else {
			cur = cur.Next
		}
	}
	return head
}

707.设计链表

题目链接:Design Linked List - LeetCode

代码如下:

// 链表上的单个节点
type Node struct {
	Val  int
	Next *Node
}

// 链表,但是注意该结构本身不是链表上的节点,更像是记录了链表信息
type MyLinkedList struct {
	// 指向虚拟头节点的指针,本身不是虚拟头节点
	dummyHead *Node
	// 链表长度
	length int
}

// 创建链表
func Constructor() MyLinkedList {
	// 虚拟头节点
	newNode := &Node{Val: 0, Next: nil}
	// 初始化链表,指针指向虚拟头节点,链表长度为0
	return MyLinkedList{dummyHead: newNode, length: 0}
}

func (this *MyLinkedList) Get(index int) int {
	// 索引无效则返回-1
	if this == nil || index < 0 || index >= this.length {
		return -1
	}
	// 游标指向真正头节点(称呼cur变量为游标挺形象的,它确实一直在链表上游动)
	cur := this.dummyHead.Next
	// 遍历到索引所在的节点
	for i := 0; i < index; i++ {
		cur = cur.Next
	}
	// 返回节点值
	return cur.Val
}

func (this *MyLinkedList) AddAtHead(val int) {
	// 生成新节点
	newNode := &Node{Val: val, Next: nil}
	// 新节点指向当前头节点
	newNode.Next = this.dummyHead.Next
	// 新节点变为头节点
	this.dummyHead.Next = newNode
	// 链表长度加1
	this.length++
}

func (this *MyLinkedList) AddAtTail(val int) {
	// 生成新节点
	newNode := &Node{Val: val, Next: nil}
	// 游标指向虚拟头节点,可一并处理空链表情况
	cur := this.dummyHead
	// 遍历到最后一个节点
	for cur.Next != nil {
		cur = cur.Next
	}
	// 新节点添加到链表尾部
	cur.Next = newNode
	// 链表长度加1
	this.length++
}

func (this *MyLinkedList) AddAtIndex(index int, val int) {
	// 处理索引越界,index < 0 则在头部插入节点,index > this.length 则不插入节点
	if index < 0 {
		index = 0
	} else if index > this.length {
		return
	}
	// 生成新节点
	newNode := &Node{Val: val, Next: nil}
	// 游标指向虚拟头节点,可一并处理插入头节点情况
	cur := this.dummyHead
	// 遍历到指定索引的前一个节点
	for i := 0; i < index; i++ {
		cur = cur.Next
	}
	// 新节点指向原索引节点
	newNode.Next = cur.Next
	// 原索引的前一个节点指向新节点
	cur.Next = newNode
	// 链表长度加1
	this.length++
}

func (this *MyLinkedList) DeleteAtIndex(index int) {
	// 索引无效则直接返回
	if index < 0 || index >= this.length {
		return
	}
	// 游标指向虚拟头节点,可一并处理删除头节点情况
	cur := this.dummyHead
	// 遍历到要删除节点的前一个节点
	for i := 0; i < index; i++ {
		cur = cur.Next
	}
	// 待删除节点不在末尾,通过调整指针从链表上剔除节点
	// 在末尾,链表长度减1,不再包含该节点即可
	if cur.Next != nil {
		cur.Next = cur.Next.Next
	}
	// 链表长度减1
	this.length--
}

206.反转链表

题目链接:Reverse Linked List - LeetCode

双指针法

代码如下:

func reverseList(head *ListNode) *ListNode {
	var pre *ListNode
	cur := head
	for cur != nil {
		next := cur.Next
		cur.Next = pre
		pre = cur
		cur = next
	}
	return pre
}

递归法

代码如下:

func reverse(pre, cur *ListNode) *ListNode {
	if cur == nil {
		return pre
	}
	next := cur.Next
	cur.Next = pre
	return reverse(cur, next)
}

func reverseList(head *ListNode) *ListNode {
	return reverse(nil, head)
}

总结

  1. 链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)
  2. 题目设计链表中的五个接口,已经覆盖了链表的常见操作,是练习链表操作非常好的一道题目
  3. 反转链表中的两种解法逻辑相同,但递归法调用了 n 层栈空间,所以空间复杂度较高,这也是递归算法需要注意的一点