代码随想录算法训练营day13| 239. 滑动窗口最大值 347. 前 K 个高频元素

239. 滑动窗口最大值

暴力+剪枝

遍历数组,每个滑动区间计算最大值,当推出的元素不是最大值时无需计算

时间复杂度O(n2)            空间复杂度O(n)

func maxSlidingWindow(nums []int, k int) []int {
	maxVal := nums[0]
	maxCount := 1
	result := make([]int, 0)
	for i := 1; i < k; i++ {
		if nums[i] > maxVal {
			maxVal = nums[i]
			maxCount = 1
		} else if nums[i] == maxVal {
			maxCount++
		}
	}
	result = append(result, maxVal)
	for i := k; i < len(nums); i++ {
		if nums[i] > maxVal {
			maxVal = nums[i]
			maxCount = 1
		} else if nums[i] == maxVal {
			maxCount++
		}
		if nums[i-k] == maxVal {
			maxCount--
			// 弹出最大值,若没有最大值了,则重新计算最大值
			if maxCount == 0 {
				maxVal = nums[i-k+1]
				maxCount = 1
				for _, val := range nums[i-k+2 : i+1] {
					if val > maxVal {
						maxVal = val
						maxCount = 1
					} else if val == maxVal {
						maxCount++
					}
				}
			}
		}
		result = append(result, maxVal)
	}
	return result
}

单调队列

硬控我三个小时,一开始想着当出队值和入队值相等时,直接修改记录front值的索引即可,但是一直错,后来才发现不能这么做,因为单调队列中不仅要保持大小顺序,还要保持入队顺序,另设一个存储索引值和值的结构体的做法本身没问题,但是不能改变索引值,不然会违背顺序的原则

时间复杂度 O(n)         空间复杂度 O(n)

type MyQueue struct {
	elements []int
}

// Size 计算长度
func (this *MyQueue) Size() int {
	return len(this.elements)
}

// Empty 判断是否为空
func (this *MyQueue) Empty() bool {
	return this.Size() == 0
}

// Front 获取队列头部元素
func (this *MyQueue) Front() int {
	return this.elements[0]
}

// Back 获取队列尾部元素
func (this *MyQueue) Back() int {
	return this.elements[this.Size()-1]
}

// Enqueue 进队操作
func (this *MyQueue) Enqueue(value int) {
	// 自定义入队操作,核心步骤之一,构造单调队列
	for !this.Empty() && value > this.Back() {
		this.elements = this.elements[:this.Size()-1]
	}
	this.elements = append(this.elements, value)
}

// Dequeue 出队操作
func (this *MyQueue) Dequeue() int {
	element := this.Front()
	this.elements = this.elements[1:]
	return element
}

func maxSlidingWindow(nums []int, k int) []int {
	myQueue := MyQueue{}
	// 先初始化myQueue,即第一个窗口的情况
	for i := 0; i < k; i++ {
		myQueue.Enqueue(nums[i])
	}
	result := []int{myQueue.Front()}
	// 开始滑动窗口
	for index := k; index < len(nums); index++ {
		if nums[index - k] == myQueue.Front(){
			myQueue.Dequeue()
		}
		// 当遇到相等值或更大值时直接Enqueue
		myQueue.Enqueue(nums[index])
		result = append(result, myQueue.Front())
	}
	return result
}

347. 前 K 个高频元素

哈希+排序

先遍历数组,将数字出现频率记录下来,再将频次排序并输出

时间复杂度 O(nlogn)         空间复杂度 O(n)

type Element struct {
	value     int // 值
	frequency int // 出现频次
}

// 法一:排序 + 哈希
func topKFrequent(nums []int, k int) []int {
	frequencyMap := map[int]*Element{} // 存放出现频率的哈希表
	for _, num := range nums {         // 遍历数组并记录每个数字出现的频率
		if _, ok := frequencyMap[num]; ok {
			frequencyMap[num].frequency++
		} else {
			frequencyMap[num] = &Element{value: num, frequency: 1}
		}

	}
	frequencyList := []Element{} // 记录频率与对应值的数组
	for _, element := range frequencyMap {
		frequencyList = append(frequencyList, *element)
	}
	// 按频率进行排序
	sort.Slice(frequencyList, func(i, j int) bool {
		return frequencyList[i].frequency > frequencyList[j].frequency
	})
	result := make([]int, k)
	for i := 0; i < k; i++ {
		result[i] = frequencyList[i].value
	}
	return result
}

优先队列

由于go中没有优先队列的容器(但有堆的容器,但我没用),所以手搓了一个,花了不少时间,当作是复习堆排序的知识了,注意堆中数组下标的关系:2i+1为左节点的下标,2i+2为右节点的下标

type Element struct {
	value     int // 值
	frequency int // 出现频次
}

