代码随想录训练营day12|栈与队列03

Go基础包之"heap"

基本概念

堆分为大顶堆小顶堆。大顶堆的堆顶元素最大,子节点元素小于父节点,但是子节点直接没有必然的大小关系。堆的结构是一颗完全二叉树

图片来源:CSDN @i-neojos

 图片来源:CSDN @ i-neojos

一般使用数组来存储完全二叉树,也就是用数组来存储堆结构。节点之间的数组索引关系如下图所示:

heap-relation

Go语言中的container/heap包提供了堆这个数据结构,我们不需要从头开始实现堆这个数据结构。

其核心接口为:

heap.Interface

type Interface interface {
        sort.Interface
        Push(x interface{}) // 将x添加至元素Len()的位置
        Pop() interface{}   // 移除并且返回元素Len()-1
}

实现该接口的任何类型都可以用作具有以下不变量的最小堆(在调用Init()后或数据为空或排序时建立)

注意,接口中的Pop()Push()是由实现该接口的对象调用的。要实现符合堆数据结构特征的添加和删除内容,需要使用heap.Pop()heap.Push()

sort.Interface

type Interface interface {
        Len() int   // Len是集合中的元素个数。
        Less(i, j int) bool
        Swap(i, j int) // Swap交换索引i和索引j的元素
}

任意实现了该接口的结构体,均可以被sort包下的各种方法进行排序。这些方法同通过整数索引来引用基础集合的元素。

实现了这五个方法的数据类型才能使用go标准库给我们提供的heap

初始化

Init()函数建立此包中其他例程所需的堆不变量。

Init()对于堆不变量是幂等的(可被多次调用),并且可以在堆不变量失效时调用。

Init()的时间复杂度是O(n)n为堆中的元素个数。

更多详细内容:

Go heap详解

347. 前 K 个高频元素

题目链接:347. 前 K 个高频元素

这个题目其实不是很难,其关键点有两个:1)如何计算元素的频率;2)如何选取前K个高频元素

  • 第一点比较简单,一个map数据结构就能够统计出元素出现的频率
  • 第二点,一般的初始想法都是将map中的key值根据value进行排序,然后取前K个

题目需求是前K个高频元素,如果维护一个全排列的数据是需要耗费大量时间的,特别是在大规模数据下。因此,我们需要找一个数据结构来维护前K个key值数据结构,这里可以使用的特性。小顶堆:堆顶元素最小。维护一个大小为K的小顶堆,我们只需要比较新的元素和堆顶的大小关系,大于执行Push操作小于则无操作。这样堆里的元素就是前K个高频元素。

package main

import "container/heap"

type HeapInts [][2]int

func (h *HeapInts) Len() int {
	return len(*h)
}

func (h *HeapInts) Less(i, j int) bool {
	return (*h)[i][1] < (*h)[j][1]
}

func (h *HeapInts) Swap(i, j int) {
	(*h)[i], (*h)[j] = (*h)[j], (*h)[i]
}

func (h *HeapInts) Push(x interface{}) {
	*h = append(*h, x.([2]int))
}

func (h *HeapInts) Pop() interface{} {
	temp := (*h)[len(*h)-1]
	*h = (*h)[:len(*h)-1]
	return temp
}

func topKFrequent(nums []int, k int) []int {

	map1 := make(map[int]int, 0)
	headInt := &HeapInts{}
	heap.Init(headInt)
	res := make([]int, k)
	for i := 0; i < len(nums); i++ {
		map1[nums[i]]++
	}

	for key, v := range map1 {
		heap.Push(headInt, [2]int{key, v})
		if len(*headInt) > k {
			heap.Pop(headInt)
		}
	}
	for i := 0; i < k; i++ {
		res[k-i-1] = heap.Pop(headInt).([2]int)[0]
	}
	return res
}

// 大顶堆和小顶堆:擅长从大规模数据中找前k个高频或者低频元素
// 堆是从堆顶移除元素,所以,求前k个高频元素使用小顶堆

239. 滑动窗口最大值

题目链接:239. 滑动窗口最大值

这个题目使用到的数据结构为单调队列:维护元素单调递减的队列(队头--->队尾)

题目的难点在于如何求一个区间里的最大值。最开始的想法是直接暴力搜索,但是时间复杂度为O(n*k)。

使用单调队列的优势在于,可以维护窗口里的元素在队列中,窗口每移动一次,队列也一进一出,然后给出每次移动之后窗口里的最大值是什么。

基于上述描述,我们是需要对队列里的元素进行排序的,所以我们需要考虑队列元素变化的两个时刻,应该如何操作队列里的元素。窗口进行移动时:

  • 窗口弹出的元素,只需要判断是否与队头元素相同,相同就出队;不相同不需要操作
  • 窗口新进的元素,大于队尾元素,则弹出队尾元素,直到队尾元素大于等于窗口新进的元素

