← 返回博客列表
演算法優化

陣列數字頻率統計演算法優化 - 原地標記與計數策略 | OAVOService 面試助攻

2026-01-10

問題背景

在資料分析和系統監控中,頻率統計是一個基礎且重要的操作。特別是在資源受限的嵌入式系統或高併發場景下,如何高效地統計給定範圍內數字的出現頻率,直接影響系統的效能表現。

OAVOService 面試助攻團隊 在長期的大廠演算法面試輔導中發現,頻率統計問題經常出現在 Google、Amazon、Microsoft 等公司的程式設計面試中,是考察候選人對時空權衡、陣列操作技巧的經典題目。

問題描述

Problem 3 — Print Frequency of Numbers from 1 to N

給定一個包含 N 個整數的陣列,陣列中只包含 1 到 N 的整數。某些數字可能出現多次,某些數字可能不出現。

編寫程式碼印出 1 到 N 每個數字在陣列中出現的次數。

範例:

PrintFrequency(new int[] {3, 1, 2});
輸出:
1 : 1
2 : 1
3 : 1

PrintFrequency(new int[] {2, 4, 4, 6, 6, 6});
輸出:
1 : 0
2 : 1
3 : 0
4 : 2
5 : 0
6 : 3

核心考察點:

解法分析

解法一:標準計數陣列(推薦)

def print_frequency_standard(arr):
    """
    標準計數陣列解法
    時間複雜度: O(n)
    空間複雜度: O(n)
    """
    if not arr:
        return
    
    n = len(arr)
    # 建立計數陣列,索引0對應數字1
    count = [0] * n
    
    # 統計每個數字的頻率
    for num in arr:
        if 1 <= num <= n:  # 邊界檢查
            count[num - 1] += 1
    
    # 印出結果
    for i in range(n):
        print(f"{i + 1} : {count[i]}")

# 優化版本:支援任意範圍
def print_frequency_range(arr, min_val, max_val):
    """
    支援任意數字範圍的頻率統計
    """
    count = [0] * (max_val - min_val + 1)
    
    for num in arr:
        if min_val <= num <= max_val:
            count[num - min_val] += 1
    
    for i in range(len(count)):
        print(f"{i + min_val} : {count[i]}")

解法二:原地標記法(進階優化)

def print_frequency_inplace(arr):
    """
    原地標記法 - O(1) 額外空間
    時間複雜度: O(n)
    空間複雜度: O(1)
    
    核心思想:使用陣列元素的正負號來標記存取狀態
    """
    if not arr:
        return
    
    n = len(arr)
    
    # 第一步:標記出現的數字
    for i in range(n):
        # 取得目前數字(可能已被標記為負數)
        num = abs(arr[i])
        
        if 1 <= num <= n:
            # 將對應位置標記為負數(表示該數字出現過)
            if arr[num - 1] > 0:
                arr[num - 1] = -arr[num - 1]
    
    # 第二步:統計頻率
    # 重新遍歷,計算每個數字的出現次數
    frequency = [0] * n
    for num in arr:
        abs_num = abs(num)
        if 1 <= abs_num <= n:
            frequency[abs_num - 1] += 1
    
    # 第三步:恢復原陣列(可選)
    for i in range(n):
        arr[i] = abs(arr[i])
    
    # 印出結果
    for i in range(n):
        print(f"{i + 1} : {frequency[i]}")

解法三:雜湊表法(通用)

from collections import defaultdict, Counter

def print_frequency_hash(arr):
    """
    雜湊表解法 - 適用於任意數字範圍
    時間複雜度: O(n)
    空間複雜度: O(k),k為不同數字的個數
    """
    if not arr:
        return
    
    # 方法1:使用Counter
    frequency = Counter(arr)
    n = len(arr)
    
    for i in range(1, n + 1):
        print(f"{i} : {frequency.get(i, 0)}")
    
    # 方法2:手動計數
    # count_map = defaultdict(int)
    # for num in arr:
    #     count_map[num] += 1
    
    # for i in range(1, n + 1):
    #     print(f"{i} : {count_map[i]}")

進階優化策略

1. 分塊處理大陣列

def print_frequency_chunked(arr, chunk_size=1000):
    """
    分塊處理大陣列,減少記憶體峰值
    適用於記憶體受限場景
    """
    if not arr:
        return
    
    n = len(arr)
    total_count = [0] * n
    
    # 分塊處理
    for start in range(0, len(arr), chunk_size):
        end = min(start + chunk_size, len(arr))
        chunk = arr[start:end]
        
        # 處理目前區塊
        chunk_count = [0] * n
        for num in chunk:
            if 1 <= num <= n:
                chunk_count[num - 1] += 1
        
        # 累加到總計數
        for i in range(n):
            total_count[i] += chunk_count[i]
    
    # 印出結果
    for i in range(n):
        print(f"{i + 1} : {total_count[i]}")

