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