链表类的算法题在面试中是最常出现的,题目虽然简单,但也十分考验面试者的逻辑思维和算法熟练度,双指针法是解单链表算法题的常用技巧,以下通过几道常见的链表题来看看它们的使用吧!
题目如下:
- 判断链表是否有环
- 链表的中间结点
- 合并两个有序链表
- 合并K个升序链表
- 分割链表
- 删除链表的倒数第 N 个结点
- 判断回文链表
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、合并两个有序链表
思路
双指针分别遍历两个链表,比较两个节点的大小,将小的放到新的链表里,遍历完一遍后,将未遍历的部分指向新链表后面即可
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
}
4、合并K个升序链表
思路
在合并两个升序链表的基础上依次将这K个链表合并即可
func mergeKLists(lists []*ListNode) (res *ListNode) {
for _, v := range lists {
res = mergeTwoLists(res, v)
}
return res
}
//mergeTwoLists() 实现见上文
5、分割链表
思路
遍历链表,根据节点的值将节点分到两个链表里,再将链表连接到一起即可
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
}
6、删除链表的倒数第 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
}
7、判断回文链表
思路
1、先通过快慢指针找到中间节点,如果是偶数节点,slow指针再向前一步
2、反转后半部分的链表
3、分别从head和slow开始遍历链表,对比值是否一致,如果是一致的则说明是回文链表
func isPalindrome(head *Node) bool {
fast := head
slow := head
//同时遍历链表
for fast != nil && fast.Next != nil {
fast = fast.Next.Next
slow = slow.Next
}
//如果fast为nil则说明是奇数链表,slow需要前进一步
if fast != nil {
slow = slow.Next
}
//反转后半部分的链表
right := reverse(slow)
//从中间和head同时遍历判断值是否一致
left := head
for right != nil {
if right.Val != left.Val {
return false
}
left = left.Next
right = right.Next
}
return true
}
//反转链表
func reverse(head *Node) *Node {
var pre, next *Node
cur := head
for cur != nil {
next = cur.Next
cur.Next = pre
pre = cur
cur = next
}
return pre
}
参考资料:
1、https://labuladong.github.io/...
2、《数据结构与算法之美》
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。