นากกายจัดโหนดฮีปบนโต๊ะมิ้นต์ ยอดฮีปเป็นวงอำพันสื่อค่าความถี่สูงสุด

LeetCode #347 Top K Frequent Elements: กองหรือถังนับความถี่

น้องกายEngineering

โจทย์ให้ลิสต์ตัวเลขและจำนวน k แล้วคืนสมาชิกที่โผล่บ่อยสุด k ตัว ตามLeetCode 347 ลำดับในคำตอบมักไม่บังคับ

วิธีที่ 1: Counter + heapq.nlargest — อ่านง่าย

นับความถี่แล้วหยิบ k ค่าที่ความถี่สูงสุดด้วยฮีป เวลาประมาณ O(n log k)

วิธีที่ 2: Bucket ตามความถี่ — เส้นตรงเฉลี่ย

หลังนับความถี่ สร้างถังขนาด n+1 ใส่ค่าตามความถี่ แล้วกวาดจากถังหนาแน่นสุด เวลา O(n) เหมาะเมื่ออยากได้เส้นตรง

วิธีที่ 3: เรียงคู่ (ค่า, ความถี่)

เรียงทั้งตารางความถี่แล้วตัด k ตัวแรก เวลา O(n log n) โค้ดสั้นแต่ไม่จำเป็นเมื่อ n ใหญ่และ k เล็ก

import unittest
from collections import Counter
import heapq

def top_k_heap(nums, k):
    count = Counter(nums)
    return heapq.nlargest(k, count.keys(), key=count.get)

def top_k_bucket(nums, k):
    count = Counter(nums)
    buckets = [[] for _ in range(len(nums) + 1)]
    for num, freq in count.items():
        buckets[freq].append(num)
    ans = []
    for freq in range(len(buckets) - 1, 0, -1):
        for num in buckets[freq]:
            ans.append(num)
            if len(ans) == k:
                return ans
    return ans

def top_k_sort(nums, k):
    count = Counter(nums)
    return [num for num, _ in sorted(count.items(), key=lambda kv: kv[1], reverse=True)[:k]]

class TopKTests(unittest.TestCase):
    def test_examples(self):
        cases = [
            ([1, 1, 1, 2, 2, 3], 2, {1, 2}),
            ([1], 1, {1}),
        ]
        for nums, k, expected in cases:
            for method in (top_k_heap, top_k_bucket, top_k_sort):
                with self.subTest(method=method.__name__, nums=nums, k=k):
                    self.assertEqual(set(method(nums, k)), expected)

if __name__ == "__main__":
    unittest.main()

เลือกตามสถานการณ์

เงื่อนไขheapbucketsort
k << nดีดีเกินจำเป็น
อยาก O(n)ไม่ใช่ใช่ไม่ใช่
โค้ดสั้นสัมภาษณ์ดีมากยาวกว่าสั้น
จำโครงสร้างheapqlist ของ listsorted

ค่าเริ่มต้นที่ผมใช้ในสัมภาษณ์: heap — ถ้าผู้สัมภาษณ์ขอเส้นตรง ค่อยขยับไป bucket

Recap

  • นับความถี่ก่อนเสมอ
  • heap เหมาะ k เล็ก, bucket เหมาะอยาก O(n)
  • อย่าสมมติลำดับคำตอบถ้าโจทย์ไม่ล็อก
  • เทสต์ด้วยเซตเมื่อลำดับไม่สำคัญ