【代码随想录——链表】

1.链表

什么是链表,链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。链表的入口节点称为链表的头结点也就是head。
在这里插入图片描述

1.1 链表的类型

  • 单链表
    • 只有一个指向下一个节点的指针
    • 只能向后查询
  • 双链表
    • 每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
    • 既可以向前查询也可以向后查询。
  • 循环链表 (就是链表首尾相连)
    • 可以用来解决约瑟夫环问题

1.2 链表的常见操作

  • 删除节点
    • 需要两个指针,一个指针p2指向要删除的节点,一个指针p1指向要删除的前一个节点。之后将p1.next指向p2.next
  • 添加节点
    • 需要先查找到要添加的位置p2,p1指向前一个节点。之后p1.next指向新节点,新节点.next指向p2即可。

在这里插入图片描述
在这里插入图片描述

2.移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回新的头节点 。
在这里插入图片描述
需要注意的是如果头结点的元素和要删除的元素相等。
这里我们通过引入空节点来避免这个状况。

func removeElements(head *ListNode, val int) *ListNode {
	emptyNode := &ListNode{}
	emptyNode.Next = head
	p1, p2 := emptyNode, head
	for p2 != nil {
		if p2.Val == val {
            //删除该节点
			p1.Next = p2.Next
		}else{
            p1 = p2
        }
		p2 = p2.Next
	}
	return emptyNode.Next
}

3. 设计链表

在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

3.1 单向链表

//单链表实现
package main

import (
	"fmt"
)

type SingleNode struct {
	Val  int         // 节点的值
	Next *SingleNode // 下一个节点的指针
}

type MyLinkedList struct {
	dummyHead *SingleNode // 虚拟头节点
	Size      int         // 链表大小
}

func main() {
	list := Constructor()     // 初始化链表
	list.AddAtHead(100)       // 在头部添加元素
	list.AddAtTail(242)       // 在尾部添加元素
	list.AddAtTail(777)       // 在尾部添加元素
	list.AddAtIndex(1, 99999) // 在指定位置添加元素
	list.printLinkedList()    // 打印链表
}

/** Initialize your data structure here. */
func Constructor() MyLinkedList {
	newNode := &SingleNode{ // 创建新节点
		-999,
		nil,
	}
	return MyLinkedList{ // 返回链表
		dummyHead: newNode,
		Size:      0,
	}

}

/** Get the value of the index-th node in the linked list. If the index is
  invalid, return -1. */
func (this *MyLinkedList) Get(index int) int {
	/*if this != nil || index < 0 || index > this.Size {
		return -1
	}*/
	if this == nil || index < 0 || index >= this.Size { // 如果索引无效则返回-1
		return -1
	}
	// 让cur等于真正头节点
	cur := this.dummyHead.Next   // 设置当前节点为真实头节点
	for i := 0; i < index; i++ { // 遍历到索引所在的节点
		cur = cur.Next
	}
	return cur.Val // 返回节点值
}

/** Add a node of value val before the first element of the linked list. After
  the insertion, the new node will be the first node of the linked list. */
func (this *MyLinkedList) AddAtHead(val int) {
	// 以下两行代码可用一行代替
	// newNode := new(SingleNode)
	// newNode.Val = val
	newNode := &SingleNode{Val: val}   // 创建新节点
	newNode.Next = this.dummyHead.Next // 新节点指向当前头节点
	this.dummyHead.Next = newNode      // 新节点变为头节点
	this.Size++                        // 链表大小增加1
}

/** Append a node of value val to the last element of the linked list. */
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
}

/** Add a node of value val before the index-th node in the linked list. If
  index equals to the length of linked list, the node will be appended to the
  end of linked list. If index is greater than the length, the node will not be
  inserted. */
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
}

/** Delete the index-th node in the linked list, if the index is valid. */
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-- // 注意删除节点后应将链表大小减一
}

// 打印链表
func (list *MyLinkedList) printLinkedList() {
	cur := list.dummyHead // 设置当前节点为虚拟头节点
	for cur.Next != nil { // 遍历链表
		fmt.Println(cur.Next.Val) // 打印节点值
		cur = cur.Next            // 切换到下一个节点
	}
}

3.2 双向链表

//循环双链表
type MyLinkedList struct {
	dummy *Node
}

type Node struct {
	Val  int
	Next *Node
	Pre  *Node
}

//仅保存哑节点,pre-> rear, next-> head
/** Initialize your data structure here. */
func Constructor() MyLinkedList {
	rear := &Node{
		Val:  -1,
		Next: nil,
		Pre:  nil,
	}
	rear.Next = rear
	rear.Pre = rear
	return MyLinkedList{rear}
}

/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
func (this *MyLinkedList) Get(index int) int {
	head := this.dummy.Next
	//head == this, 遍历完全
	for head != this.dummy && index > 0 {
		index--
		head = head.Next
	}
	//否则, head == this, 索引无效
	if 0 != index {
		return -1
	}
	return head.Val
}

/** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
func (this *MyLinkedList) AddAtHead(val int) {
	dummy := this.dummy
	node := &Node{
		Val: val,
		//head.Next指向原头节点
		Next: dummy.Next,
		//head.Pre 指向哑节点
		Pre: dummy,
	}

	//更新原头节点
	dummy.Next.Pre = node
	//更新哑节点
	dummy.Next = node
	//以上两步不能反
}

/** Append a node of value val to the last element of the linked list. */
func (this *MyLinkedList) AddAtTail(val int) {
	dummy := this.dummy
	rear := &Node{
		Val: val,
		//rear.Next = dummy(哑节点)
		Next: dummy,
		//rear.Pre = ori_rear
		Pre: dummy.Pre,
	}

	//ori_rear.Next = rear
	dummy.Pre.Next = rear
	//update dummy
	dummy.Pre = rear
	//以上两步不能反
}

/** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
func (this *MyLinkedList) AddAtIndex(index int, val int) {
	head := this.dummy.Next
	//head = MyLinkedList[index]
	for head != this.dummy && index > 0 {
		head = head.Next
		index--
	}
	if index > 0 {
		return
	}
	node := &Node{
		Val: val,
		//node.Next = MyLinkedList[index]
		Next: head,
		//node.Pre = MyLinkedList[index-1]
		Pre: head.Pre,
	}
	//MyLinkedList[index-1].Next = node
	head.Pre.Next = node
	//MyLinkedList[index].Pre = node
	head.Pre = node
	//以上两步不能反
}

/** Delete the index-th node in the linked list, if the index is valid. */
func (this *MyLinkedList) DeleteAtIndex(index int) {
	//链表为空
	if this.dummy.Next == this.dummy {
		return
	}
	head := this.dummy.Next
	//head = MyLinkedList[index]
	for head.Next != this.dummy && index > 0 {
		head = head.Next
		index--
	}
	//验证index有效
	if index == 0 {
		//MyLinkedList[index].Pre = index[index-2]
		head.Next.Pre = head.Pre
		//MyLinedList[index-2].Next = index[index]
		head.Pre.Next = head.Next
		//以上两步顺序无所谓
	}
}

4.反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
在这里插入图片描述

4.1 双指针法

利用了pre,cur两个指针来实现翻转操作,temp是一个临时变量,用于保存cur的下一个节点,防止cur.Next被覆盖后找不到下一个节点的位置。

func reverseList(head *ListNode) *ListNode {
    var temp,pre *ListNode
    cur := head
    for cur!=nil{
        //temp是专门用来保存cur的下一个指针
        temp = cur.Next
        //翻转操作
        cur.Next = pre
        //更新各个指针的
        pre = cur
        cur = temp
    }
    return pre
}

4.2 递归法

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

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

5. 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
在这里插入图片描述
我们可以想象pre指向的是1号节点,p1指的是3号节点,p2指的是4号节点。

func swapPairs(head *ListNode) *ListNode {
	// 创建一个虚拟的头结点
    emptyNode := &ListNode{-999,head}
    // 空列表
    p1 := head
    if p1==nil{
    	//判断是否是空列表,防止取p2时报错
        return p1
    }
    p2 := head.Next
    pre := emptyNode
    for p1!=nil && p2!=nil{
        temp:=p2.Next
        pre.Next = p2
        p2.Next = p1
        pre = p1
        p1.Next = temp
        //更新p1,p2
        p1 = temp
        if(p1!=nil){
            p2 = p1.Next
        }
    }
    return emptyNode.Next
}

6.删除链表的倒数第N个节点

使用双指针,一个指针先走N步。之后一起向前走,当前面到达尾端时,后面这个就是倒数第N个节点。
就好比,两个人跑步。一个人先跑到50m,之后两个人在以一样的速度前进,当第一个到达终点时,第二个人距离终点50m。

func removeNthFromEnd(head *ListNode, n int) *ListNode {
    // 添加空节点
    emptyNode := &ListNode{-999,head}
    pre := emptyNode
    fastPoint,slowPoint := head,head
    for n>0{
        fastPoint = fastPoint.Next
        n--
    }
    for fastPoint!=nil{
        pre = slowPoint
        fastPoint = fastPoint.Next
        slowPoint = slowPoint.Next
    }
    pre.Next = slowPoint.Next

    return emptyNode.Next
}

7.链表相交

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
图示两个链表在节点 c1 开始相交:
在这里插入图片描述

7.1 方法一

思路1:求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置。此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。

func getIntersectionNode(headA, headB *ListNode) *ListNode {
    lenA,lenB := 0,0
    p1,p2 := headA,headB
    for p1!=nil{
        p1=p1.Next
        lenA++
    }
    for p2!=nil{
        p2=p2.Next
        lenB++
    }
    //将两个链表变得长度相等
    sub := lenA-lenB
    if sub>0 {
        for sub>0 {
            sub--
            headA = headA.Next
        }
    }else{
        for sub<0{
            sub++
            headB = headB.Next
        }
    }
    //开始查找
    for headA!=headB{
        headA=headA.Next
        headB=headB.Next
    }
    return headA
}

7.2 方法二

思路:使用map,遍历一个链表的全部节点,放入其中。再遍历另外一个链表,并在其中map中进行查询,当第一次查询到时即是交点。

 type void struct{
 }
func getIntersectionNode(headA, headB *ListNode) *ListNode {
    mp := make(map[*ListNode]void,0)
    for headA!=nil{
        mp[headA]=void{}
        headA=headA.Next
    }
    for headB!=nil{
        _,ok := mp[headB]
        if ok{
            return headB
        }
        headB = headB.Next
    }
    return nil
}

8.环形链表②

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改链表。
在这里插入图片描述

8.1 快慢指针

func detectCycle(head *ListNode) *ListNode {
    slow,fast := head,head
    for fast != nil && fast.Next != nil{
        slow = slow.Next
        fast = fast.Next.Next
        if(slow == fast){//有换?
            index1 := fast
            index2 := head
            // 两个指针,从头结点和相遇节点,各走一步,直到相遇,相遇点即为环入口
            for index1 != index2{
                index1 = index1.Next
                index2 = index2.Next
            }
            return index1
        }
    }
    return nil
}

8.2 哈希

type void struct{
}

func detectCycle(head *ListNode) *ListNode {
   mp := make(map[*ListNode]void,0)
    for head!=nil{
        mp[head]=void{}
        head=head.Next
        if _,ok:=mp[head];ok{
            return head
        }
    }
    return nil
}
  • 29
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值