青训营X豆包MarsCode 技术训练营第一篇——查找热点数据问题(题解与总结) | 豆包MarsCode AI 刷题

154 阅读6分钟

查找热点数据问题

问题描述

给你一个整数数组 nums 和一个整数 k,请你用一个字符串返回其中出现频率前 k 高的元素。请按升序排列。

你所设计算法的时间复杂度必须优于 O(n log n),其中 n 是数组大小。

输入

  • nums: 一个正整数数组
  • k: 一个整数

返回

返回一个包含 k 个元素的字符串,数字元素之间用逗号分隔。数字元素按升序排列,表示出现频率最高的 k 个元素。

参数限制

  • 1 <= nums[i] <= 10^4
  • 1 <= nums.length <= 10^5
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

测试样例

样例1:

输入:nums = [1, 1, 1, 2, 2, 3], k = 2
输出:"1,2"
解释:元素 1 出现了 3 次,元素 2 出现了 2 次,元素 3 出现了 1 次。因此前两个高频元素是 1 和 2。

样例2:

输入:nums = [1], k = 1
输出:"1"

样例3:

输入:nums = [4, 4, 4, 2, 2, 2, 3, 3, 1], k = 2

1. 题目解析

在这道题中,我们被要求从给定的整数数组中找出出现频率最高的 k 个元素,并按升序排列。要求算法的时间复杂度优于 O(n log n),因此我们需要避免使用 sort()O(n log n) 复杂度的排序算法,而应该通过高效的数据结构和算法来解决问题。

题目分析与思路:

  • 我们首先需要统计数组中各个元素的出现频率。Python 的 collections.Counter 类可以帮助我们快速完成这一任务,时间复杂度为 O(n),其中 n 是数组的长度。
  • 接着,我们需要找出出现频率最高的 k 个元素。常规的方法是使用排序,但考虑到我们要求时间复杂度优于 O(n log n),可以使用 heapq.nlargest() 函数来获取频率最高的 k 个元素。这个函数的时间复杂度是 O(n log k),比排序要高效得多。
  • 最后,题目要求输出结果时要按升序排列,因此我们在得到频率最高的 k 个元素后,需对其进行排序,时间复杂度是 O(k log k),通常 k 的值远小于 n

算法步骤:

  1. 统计元素频率:利用 Counter 对数组进行计数,得到每个元素的频次。
  2. 获取前 k 个高频元素:使用 heapq.nlargest(k, ...) 获取频率最高的 k 个元素。
  3. 排序结果:对 k 个高频元素按升序排序并返回。

代码实现:

from collections import Counter
import heapq

def solution(nums, k):
    # 1. 统计频率
    frequency = Counter(nums)
    
    # 2. 使用堆来找出频率前 k 的元素
    # 这里使用 heapq.nlargest 获取频率前 k 的元素
    top_k = heapq.nlargest(k, frequency.keys(), key=lambda x: frequency[x])
    
    # 3. 对结果进行排序
    return ','.join(map(str, sorted(top_k)))

if __name__ == "__main__":
    # 测试用例
    print(solution([1, 1, 1, 2, 2, 3], 2))  # "1,2"
    print(solution([1], 1))                  # "1"
    print(solution([4, 4, 4, 2, 2, 2, 3, 3, 1], 2))  # "2,4"

代码分析:

  1. Counter(nums):这个操作的时间复杂度是 O(n),用于统计每个元素的出现次数。
  2. heapq.nlargest(k, frequency.keys(), key=lambda x: frequency[x]):这里使用 heapq.nlargest 获取出现频率最高的 k 个元素。该操作的时间复杂度是 O(n log k),比排序更高效。
  3. sorted(top_k):最后对 k 个元素按升序排序,时间复杂度是 O(k log k),一般情况下,k 的大小远小于 n,因此排序操作的影响较小。

2. 知识总结

通过解这道题,我总结了一些提高刷题效率和算法设计的关键点:

(1) 频率统计的技巧

  • 在很多与统计相关的题目中,collections.Counter 是一个非常有用的工具。它能够高效地统计数组中每个元素的出现次数,并且返回一个字典形式的数据结构,其中键为元素,值为频次。

(2) 堆的使用

  • 在解决“前 k 高频元素”类问题时,使用堆是一个很常见的技巧。通过堆可以在 O(n log k) 的时间内快速找到频率最高的 k 个元素,避免了全数组排序的开销。
  • Python 提供的 heapq.nlargest 函数,能够根据自定义的比较规则,返回前 k 个元素,并且时间复杂度优于排序方法。

(3) 时间复杂度分析

  • 在优化算法时,选择合适的数据结构非常重要。在这个问题中,Counterheapq 的结合使得我们能够在保证效率的前提下,完成任务。整体的时间复杂度为 O(n log k + k log k),其中 n 为数组的长度,k 是要找出的高频元素的个数。对于大多数场景,k 会比 n 小很多,因此这个复杂度是可以接受的。

3. 学习计划

通过豆包MarsCode AI 刷题,我发现了一些高效学习算法的技巧和方法,结合我自己的学习体会,我制定了如下的学习计划:

(1) 刷题目标的制定

  • 每周设置一个清晰的刷题目标,例如学习一个特定的数据结构(如堆、哈希表、树)或解决一个具体类型的问题(如排序、查找、动态规划)。
  • 每次学习后,确保能独立解决相同类型的题目,并反思自己的解题思路和优化策略。

(2) 针对性学习与定期回顾

  • 在刷题过程中,尤其是遇到不会的题目时,可以通过右侧的豆包MarsCode AI,得到一些代码的提示,通过自己的思考写出完整的代码,又或者通过豆包MarsCode AI直接得到整体的思路,自己转换成正确的代码,但更重要的是理解和消化解法。对于未做出的题,应该重点分析原因,重新推演解法,可以手写算法的实现过程,以及伪代码等等。
  • 将未能做正确的题进行整理,可以按题型定期回顾和重新练习,以此提高解决类似问题的能力。

(3) 做题后总结

  • 每做完一道题目后,都应该总结自己的思路,包括:
    • 题目类型和常见解法。
    • 自己采用的算法复杂度和优化方法。
    • 如果时间允许,尝试对解法进行优化,找到更高效的解法。
    • 相关数据结构的应用和实现方式。

4. 工具运用

在学习过程中,除了使用豆包MarsCode AI的功能外,合理利用其他学习资源可以大大提高学习效率:

(1) 在线算法教程和书籍

  • 结合在线教程(如《算法导论》、《LeetCode 算法题解》),可以快速理解问题的解法和理论背后的算法原理。
  • 书籍的系统性和深入性有助于打好理论基础,而在线刷题则提供了实践的机会。

(2) 与他人讨论和交流

  • 与同学或学习伙伴共同讨论问题的解法,可以帮助加深对算法的理解。很多时候,别人的思路可能会提供更高效或者更简洁的解决方案。

通过不断刷题、总结和优化,我相信可以在算法的学习和应用上取得更大的进步。