1. 程式人生 > 實用技巧 >leetcode之374前K個高頻元素Golang

leetcode之374前K個高頻元素Golang

題目描述

給定一個非空的整數陣列,返回其中出現頻率前 *k* 高的元素。

示例 1:

輸入: nums = [1,1,1,2,2,3], k = 2
輸出: [1,2]

示例 2:

輸入: nums = [1], k = 1
輸出: [1]

提示:

  • 你可以假設給定的 k 總是合理的,且 1 ≤ k ≤ 陣列中不相同的元素的個數。
  • 你的演算法的時間複雜度必須優於 O(n log n) , n 是陣列的大小。
  • 題目資料保證答案唯一,換句話說,陣列中前 k 個高頻元素的集合是唯一的。
  • 你可以按任意順序返回答案。

演算法

本題的主要解題過程就是先用一個map儲存數字和它出現次數的鍵值對,然後對這些鍵值對進行從大到小排序,選出前K

個元素就可以了

所以本題的核心就是進行排序,我們在本題採用的排序方法是堆排序,也怪我,前面不知道go原始碼已經實現了堆排序的庫,我們直接呼叫container/heap這個包就可以了,所以我先自己實現了堆排序,然後又用go語言內建的sort排序實現了一遍,最後採用go內建的堆排序又實現了一遍,所以本題我採用了三種解法

首先是我自己的堆排序

  • 首先遍歷map中的鍵值對,然後用這些鍵值對組成一棵完全二叉樹
  • 然後調整堆結構,讓它成為一個大頂堆
    • ·採用層序遍歷的方法
      • 比較當前子樹根節點左右孩子結點的大小,保證根結點的值比孩子結點的值大,如果根結點的值小,就和孩子結點交換值
      • 層序遍歷完成以後,如果上一次沒有改變堆的結構,說明就已經是一個大頂堆了,否則再次層序遍歷堆
  • 重複下面這個過程
    • 取出堆的根結點
    • 然後將最後的結點位於根結點的位置,刪除最後一個結點
    • 重新調整堆結構,保證是一個大頂堆
    • 如果沒有取夠k個值,就繼續這個迴圈,然後再次獲取

我的堆結構如下圖所示:

右邊的矩形是一個數組,裡面每一個元素是一個連結串列的頭結點。連結串列的作用是用來定位最後一個結點的位置,例如當前最後一個結點是1,當我們將1移動到根結點並且刪除原來結點1以後,新的最後一個結點就是2

程式碼

func topKFrequent2(nums []int, k int) []int {
	// 先用一個map來儲存數字以及出現的次數
	numsMap := make(map[int]int)
	for _, num := range nums {
		numsMap[num]++
	}
	// 用這個map構建二叉樹,用二叉樹實現堆
	type node struct {
		num        int
		times      int
		leftChild  *node
		rightChild *node
		leftLoc    *node
		parent     *node
	}
	var root *node
	var topQue, downQue []*node
	var lOrR, newLine bool
	var parentNode *node
	var levelList []*node
	for key, value := range numsMap {
		tmpNode := &node{
			num:   key,
			times: value,
		}
		if root == nil {
			// 此時樹還是空的
			root = tmpNode
			topQue = append(topQue, root)
			levelList = append(levelList, root)
		} else {
			// 此時樹不是空的,從上層隊列出隊,下層佇列入隊
			if !newLine {
				// 這是新的一層
				levelList = append(levelList, tmpNode)
				newLine = !newLine
			} else {
				// 不是新的一層
				// 在每一層的連結串列頭加上結點
				levelList[len(levelList)-1], tmpNode.leftLoc = tmpNode, levelList[len(levelList)-1]
			}
			if !lOrR {
				parentNode = topQue[0]
				topQue = topQue[1:]
				parentNode.leftChild = tmpNode
				parentNode.leftChild.parent = parentNode
				downQue = append(downQue, tmpNode)
			} else {
				parentNode.rightChild = tmpNode
				parentNode.rightChild.parent = parentNode
				downQue = append(downQue, tmpNode)
				if len(topQue) == 0 {
					topQue, downQue = downQue, topQue
					newLine = !newLine
				}
			}
			// downQue = append(downQue, tmpNode)
			lOrR = !lOrR
		}
	}
	// 然後將二叉樹轉化為大頂堆,按層序遍歷
	topQue = []*node{root}
	var alterFlag bool
	for {
		if len(topQue) == 0 {
			if !alterFlag {
				break
			} else {
				topQue = []*node{root}
				alterFlag = false
			}
		}
		downQue = []*node{}
		for _, tmpNode := range topQue {
			if tmpNode.leftChild == nil && tmpNode.rightChild == nil {
				continue
			} else if tmpNode.leftChild != nil && tmpNode.rightChild != nil {
				// 既有左孩子又有右孩子
				downQue = append(downQue, tmpNode.leftChild, tmpNode.rightChild)
				var largerChild *node
				if tmpNode.leftChild.times >= tmpNode.rightChild.times {
					largerChild = tmpNode.leftChild
				} else {
					largerChild = tmpNode.rightChild
				}
				if tmpNode.times < largerChild.times {
					alterFlag = true
					tmpNode.num, tmpNode.times, largerChild.num, largerChild.times = largerChild.num, largerChild.times, tmpNode.num, tmpNode.times
				}
			} else {
				// 此時必定只有左孩子
				downQue = append(downQue, tmpNode.leftChild)
				if tmpNode.times < tmpNode.leftChild.times {
					alterFlag = true
					tmpNode.num, tmpNode.times, tmpNode.leftChild.num, tmpNode.leftChild.times = tmpNode.leftChild.num, tmpNode.leftChild.times, tmpNode.num, tmpNode.times
				}
			}
		}
		topQue = downQue
	}
	// 上面已經構造了一個大頂堆,每次只需要取出堆頂的資料就可以了,取k次
	var res []int
	for {
		res = append(res, root.num)
		k--
		if k == 0 {
			break
		}
		// 取完再調整堆
		lastNode := levelList[len(levelList)-1]
		root.num, root.times, lastNode.num, lastNode.times = lastNode.num, lastNode.times, root.num, root.times
		if levelList[len(levelList)-1] = lastNode.leftLoc; levelList[len(levelList)-1] == nil {
			levelList = levelList[:len(levelList)-1]
		}
		if lastNode.parent.rightChild != nil {
			lastNode.parent.rightChild = nil
		} else {
			lastNode.parent.leftChild = nil
		}
		// 再次調整堆
		tmpRoot := root
		for {
			if tmpRoot.leftChild == nil && tmpRoot.rightChild == nil {
				break
			} else if tmpRoot.leftChild != nil && tmpRoot.rightChild != nil {
				var largerChild *node
				if tmpRoot.leftChild.times >= tmpRoot.rightChild.times {
					largerChild = tmpRoot.leftChild
				} else {
					largerChild = tmpRoot.rightChild
				}
				if tmpRoot.times >= largerChild.times {
					break
				} else {
					tmpRoot.num, tmpRoot.times, largerChild.num, largerChild.times = largerChild.num, largerChild.times, tmpRoot.num, tmpRoot.times
					tmpRoot = largerChild
					continue
				}
			} else {
				// 只存在左孩子
				if tmpRoot.times >= tmpRoot.leftChild.times {
					break
				} else {
					tmpRoot.num, tmpRoot.times, tmpRoot.leftChild.num, tmpRoot.leftChild.times = tmpRoot.leftChild.num, tmpRoot.leftChild.times, tmpRoot.num, tmpRoot.times
					tmpRoot = tmpRoot.leftChild
					continue
				}
			}
		}
	}
	return res
}

