链表类的算法题在面试中是最常呈现的,题目尽管简略,但也非常考验面试者的逻辑思维和算法熟练度,以下通过几道常见的链表题来看看它们惯例的解法吧!(点击题目可到leetcode原题,不定时更新)
1、判断链表是否有环
思路
应用快慢指针遍历链表,快指针每次走2步,慢指针每次走1步,如果两者相遇,则有环,否则没有
package mainimport "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}