經典算法-鏈表(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
}

// 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

相關文章
相關標籤/搜索