239.滑动窗口最大值-2

图片参考:代码随想录

 还有另外一种想法,使用上面提到的堆数据结构。使用大顶堆,窗口每次移动之后,都弹出堆顶元素。但是有一个问题,需要维护堆内元素仅为窗口内元素,而堆只能从堆顶弹出,所以使用起来较为不方便。这里不考虑使用。

func maxSlidingWindow(nums []int, k int) []int {

	queue := make([]int, 0)
	res := []int{}

	for i := 0; i < k; i++ {
		if len(queue) == 0 {
			queue = append(queue, nums[i])
		} else {
			for len(queue) != 0 {
				if nums[i] > queue[len(queue)-1] {
					queue = queue[:len(queue)-1]
				} else {
					break
				}
			}
			queue = append(queue, nums[i])
		}
	}
	res = append(res, queue[0])

	for i := 1; i < len(nums)-k+1; i++ {
		if nums[i-1] == queue[0] {
			queue = queue[1:]
		}
		// 新进窗口的元素,如果比单调队列中尾部的元素大,则弹出队列尾部元素,直到小于队尾元素
		for len(queue) != 0 {
			if nums[i+k-1] > queue[len(queue)-1] {
				queue = queue[:len(queue)-1]
			} else {
				break
			}
		}
		queue = append(queue, nums[i+k-1])
		res = append(res, queue[0])
	}
	return res
}

 栈和队列总结239.滑动窗口最大值-2

通过括号匹配问题、字符串去重问题、逆波兰表达式问题掌握栈在做题当中的使用技巧。

通过求滑动窗口最大值,以及前K个高频元素介绍了两种队列:单调队列和优先级队列

主要需要了解在Go语言中,是如何实现常见的数据结构以及使用方法。在滑动窗口最大值问题中学会了使用单调队列,优先级队列的实现在这一块还没有掌握,其底层实现是利用堆的数据结构,后续需要对其进行实现,掌握其底层原理。

优先队列实现

import (
	"container/heap"
	"fmt"
	"testing"
)

// Item 是我们在优先队列中管理的东西
type Item struct {
	value    string // The value of the item; arbitrary.
	priority int    // 队列中项目的优先级
	// The index is needed by update and is maintained by the heap.Interface methods.
	index int //在堆中的索引
}

// 优先级队列
type PriorityQueue []*Item

func (pq PriorityQueue) Len() int { return len(pq) }

func (pq PriorityQueue) Less(i, j int) bool {
	// 我们希望 Pop 给我们最高而不是最低的优先级,所以我们使用比这里更大的优先级。
	return pq[i].priority > pq[j].priority
}

func (pq PriorityQueue) Swap(i, j int) {
	pq[i], pq[j] = pq[j], pq[i]
	pq[i].index = i
	pq[j].index = j
}

func (pq *PriorityQueue) Push(x interface{}) {
	n := len(*pq)
	item := x.(*Item)
	item.index = n
	*pq = append(*pq, item)
}

func (pq *PriorityQueue) Pop() interface{} {
	old := *pq
	n := len(old)
	item := old[n-1]
	old[n-1] = nil  // 避免内存泄漏
	item.index = -1 // 为了安全
	*pq = old[0 : n-1]
	return item
}

// update 修改队列中项目的优先级和值。
func (pq *PriorityQueue) update(item *Item, value string, priority int) {
	item.value = value
	item.priority = priority
	heap.Fix(pq, item.index)
}

// This example creates a PriorityQueue with some items, adds and manipulates an item,
// and then removes the items in priority order.
func Test_priorityQueue(t *testing.T) {
	// Some items and their priorities.
	items := map[string]int{
		"banana": 3, "apple": 2, "pear": 4,
	}

	// Create a priority queue, put the items in it, and
	// establish the priority queue (heap) invariants.
	pq := make(PriorityQueue, len(items))
	i := 0
	for value, priority := range items {
		pq[i] = &Item{
			value:    value,
			priority: priority,
			index:    i,
		}
		i++
	}
	heap.Init(&pq)

	// 插入一个新项目,然后修改其优先级.
	item := &Item{
		value:    "orange",
		priority: 1,
	}
	heap.Push(&pq, item)
	pq.update(item, item.value, 5)

	// Take the items out; they arrive in decreasing priority order.
	for pq.Len() > 0 {
		item := heap.Pop(&pq).(*Item)
		fmt.Printf("%.2d:%s ", item.priority, item.value)
	}
	// Output:
	// 05:orange 04:pear 03:banana 02:apple
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值