链表
type Node struct {
Data any
Next *Node
}
// 创建一个长度为n的链表
func CreateNode(head *Node, n int) {
for i := 0; i < n; i++ {
head.Next = &Node{rand.Intn(1000), nil}
head = head.Next
}
}
// 打印链表
func PrintLink(head *Node) {
cur := head
for cur.Next != nil {
fmt.Printf("data: %v, point: %p\n\n", cur.Data, cur)
cur = cur.Next
}
}
func main() {
head := &link.Node{10, nil}
link.CreateNode(head, 5)
link.PrintLink(head)
}
//data: 10, point: 0xc000010030
//data: 21, point: 0xc000010048
//data: 405, point: 0xc000010060
//data: 402, point: 0xc000010078
//data: 253, point: 0xc000010090
这里打印的node内存地址看似是连续的,实际上是因为我for循环创建的链表,可能分配的是同一块内存空间,导致连续,但是实际上链表本身节点地址不一定是连续的
203 移除链表指定元素
func removeElements(head *ListNode, val int) *ListNode {
// 思考,虚拟头节点,防止出现要删除第一个节点的情况
newhead := &ListNode{0, head} // 创建一个头节点,指向题目头节点
cur := newhead
for cur.Next != nil {
temp := cur.Next
if temp.Val == val {
// 移除元素,o1
cur.Next = temp.Next // 指向下一个节点
}else {
cur = cur.Next
}
}
return newhead.Next
}
时间 平均遍历n/2 n 空间 n
func removeElements(head *ListNode, val int) *ListNode {
// 思考 尝试不使用虚拟节点,暴力破解
if head == nil {
return head
}
// 针对头节点单独处理
for head.Val == val {
head = head.Next
if head == nil {
return head
}
}
fmt.Println("----------", head)
cur := head
for cur != nil && cur.Next != nil {
if cur.Next.Val == val{ // 中间节点处理
cur.Next = cur.Next.Next // 指向下一个节点
}else {
cur = cur.Next
}
}
return head
}
时间 n 空间 n
707 设计链表
// !!!!!!!!!!!!!!!!!!!! 下面回答是错误的 !!!!!!!!!!!!!!!!!!
type MyLinkedList struct {
Val int // 值
Next *MyLinkedList
Prev *MyLinkedList
}
func Constructor() MyLinkedList {
return MyLinkedList{}
}
func (this *MyLinkedList) Get(index int) int {
if index < 0 {
return -1
}
var count int
cur := this
for cur != nil {
if count == index {
return cur.Val
}
cur = cur.Next
count++
}
return -1 // 全部便利还没找到,说明idx超过链表长度
}
func (this *MyLinkedList) AddAtHead(val int) {
var node = &MyLinkedList{val, this, nil}
this.Prev = node
return
}
func (this *MyLinkedList) AddAtTail(val int) {
var node = &MyLinkedList{val, nil, nil}
cur := this
for cur != nil {
if cur.Next == nil {
cur.Next = node
node.Prev = cur
break
}else{
cur = cur.Next
}
}
return
}
func (this *MyLinkedList) AddAtIndex(index int, val int) {
var dummyHead = &MyLinkedList{0, this, nil}
this.Prev = dummyHead
var node = &MyLinkedList{val, nil, nil}
var count int
cur := this
for cur != nil {
if count == index {
temp := cur.Prev
temp.Next = node
node.Prev = temp
node.Next = cur
cur.Prev = node
break
}else {
count++
cur = cur.Next
}
}
this = dummyHead.Next
return
}
func (this *MyLinkedList) DeleteAtIndex(index int) {
var dummyHead = &MyLinkedList{0, this, nil}
this.Prev = dummyHead
cur := this
var count int
for cur != nil {
if count == index {
temp := cur.Prev
temp.Next = cur.Next
if cur.Next != nil {
cur.Next.Prev = temp
}
break
}else {
count++
cur = cur.Next
}
}
this = dummyHead.Next
return
}
// tm的sb题目不给我写出来node结构体,让我错误译为给出的结构体是用来设计node,导致一步错,步步错
// 比较大的错误点在于 我多地方尝试将修改后的head重新赋值给this,但是这样是错误的,在 Go 中,方法接收器是值接收器(即使是指针接收器),对 this 的重新赋值不会改变调用者的状态,所以,本质上this并没有发生变化
206 反转列表
func reverseList(head *ListNode) *ListNode {
// 思路,创建一个虚拟头节点,遍历链表,插入到虚拟头和下一个节点中间实现反转 头插法
// nil-1-2-3 --> nil-2-1-3 --> nil-3-2-1
if head == nil || head.Next == nil {
return head
}
var dummyHead = &ListNode{0, head}
// 为了方便理解,多用几个变量,本质上可以节省点
cur := head.Next
pre, next := head, cur.Next // 保存头节点和 cur之后节点信息
h := head // 头节点
for cur != nil {
// insert 到底要重新指向几个元素,要看断了几条链,cur 往前往后两条链都断了,加上虚拟节点之后的链也断了共三条,需要指向三次
dummyHead.Next = cur // 指向虚拟到cur
cur.Next = h// 指向cur 到 头
pre.Next = next // cur原来的pre指向原来的next
// 重新赋值cur, pre, next, h(ead)
h = cur // 新的头节点
cur = next // 指向下一个节点
pre = head // 本质上移动之后pre没有发生变动,原来的head往后移动了,可以省略
if next != nil {
next = next.Next // 下下节点
}
}
return dummyHead.Next
}
时间n 空间1
// 简化代码
func reverseList(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
return head
}
var dummyHead = &ListNode{Next: head}
cur := head.Next
head.Next = nil // 将原头节点的 Next 置为 nil,成为新尾节点
for cur != nil {
next := cur.Next
cur.Next = dummyHead.Next
dummyHead.Next = cur
cur = next
}
return dummyHead.Next
}
func reverseList(head *ListNode) *ListNode {
// 思路 双指针法 相当于维护一个新反转链表
if head == nil {
return head
}
var pre *ListNode // !!! 此处如果使用pre = &ListNode{} 方式进行定义,那么就初始化了!= nil
cur := head
for cur != nil {
temp := cur.Next
cur.Next = pre
pre = cur // 新头节点
cur = temp
}
return pre
}
时间n 空间1
func reverseList(head *ListNode) *ListNode {
// 思路 通过双指针思路尝试写出递归解法
var pre *ListNode
return reverse(pre, head)
}
func reverse(pre, cur *ListNode) *ListNode{
if cur == nil {
return pre
}
temp := cur.Next
cur.Next = pre
return reverse(cur, temp)
}
时间 n 空间 n 为什么不是logn,因为本质上此递归并没有缩小问题规模,本质上还是一个一个处理节点
绷不住,递归效率最低,头插居然最高