链表高频算法题之Golang版(思路清晰,注释详尽)

链表类的算法题在面试中是最常出现的,题目虽然简单,但也十分考验面试者的逻辑思维和算法熟练度,以下通过几道常见的链表题来看看它们常规的解法吧!(点击标题可到leetcode原题,不定时更新)

1、判断链表是否有环

思路

使用快慢指针遍历链表,快指针每次走2步,慢指针每次走1步,如果两者相遇,则有环,否则没有

package main

import "fmt"

type Node struct {
    Val  int
    Next *Node
}

func hasCycle(head *Node) bool {
    if head != nil {
        fast := head
        slow := head
        for fast != nil && fast.Next != nil {
            fast = fast.Next.Next
            slow = slow.Next
            if fast == slow {
                return true
            }
        }
    }
    return false
}

2、链表的中间结点

思路

使用快慢指针,快指针每次走2步,慢指针每次走1步,快指针走完了,慢指针所在的位置就是中间节点

func middleNode(head *Node) *Node {
    if head == nil || head.Next == nil {
        return nil
    }
    slow := head
    fast := head
    for fast != nil && fast.Next != nil {
        slow = slow.Next
        fast = fast.Next.Next
        head.Printf()
    }
    return slow
}

3、反转链表

思路

遍历链表,将每个节点的next指针指向前驱节点,遍历到最后的尾节点就是新的头节点

func (head *Node) reverse() *Node {
    if head == nil {
        return nil
    }
    var preNode *Node
    var newHead *Node
    curNode := head
    for curNode != nil {
        //保存一份当前节点的后驱节点
        nextNode := curNode.next
        if nextNode == nil {
            newHead = curNode
        }
        //设置当前节点的next为前驱节点
        curNode.next = preNode
        //设置下一个节点的前驱节点为当前节点
        preNode = curNode
        //指针后移
        curNode = nextNode
    }
    return newHead
}

4、合并两个有序链表

思路

双指针分别遍历两个链表,比较两个节点的大小,将小的放到新的链表里,遍历完一遍后,将未遍历的部分指向新链表后面即可

func MergeTwoLists(list1 *Node, list2 *Node) *Node {
    //初始化一个虚拟的头节点
    newList := &Node{}
    p := newList
    p1 := list1
    p2 := list2
    //遍历对比两个指针值的大小,有一个走完了就停止
    for p1 != nil && p2 != nil {
        //将值小的节点接到p指针后面
        if p1.Val > p2.Val {
            p.Next = p2
            p2 = p2.Next
        } else {
            p.Next = p1
            p1 = p1.Next
        }
        //p指针前进
        p = p.Next
    }
    //将未比较的剩余节点都放到p指针后面
    if p1 != nil {
        p.Next = p1
    }
    if p2 != nil {
        p.Next = p2
    }
    //返回虚拟头节点的下一个节点就是真正的头节点
    return newList.Next
}

5、合并K个升序链表

思路

在合并两个升序链表的基础上依次将这K个链表合并即可

func mergeKLists(lists []*ListNode) (res *ListNode) {
    for _, v := range lists {
        res = mergeTwoLists(res, v)
    }
    return res
}

//mergeTwoLists() 实现见上文

6、分割链表

思路

遍历链表,根据节点的值将节点分到两个链表里,再将链表连接到一起即可

func partition(head *Node, x int) *Node {
    curNode := head
    //存放值小于x的链表的虚拟头节点
    dummy1 := &Node{}
    //存放值大于等于x节点的链表的虚拟头节点
    dummy2 := &Node{}
    p1 := dummy1
    p2 := dummy2
    //遍历链表,将原链表分割为两个
    for curNode != nil {
        if curNode.Val < x {
            p1.Next = curNode
            p1 = p1.Next
        } else {
            p2.Next = curNode
            p2 = p2.Next
        }
        //断开原链表的指针
        temp := curNode.Next
        curNode.Next = nil
        curNode = temp
    }
    //连接两个链表
    p1.Next = dummy2.Next
    return dummy1.Next
}

7、删除链表的倒数第 N 个结点

思路

1、要删除链表的倒数第n个结点就要先找到倒数第n+1个结点
2、如何找:初始化两个双指针,先让p1走k个节点,然后让p1、p2一起走,p1走到nil了,p2所在的位置就是倒数第k个节点

func removeNthFromEnd(head *Node, n int) *Node {
    //虚拟头节点
    p1 := &Node{}
    p1.Next = head
    cur := getKthFromEnd(p1, n+1)
    cur.Next = cur.Next.Next
    return p1.Next
}

//获取链表中倒数第k个节点
func getKthFromEnd(head *Node, k int) *Node {
    p1 := head
    p2 := head
    //先让p1走k步
    for i := 0; i < k; i++ {
        p1 = p1.Next
    }
    for p1 != nil {
        p1 = p1.Next
        p2 = p2.Next
    }
    return p2
}

你可能感兴趣的:(go链表算法双指针面试)