2. 並行處理優化

from concurrent.futures import ThreadPoolExecutor
import threading

def print_frequency_parallel(arr, num_threads=4):
    """
    多執行緒並行處理
    適用於大陣列的頻率統計
    """
    if not arr:
        return
    
    n = len(arr)
    chunk_size = len(arr) // num_threads
    results = [None] * num_threads
    lock = threading.Lock()
    
    def process_chunk(thread_id, start_idx, end_idx):
        chunk_count = [0] * n
        for i in range(start_idx, end_idx):
            num = arr[i]
            if 1 <= num <= n:
                chunk_count[num - 1] += 1
        
        with lock:
            results[thread_id] = chunk_count
    
    # 啟動執行緒
    with ThreadPoolExecutor(max_workers=num_threads) as executor:
        futures = []
        for i in range(num_threads):
            start = i * chunk_size
            end = len(arr) if i == num_threads - 1 else (i + 1) * chunk_size
            future = executor.submit(process_chunk, i, start, end)
            futures.append(future)
        
        # 等待所有執行緒完成
        for future in futures:
            future.result()
    
    # 合併結果
    total_count = [0] * n
    for chunk_result in results:
        if chunk_result:
            for i in range(n):
                total_count[i] += chunk_result[i]
    
    # 印出結果
    for i in range(n):
        print(f"{i + 1} : {total_count[i]}")

3. 串流處理大檔案

def print_frequency_streaming(file_path):
    """
    串流處理大檔案中的數字頻率
    適用於無法一次性載入到記憶體的大資料集
    """
    frequency = {}
    max_num = 0
    
    with open(file_path, 'r') as file:
        for line in file:
            numbers = map(int, line.strip().split())
            for num in numbers:
                frequency[num] = frequency.get(num, 0) + 1
                max_num = max(max_num, num)
    
    # 印出結果
    for i in range(1, max_num + 1):
        print(f"{i} : {frequency.get(i, 0)}")

效能基準測試

import time
import random

def benchmark_frequency_methods():
    """
    效能基準測試
    """
    # 產生測試資料
    test_sizes = [1000, 10000, 100000]
    
    for size in test_sizes:
        arr = [random.randint(1, size) for _ in range(size)]
        
        print(f"\n測試陣列大小: {size}")
        
        # 測試標準計數法
        start_time = time.time()
        print_frequency_standard(arr.copy())
        standard_time = time.time() - start_time
        
        # 測試原地標記法
        start_time = time.time()
        print_frequency_inplace(arr.copy())
        inplace_time = time.time() - start_time
        
        # 測試雜湊表法
        start_time = time.time()
        print_frequency_hash(arr.copy())
        hash_time = time.time() - start_time
        
        print(f"標準計數法: {standard_time:.4f}s")
        print(f"原地標記法: {inplace_time:.4f}s")
        print(f"雜湊表法: {hash_time:.4f}s")

OAVOService 面試技巧

常見面試變形

  1. 限制空間複雜度 - 要求 O(1) 空間解法
  2. 處理負數/零 - 擴展到任意整數範圍
  3. Top-K頻率 - 只輸出出現次數最多的K個數字
  4. 即時更新 - 支援動態增刪數字

面試討論要點

複雜度對比

方法 時間複雜度 空間複雜度 適用場景
標準計數 O(n) O(n) 數字範圍已知且不大
原地標記 O(n) O(1) 空間受限,數字範圍小
雜湊表 O(n) O(k) 數字範圍大,稀疏分佈
分塊處理 O(n) O(n/塊數) 記憶體受限的大陣列

OAVOService 專業面試助攻服務 - 我們深度解析各類演算法面試題目,為學員提供 Google、Amazon、Microsoft、Meta 等頂級公司的即時面試輔助。憑藉豐富的面試經驗和演算法優化技巧,助你在程式設計面試中脫穎而出。

如果你正在準備演算法面試,歡迎了解我們的 客製化演算法面試訓練方案 —— 從基礎資料結構到進階優化技巧,全面提升你的程式設計面試競爭力。

標籤: 頻率統計, 陣列演算法, 空間優化, 原地操作, 演算法面試, OAVOService