经典算法-链表(golang)
type ListNode struct {
Val int
Next *ListNode
}
// 83. Remove Duplicates from Sorted List 删除有序链表中的重复元素
// 解题思路:相同的只改变指针指向,不同的才会移动当前的cur指针,cur作为当前判断的指针
// Input: 1->1->2 Output: 1->2
// Input: 1->1->2->3->3 Output: 1->2->3
func deleteDuplicates(head *ListNode) *ListNode {
if head == nil {
return head
}
cur := head // cur指向头结点,而且改变cur指向不会影响到head的指向
for cur.Next != nil {
if cur.Val == cur.Next.Val { // 当前节点的值等于下个节点的值
cur.Next = cur.Next.Next // cur指向下下个节点(cur指针不会移动)
}else { // 当前节点的值不等于下个节点的值
cur = cur.Next // 当前指针后移到下一个不同值的节点
}
}
return head
}
// 876. Middle of the Linked List 找到链表的中间点
// 解题思路:快慢指针,从head开始跑,快指针结束时,返回慢指针
// Input: 1->2->3->4->5 Output: 3
// Input: 1->2->3->4->5->6 Output: 4
func middleNode(head *ListNode) *ListNode {
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
}
return slow
}
// 206. Reverse Linked List 翻转链表
// 解题思路:将链表分为两个部分:第一个节点和剩余节点
//Input: 1->2->3->4->5->NULL Output: 5->4->3->2->1->NULL
func reverseList(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
return head
}
var pre *ListNode
cur := head
for cur != nil {
//pre, cur, cur.Next = cur, cur.Next, pre //这句话最重要
nextNode := cur.Next // 先保存cur后面的节点
cur.Next = pre // 将cur指向pre,pre刚开始为nil,就相当于最后一位
pre = cur // 这时cur是最新的整个需要被链接的部分,这时赋值给pre,pre就是每次要被cur链接的
cur = nextNode // 继续处理后面的节点
}
return pre
}
// 反转从位置m到n的链表
func reverseBetween(head *ListNode, left int, right int) *ListNode { dummy := &ListNode{-1, head} prem := dummy for i := 0; i < left-1; i++ { prem = prem.Next } var pre *ListNode cur := prem.Next for i := left; i <= right; i++ { nextNode := cur.Next cur.Next = pre pre = cur cur = nextNode } // prem.Next为left节点,原始left节点应该指向right+1节点(left节点的下一个点应该指向翻转链表的尾节点) 画图最清晰!!! prem.Next.Next = cur prem.Next = pre // prem指向pre(反转之后的头节点) return dummy.Next }
// k个一组翻转链表(hard) func reverseKGroup( head *ListNode , k int ) *ListNode { var pre, nextNode *ListNode cur, p := head, head index := 0 for ; p != nil && index < k; index++ { p = p.Next } if index == k { for i := 0; cur != nil && i < k; i++ { nextNode = cur.Next cur.Next = pre pre = cur cur = nextNode } // 尾节点不为空,继续递归地执行上述过程 if nextNode != nil { head.Next = reverseKGroup(nextNode, k) } return pre // pre记录了为子链表翻转后的头结点 } else { return head // 子链表长度不足k,不翻转 } }
// 141. Linked List Cycle 判断链表是否有环
// 解题思路:快慢指针,从head开始跑,快指针结束前,一直判断slow == fast
// Input: head = [3,2,0,-4], pos = 1 Output: true
// Input: head = [1,2], pos = 0 Output: true
func hasCycle(head *ListNode) bool {
if head == nil {
return false
}
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
if slow == fast {
return true
}
}
return false
}
// 237. Delete Node in a Linked List 删除某个节点
// 解题思路:相当于用下个节点来替换当前节点
// Input: head = [4,5,1,9], node = 5 Output: [4,1,9]
// Input: head = [4,5,1,9], node = 1 Output: [4,5,9]
func deleteNode(node *ListNode) {
node.Val = node.Next.Val
node.Next = node.Next.Next
}
// 203. Remove Linked List Elements 删除所有等于这个值的节点
// 解题思路:注意如何删除头结点,所以需要额外创建一个节点p,指向头结点,头结点是第一结点,只是一般没有数据
// Input: 1->2->6->3->4->5->6, val = 6 Output: 1->2->3->4->5
func removeElements(head *ListNode, val int) *ListNode {
p := &ListNode{-1, head}
cur := p
for cur.Next != nil {
if cur.Next.Val == val { // 只要等于val,都要跳过该节点,cur此时不会移动,因为下一次判断都是cur.Next
cur.Next = cur.Next.Next
}else {
cur = cur.Next // 不等的时候,cur后移一步
}
}
return p.Next // 不能return head, 因为head有可能就是要删除的节点
}
// 234. Palindrome Linked List 判断是否为回文链表
// 解题思路:找到中心点,如果中心点是奇数需要+1(不需要比较这个节点),然后将链表后半段翻转和前半段进行比较(后半段链表个数作循环条件)
// Input: 1->2 Output: false
// Input: 1->2->2->1 Output: true
func isPalindrome(head *ListNode) bool {
dummyP, midP := head, head
for dummyP != nil && dummyP.Next != nil {
dummyP = dummyP.Next.Next
midP = midP.Next
}
// 如果是奇数
if dummyP != nil {
midP = midP.Next
}
midP = reverseList(midP)
for midP != nil {
if head.Val == midP.Val {
head, midP = head.Next, midP.Next
continue
}
return false
}
return true
}
// 160. Intersection of Two Linked Lists 找到两个链表(没有环)的交叉点
// 解题思路:不能使用暴力破解法,循环遍历A,B。 应该两个指针一起走,短链表先到达终点,从那一刻开始算,长链表继续走直到终点同时长链表的头指针也在走,
// 等到终点的时候,长短链表的长度一样了,最后循环判断他们,只要有一个节点相等就ok了
// Input: intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
// Output: Reference of the node with value = 8
func getIntersectionNode(headA, headB *ListNode) *ListNode {
curA, curB := headA, headB
for curA != nil && curB != nil {
curA = curA.Next
curB = curB.Next
}
for curA != nil {
curA = curA.Next
headA = headA.Next
}
for curB != nil {
curB = curB.Next
headB = headB.Next
}
for headA != headB {
headA = headA.Next
headB = headB.Next
}
return headA
}
// 19. Remove Nth Node From End of List 删除倒数第n个节点
// 解题思路: 定义快慢指针,快的先走n步,然后快慢再一起走,直到快指针Next为空, 记得返回的是p.Next而不是head,因为head也有可能被删
// input: list: 1->2->3->4->5, and n = 2
// Output: 1->2->3->5
func removeNthFromEnd(head *ListNode, n int) *ListNode {
p := &ListNode{-1, head}
slow, fast := p, p
for ; n > 0; n-- {
fast = fast.Next
}
for fast.Next != nil {
slow = slow.Next
fast = fast.Next
}
slow.Next = slow.Next.Next
return p.Next
}
// 142. Linked List Cycle II 找到链表中环的起点
// 方法一:解题思路:使用额外内存map,将链表的节点存进map,判断如果有相同的点,则返回节点(即为环的起点)
func detectCycle(head *ListNode) *ListNode {
m := make(map[*ListNode]int)
cur := head
for ; cur != nil; cur = cur.Next {
if _, ok := m[cur]; ok {
return cur
}
m[cur] = 1
}
return nil
}
// 方法二:链表中环的入口结点
func detectCycle(head *ListNode) *ListNode {
if head == nil {
return nil
}
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow, fast = slow.Next, fast.Next.Next
if slow == fast {
break
}
}
slow = head
for slow != fast {
slow, fast = slow.Next, fast.Next
}
return slow
}
// 148. Sort List 链表排序
// 解题思路(需要额外内存):使用额外内存slice,将链表的节点存进slice,然后sort.Ints排序后,再回写到链表
func sortList(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
return head
}
var list []int
cur, cur1 := head, head
for cur != nil {
list = append(list, cur.Val)
cur = cur.Next
}
sort.Ints(list)
for _, v := range list {
cur1.Val = v
cur1 = cur1.Next
}
return head
}
// 148. Sort List 链表排序
// 解题思路(不需要额外内存):分治法-归并排序,需要用到递归
func sortList(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
return head
}
var pre *ListNode
slow, fast := head, head
for fast != nil && fast.Next != nil {
pre = slow
slow, fast = slow.Next, fast.Next.Next
}
pre.Next = nil // 中间截断,slow的前面一个节点作为head的结束点
l := sortList(head)
r := sortList(slow)
return func (l, r *ListNode) *ListNode {
list := &ListNode{}
cur := list
for l != nil && r != nil {
if l.Val <= r.Val {
cur.Next = l
l = l.Next
}else {
cur.Next = r
r = r.Next
}
cur = cur.Next
}
if l == nil {
cur.Next = r
}
if r == nil {
cur.Next = l
}
return list.Next
}(l, r)
}
func mergeList(l, r *ListNode) *ListNode {
list := &ListNode{}
cur := list
for l != nil && r != nil {
if l.Val <= r.Val {
cur.Next = l
l = l.Next
}else {
cur.Next = r
r = r.Next
}
cur = cur.Next
}
if l == nil {
cur.Next = r
}
if r == nil {
cur.Next = l
}
return list.Next
}
// 21. Merge Two Sorted Lists 合并两个有序链表
// 解题思路:新创建一个结构体,比较l1和l2大小,赋值给cur.Next,然后都后移一步
// Input: 1->2->4, 1->3->4
// Output: 1->1->2->3->4->4
func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
head := &ListNode{}
cur := head
for l1 != nil && l2 != nil {
if l1.Val <= l2.Val {
cur.Next = l1
l1 = l1.Next
}else {
cur.Next = l2
l2 = l2.Next
}
cur = cur.Next
}
if l1 == nil && l2 != nil {
cur.Next = l2
}
if l1 != nil && l2 == nil {
cur.Next = l1
}
return head.Next
}
// 23. Merge k Sorted Lists 合并k个有序链表
// 解题思路:使用额外内存slice,遍历将链表的节点存进slice,然后sort.Ints排序后,再回写到链表
/*Input:
[
1->4->5,
1->3->4,
2->6
]
Output: 1->1->2->3->4->4->5->6
*/
func mergeKLists(lists []*ListNode) *ListNode {
if len(lists) == 0 {
return nil
}
var sliceList []int
for _, v := range lists {
for v != nil {
sliceList = append(sliceList, v.Val)
v = v.Next
}
}
if sliceList == nil {
return nil
}
sort.Ints(sliceList)
head := &ListNode{}
cur := head
for k, v := range sliceList {
cur.Val = v
if k + 1 == len(sliceList) {
cur.Next = nil
}else {
cur.Next = &ListNode{}
cur = cur.Next
}
}
return head
}
第二种方法:不使用额外内存 func mergeKLists(lists []*ListNode) *ListNode { if len(lists) == 0 { return nil } if len(lists) == 1 { return lists[0] } mid := len(lists) >> 1 return mergeTwoList(mergeKLists(lists[:mid]), mergeKLists(lists[mid:])) } func mergeTwoList(l1, l2 *ListNode) *ListNode { head := &ListNode{} cur := head for l1 != nil && l2 != nil { if l1.Val < l2.Val { cur.Next = l1 l1 = l1.Next }else { cur.Next = l2 l2 = l2.Next } cur = cur.Next } if l1 == nil { cur.Next = l2 } if l2 == nil { cur.Next = l1 } return head.Next }
// https://segmentfault.com/a/1190000020730451?utm_source=tag-newest
// 24. Swap nodes in Pairs 两两翻转,或者翻转链表中的某一段
// 解题思路: 处理前置节点pre和b.next,然反转a,b节点
// input: 1->2->3->4
// output: 2->1->4->3
func swapPairs(head *ListNode) *ListNode {
pre := &ListNode{-1, head}
cur := pre
for cur.Next != nil && cur.Next.Next != nil {
a := cur.Next
b := cur.Next.Next
cur.Next = b
a.Next = b.Next
b.Next = a
cur = a
}
return pre.Next
}

两两翻转,画这个图一目了然!!!
1.平时刷题一定要总结归纳,最好分类。比如关于树的题型,链表的,数组等等,观察它们的解题思路,总结出解题套路。
2.积累工具类算法。什么叫工具类算法?就是你解一道算法题需要用到另一种算法,这个被调用的算法就是解决这道算法题的工具。比如常见的「深度优先遍历」、「广度优先遍历」、「01背包」、「KMP算法」以及常见的选择和排序算法都是经常使用的工具类算法。
3.学会抽象题目。笔试算法题不同于面试算法,不会直白跟你说要使用哪种算法去解答,更多的要自己学会抽象,抛开题目本身,要明白内部讲的是什么,别被题目的糖衣炮弹迷惑了。只有把题目抽象成最原始的算法你才能更好地使用工具类算法进行解答。
剑指offer算法---Go实现
这下面也有一些经典的题目:
https://segmentfault.com/a/1190000020062117

浙公网安备 33010602011771号