前言
工作党太忙了,所以准备从本篇博客开始,本专栏《算法训练营》中的每篇博客,只简单记录一下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)
}
总结
- 链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)
- 题目设计链表中的五个接口,已经覆盖了链表的常见操作,是练习链表操作非常好的一道题目
- 反转链表中的两种解法逻辑相同,但递归法调用了 n 层栈空间,所以空间复杂度较高,这也是递归算法需要注意的一点