第二個演算法

首先同樣使用map儲存鍵值對,然後對這些鍵值對進行排序,使用官方的sort需要實現sort.Interface這個介面

type Interface interface {
    // Len方法返回集合中的元素個數
    Len() int
    // Less方法報告索引i的元素是否比索引j的元素小
    Less(i, j int) bool
    // Swap方法交換索引i和j的兩個元素
    Swap(i, j int)
}

程式碼如下

type numMapToTimes struct {
	num   int
	times int
}

type mapSlice []*numMapToTimes
func (m mapSlice) Len() int {
	return len(m)
}
func (m mapSlice) Less(i, j int) bool {
	if m[i].times >= m[j].times {
		return true
	}
	return false
}
func (m mapSlice) Swap(i, j int) {
	m[i].num, m[i].times, m[j].num, m[j].times = m[j].num, m[j].times, m[i].num, m[i].times
}
func topKFrequent1(nums []int, k int) []int {
	var res []int
	numsMap := make(map[int]int)
	for _, num := range nums {
		numsMap[num]++
	}
	var slice mapSlice
	for key, v := range numsMap {
		tmpMap := &numMapToTimes{
			num:   key,
			times: v,
		}
		slice = append(slice, tmpMap)
	}
	sort.Sort(slice)
	for i := 0; i < k; i++ {
		res = append(res, slice[i].num)
	}
	return res
}

第三個演算法就是採用官方的堆排序

需要實現的介面container/heap.Interface:

type Interface interface {
    sort.Interface
    Push(x interface{}) // 向末尾新增元素
    Pop() interface{}   // 從末尾刪除元素
}

程式碼:

type numMapToTimes struct {
	num   int
	times int
}

type mapSlice []*numMapToTimes
// 當實現sort.Interface的時候,要用值實現哦
func (m mapSlice) Len() int {
	return len(m)
}
func (m mapSlice) Less(i, j int) bool {
	if m[i].times >= m[j].times {
		return true
	}
	return false
}
func (m mapSlice) Swap(i, j int) {
	m[i].num, m[i].times, m[j].num, m[j].times = m[j].num, m[j].times, m[i].num, m[i].times
}

func (m *mapSlice) Push(x interface{}) {
	// 向末尾新增元素
	// item:=x.(*mapSlice)
	// *m = append(*m,*item...)
	// 因為這個方法是指標實現的,所以當呼叫指標指向的值的時候,需要在前面加*
	// 類似於x.(type)這種只有當x的型別是interface{}的時候使用,並且它的作用是檢查型別是否匹配
	// 因為一個介面型別中包含了值域和型別域
	// x.(int)就是檢查型別域是不是int,x.(*numMapToTimes)檢查型別域是不是numMapToTimes指標
	*m = append(*m, x.(*numMapToTimes))
}

func (m *mapSlice) Pop() interface{} {
	// 從末尾刪除元素
	// 這個方法還是指標實現的,所以前面加*,並且根據優先順序,*m需要括起來
	res := (*m)[len(*m)-1]
	*m = (*m)[:len(*m)-1]
	return res
}

func topKFrequent(nums []int, k int) []int {
	res := make([]int, 0)
	numsMap := make(map[int]int)
	for _, num := range nums {
		numsMap[num]++
	}
	slice := &mapSlice{}
	// 用實現了heap.Interface介面的型別來初始化堆
	heap.Init(slice)
	for key, value := range numsMap {
		tmpMap := &numMapToTimes{key, value}
		// 向堆中加入資料
		heap.Push(slice, tmpMap)
	}
	for i := 0; i < k; i++ {
		// 從堆中彈出資料
		elem := heap.Pop(slice)
		res = append(res, elem.(*numMapToTimes).num)
	}
	return res
}