leetcode之374前K個高頻元素Golang
阿新 • • 發佈:2020-11-14
題目描述
給定一個非空的整數陣列,返回其中出現頻率前 *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
}