// PriorityQueue 优先队列(使用数组实现堆)
type PriorityQueue struct {
	myHeap []Element // 内部维护的一个堆
}

// Size 获取长度
func (pq *PriorityQueue) Size() int {
	return len(pq.myHeap)
}

// UpLeak 上滤函数
func (pq *PriorityQueue) UpLeak() {
	tempCur := pq.Size() - 1 // 新元素的索引 
	for tempCur != 0 && pq.myHeap[tempCur].frequency < pq.myHeap[(tempCur - 1)/2].frequency { // 若小,则与父节点交换
		pq.myHeap[tempCur], pq.myHeap[(tempCur - 1)/2] = pq.myHeap[(tempCur - 1)/2], pq.myHeap[tempCur] // 交换节点
		tempCur = (tempCur - 1)/2 
	}
}

// DownLeak 下渗函数
func (pq *PriorityQueue) DownLeak() {
	tempCur := 0
	if pq.Size() == 1 {
		return
	}
	isLeft := true
	for tempCur*2+1 < pq.Size() {
		isLeft = true
		if tempCur*2+2 < pq.Size() && pq.myHeap[tempCur*2+2].frequency < pq.myHeap[tempCur*2+1].frequency {
			isLeft = false
		}
		if isLeft { // 左边
			if pq.myHeap[tempCur].frequency > pq.myHeap[tempCur*2+1].frequency {
				pq.myHeap[tempCur*2+1], pq.myHeap[tempCur] = pq.myHeap[tempCur], pq.myHeap[tempCur*2+1]
				tempCur = tempCur*2 + 1
			} else {
				return
			}
		} else {
			if pq.myHeap[tempCur].frequency > pq.myHeap[tempCur*2+2].frequency {
				pq.myHeap[tempCur*2+2], pq.myHeap[tempCur] = pq.myHeap[tempCur], pq.myHeap[tempCur*2+2]
				tempCur = tempCur*2 + 2
			} else {
				return
			}
		}
	}
}

// InitHeap 建堆
func (pq *PriorityQueue) InitHeap(nums []Element) {
	// 自上向下建堆(容易实现,但时间复杂度为O(nlogn)
	pq.myHeap = make([]Element, 0)
	pq.myHeap = append(pq.myHeap, nums[0])
	for _, element := range nums[1:] {
		pq.pushBack(element)
	}
}

// pushBack 添加元素到末尾
func (pq *PriorityQueue) pushBack(element Element) {
	pq.myHeap = append(pq.myHeap, element)
	pq.UpLeak()
}

// Front 头部元素
func (pq *PriorityQueue) Front() Element {
	return pq.myHeap[0]
}

// Push 添加新元素
func (pq *PriorityQueue) Push(element Element) {
	if element.frequency > pq.Front().frequency {
		pq.myHeap[0] = element
		pq.DownLeak()
	}
}

// Pop 移除头部元素
func (pq *PriorityQueue) Pop() Element {
	element := pq.myHeap[0]
	pq.myHeap = pq.myHeap[1:]
	return element
}

// 法二:优先队列(堆排序) + 哈希
func topKFrequent(nums []int, k int) []int {
	myQueue := &PriorityQueue{}
	countMap := map[int]int{}
	for _, element := range nums {
		if _, ok := countMap[element]; !ok {
			countMap[element] = 1
		} else {
			countMap[element]++
		}
	}
	countList := make([]Element, 0)
	for key, val := range countMap {
		countList = append(countList, Element{key, val})
	}
	myQueue.InitHeap(countList[:k])
	for _, element := range countList[k:] {
		myQueue.Push(element)
	}
	resultList := make([]int, 0)
	for myQueue.Size() != 0 {
		resultList = append(resultList, myQueue.Pop().value)
	}
	return resultList
}

桶排序

先使用哈希表计算数字出现的频次,再桶排序,最后倒序输出,注意可能出现相同频次的不同数字,故桶中存放一个Slice

时间复杂度: O(n)     空间复杂度:O(n)

// 法三:桶排序
func topKFrequent(nums []int, k int) []int {
	countMap := map[int]int{}
	for _, element := range nums {
		if _, ok := countMap[element]; !ok {
			countMap[element] = 1
		} else {
			countMap[element]++
		}
	}
	boltList := make([][]int, len(nums)+1)  // 考虑到重复频次的不同数字,桶中存放slice

	for key, frequent := range countMap {
		boltList[frequent] = append(boltList[frequent], key)
	}
	resultList := make([]int, 0)
	for index := len(boltList) - 1; index >= 0 && len(resultList) < k; index-- {
		if boltList[index] != nil {
			resultList = append(resultList, boltList[index]...)
		}
	}
	return resultList
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值