一、移除链表元素
题干:给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
测试地址:. - 力扣(LeetCode)
操作原链表
首先让头结点进行遍历,找到第一个节点的Val不为val的节点作为头结点。然后设置变量cur指向head,随后进行遍历。同时这里for cur != nil && cur.Next != nil 必须加上cur!=nil的原因是head可能指向nil,然后让cur指向head并操作cur.Next是会报错的。或者在cur赋值语句前加上对head == nil的判断,如果是则return,也可以通过测试。
核心:时刻注意不要操作空指针
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
}
虚拟头结点
在原链表上操作首先是要找到第一个值不为val的节点,然后以此为头再进行遍历操作。而如果使用虚拟头结点,换言之直接找到了第一个不为val的节点,直接对后续进行遍历即可。最后返回的之后返回虚拟头结点的next即可。
func removeElements(head *ListNode, val int) *ListNode {
dummy := &ListNode{}
dummy.Next = head
cur := dummy
for cur.Next != nil {
if cur.Next.Val == val {
cur.Next = cur.Next.Next
}else {
cur = cur.Next
}
}
return dummy.Next
}
二、设计链表
题干:
在链表类中实现这些功能:
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
测试地址:. - 力扣(LeetCode)
这里好像必须要用虚拟头结点法,不然因为go语言中的引用传递也不过是拷贝一份指针指向堆中变量的位置,所以没有办法对原始指针进行更改,所以这里的addAtHead没有办法更新,当然在自己的代码里面可以通过
a = a.AddAtHead(7)
进行更改,但是在lc里面不会这样进行操作,所以过不了检测。但是也有一种方案是在头结点之后插入节点,然后把它的值和头结点的值进行互换,这里是采用了这种方式
非虚拟头结点:
func Constructor() MyLinkedList {
return MyLinkedList{}
}
func (this *MyLinkedList) Get(index int) int {
node := this
for i := 0; i <= index; i++ {
if i == index {
fmt.Println(node.Value)
return node.Value
}
if node.Next == nil {
return -1
}
node = node.Next
}
return -1
}
func (this *MyLinkedList) AddAtHead(val int) *MyLinkedList {
node := MyLinkedList{
Value: val,
Next: nil,
}
node.Next = this
this = &node
return this
}
func (this *MyLinkedList) AddAtTail(val int) {
tail := this
for tail.Next != nil {
tail = tail.Next
}
node := MyLinkedList{
Value: val,
Next: nil,
}
tail.Next = &node
}
func (this *MyLinkedList) AddAtIndex(index int, val int) {
if index < 0 {
this.AddAtHead(val)
return
}
var size int
cur := this
for cur != nil {
size++
cur = cur.Next
}
if size == index {
this.AddAtTail(val)
}
if size > index {
last := this
cur = this
for i := 0; i < index; i++ {
last = cur
cur = cur.Next
}
node := MyLinkedList{
Value: val,
Next: nil,
}
node.Next = cur
last.Next = &node
}
if size < index {
return
}
}
func (this *MyLinkedList) DeleteAtIndex(index int) {
if index < 0 {
return
}
var size int
cur := this
for cur != nil {
size++
cur = cur.Next
}
size = size - 1
if index <= size {
last := this
cur = this
for i := 0; i < index; i++ {
last = cur
cur = cur.Next
}
last.Next = cur.Next
}
}
虚拟头结点
使用虚拟头结点来作为头结点,从而避免了用addthead函数时引用传递导致的头结点无法更新的情况。同时也记录了链表的大小,方便确定index和链表大小的关系。
//单链表实现
package main
import (
"fmt"
)
type SingleNode struct {
Val int // 节点的值
Next *SingleNode // 下一个节点的指针
}
type MyLinkedList struct {
dummyHead *SingleNode // 虚拟头节点
Size int // 链表大小
}
/** Initialize your data structure here. */
func Constructor() MyLinkedList {
newNode := &SingleNode{ // 创建新节点
-999,
nil,
}
return MyLinkedList{ // 返回链表
dummyHead: newNode,
Size: 0,
}
}
func (this *MyLinkedList) Get(index int) int {
if this == nil || index < 0 || index >= this.Size { // 如果索引无效则返回-1
return -1
}
cur := this.dummyHead.Next
for i := 0; i < index; i++ {
cur = cur.Next
}
return cur.Val // 返回节点值
}
func (this *MyLinkedList) AddAtHead(val int) {
newNode := &SingleNode{Val: val} // 创建新节点
newNode.Next = this.dummyHead.Next // 新节点指向当前头节点
this.dummyHead.Next = newNode // 新节点变为头节点
this.Size++ // 链表大小增加1
}
func (this *MyLinkedList) AddAtTail(val int) {
newNode := &SingleNode{Val: val} // 创建新节点
cur := this.dummyHead // 设置当前节点为虚拟头节点
for cur.Next != nil { // 遍历到最后一个节点
cur = cur.Next
}
cur.Next = newNode // 在尾部添加新节点
this.Size++ // 链表大小增加1
}
func (this *MyLinkedList) AddAtIndex(index int, val int) {
if index < 0 { // 如果索引小于0,设置为0
index = 0
} else if index > this.Size { // 如果索引大于链表长度,直接返回
return
}
newNode := &SingleNode{Val: val} // 创建新节点
cur := this.dummyHead // 设置当前节点为虚拟头节点
for i := 0; i < index; i++ { // 遍历到指定索引的前一个节点
cur = cur.Next
}
newNode.Next = cur.Next // 新节点指向原索引节点
cur.Next = newNode // 原索引的前一个节点指向新节点
this.Size++ // 链表大小增加1
}
func (this *MyLinkedList) DeleteAtIndex(index int) {
if index < 0 || index >= this.Size { // 如果索引无效则直接返回
return
}
cur := this.dummyHead // 设置当前节点为虚拟头节点
for i := 0; i < index; i++ { // 遍历到要删除节点的前一个节点
cur = cur.Next
}
if cur.Next != nil {
cur.Next = cur.Next.Next // 当前节点直接指向下下个节点,即删除了下一个节点
}
this.Size-- // 注意删除节点后应将链表大小减一
}
三、反转链表
题干:给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
测试地址:. - 力扣(LeetCode)
思路很简单,就是本来节点1连向节点2,现在让节点1指向空,节点2指向节点1即可,但是这样的问题会是如果后面有节点3,那不就找不到节点3了吗,所以需要多用一个变量即可。
循环版本
func reverseList(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
return head
}
var last, cur *ListNode
last, cur = nil, head
for cur != nil {
next := cur.Next
cur.Next = last
last = cur
cur = next
}
return last
}
递归版本
func reverseList(head *ListNode) *ListNode {
return reverse(nil, head)
}
func reverse(last, cur *ListNode) *ListNode {
if cur == nil {
return last
}
next := cur.Next
cur.Next = last
return reverse(cur, next)
}
四、两两交换链表中的节点
题干:给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
测试地址:. - 力扣(LeetCode)
建议画图,还是比较好搞懂的。虚拟头结点法确实是好用的。
func swapPairs(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
return head
}
dummy := &ListNode{Next: head}
cur := dummy
for cur.Next != nil && cur.Next.Next != nil {
temp := cur.Next
cur.Next = temp.Next
cur = temp.Next
temp.Next = cur.Next
cur.Next = temp
cur = temp
}
return dummy.Next
}