LeetCode 3347. 执行操作后元素的最高频率 II — C 语言实现

题目回顾

给定整数数组 `nums` 和两个整数 `k`、`numOperations`。可以执行最多 `numOperations` 次操作,每次选择一个未被选过的下标 `i`,将 `nums[i]` 增加 `[-k, k]` 范围内的任意整数。求操作后数组中最高频率元素的出现次数。

关键约束(II 与 I 的区别):
- `nums[i]` 最大可达 10⁹(I 中是 10⁵)
- `k` 最大可达 10⁹

因此不能用数组遍历值域,需要使用差分数组(离散化)或排序+双指针来优化。

---

核心思路:差分数组(离散化)

每个元素 `x` 可以变成 `[x-k, x+k]` 范围内的任意值。对于某个目标值 `T`:
- 能变成 `T` 的元素个数 = 满足 `x-k ≤ T ≤ x+k` 的元素个数
- 其中原本就等于 `T` 的元素不需要消耗操作次数
- 实际需要操作次数 = `能变成T的元素个数 - 原本等于T的元素个数`
- 最终频率 = `原本等于T的元素个数 + min(实际需要操作次数, numOperations)`

关键洞察:最优的 `T` 一定出现在某个 `nums[i]-k`、`nums[i]` 或 `nums[i]+k` 处。因此可以用差分数组在离散坐标上标记每个元素的覆盖范围,然后通过前缀和统计每个候选点的覆盖数。

---

C 语言代码实现

```c
#include <stdlib.h>

// 用于 qsort 的比较函数
static int cmp_int(const void *a, const void *b) {
    int x = *(const int *)a;
    int y = *(const int *)b;
    if (x < y) return -1;
    if (x > y) return 1;
    return 0;
}

// 用于 qsort 比较结构体(按坐标排序)
typedef struct {
    long long key;   // 坐标值(用 long long 防止溢出)
    int val;         // 差分值
} DiffEntry;

static int cmp_entry(const void *a, const void *b) {
    DiffEntry *ea = (DiffEntry *)a;
    DiffEntry *eb = (DiffEntry *)b;
    if (ea->key < eb->key) return -1;
    if (ea->key > eb->key) return 1;
    return 0;
}

int maxFrequency(int* nums, int numsSize, int k, int numOperations) {
    // 1. 统计每个数字的原始出现频率
    // 先对 nums 排序,方便统计频率
    int *sorted_nums = (int *)malloc(numsSize * sizeof(int));
    for (int i = 0; i < numsSize; i++) {
        sorted_nums[i] = nums[i];
    }
    qsort(sorted_nums, numsSize, sizeof(int), cmp_int);
    
    // 用数组存储 (值, 频率) 对
    // 最多 numsSize 个不同的值
    long long *freq_keys = (long long *)malloc(numsSize * sizeof(long long));
    int *freq_vals = (int *)malloc(numsSize * sizeof(int));
    int freq_count = 0;
    
    for (int i = 0; i < numsSize; ) {
        int j = i;
        while (j < numsSize && sorted_nums[j] == sorted_nums[i]) {
            j++;
        }
        freq_keys[freq_count] = sorted_nums[i];
        freq_vals[freq_count] = j - i;
        freq_count++;
        i = j;
    }
    
    // 2. 构建差分数组
    // 每个元素产生 3 个关键点:num-k (+1), num (确保存在), num+k+1 (-1)
    // 但 num 可能已经存在,所以最多 3*numsSize 个条目
    // 实际上 num-k 和 num+k+1 可能重复,我们用数组收集后排序合并
    int max_diff_size = numsSize * 3;
    DiffEntry *diff = (DiffEntry *)malloc(max_diff_size * sizeof(DiffEntry));
    int diff_count = 0;
    
    for (int i = 0; i < numsSize; i++) {
        long long num = nums[i];
        long long left = num - (long long)k;
        long long right = num + (long long)k + 1;
        
        // 标记左端点 +1
        diff[diff_count].key = left;
        diff[diff_count].val = 1;
        diff_count++;
        
        // 标记右端点+1处 -1
        diff[diff_count].key = right;
        diff[diff_count].val = -1;
        diff_count++;
    }
    
    // 确保每个原始值在 diff 中存在(值为0)
    for (int i = 0; i < freq_count; i++) {
        diff[diff_count].key = freq_keys[i];
        diff[diff_count].val = 0;
        diff_count++;
    }
    
    // 3. 按坐标排序差分数组,并合并相同坐标的值
    qsort(diff, diff_count, sizeof(DiffEntry), cmp_entry);
    
    // 合并相同 key 的条目
    DiffEntry *merged = (DiffEntry *)malloc(diff_count * sizeof(DiffEntry));
    int merged_count = 0;
    
    for (int i = 0; i < diff_count; ) {
        long long key = diff[i].key;
        int sum = 0;
        while (i < diff_count && diff[i].key == key) {
            sum += diff[i].val;
            i++;
        }
        merged[merged_count].key = key;
        merged[merged_count].val = sum;
        merged_count++;
    }
    
    // 4. 计算前缀和,遍历每个点求最大频率
    int ans = 0;
    int cover = 0; // 当前前缀和
    
    for (int i = 0; i < merged_count; i++) {
        cover += merged[i].val;
        
        long long x = merged[i].key;
        
        // 查找 x 在 freq 中的原始频率
        int original = 0;
        // 二分查找
        int lo = 0, hi = freq_count - 1;
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            if (freq_keys[mid] == x) {
                original = freq_vals[mid];
                break;
            } else if (freq_keys[mid] < x) {
                lo = mid + 1;
            } else {
                hi = mid - 1;
            }
        }
        
        // 计算当前坐标能达到的最大频率
        int possible = cover;
        int limit = original + numOperations;
        int current = possible < limit ? possible : limit;
        
        if (current > ans) {
            ans = current;
        }
    }
    
    // 释放内存
    free(sorted_nums);
    free(freq_keys);
    free(freq_vals);
    free(diff);
    free(merged);
    
    return ans;
}
```

---

代码详解

步骤    说明    
统计频率    先对 `nums` 排序,然后遍历统计每个不同值的频率,存储在 `freq_keys`(值)和 `freq_vals`(频率)中    
差分标记    对每个 `num`,在 `num-k` 处 `+1`(开始覆盖),在 `num+k+1` 处 `-1`(结束覆盖)。同时确保每个原始值在 diff 中存在    
排序合并    将所有差分条目按坐标排序,合并相同坐标的值    
前缀和计算    遍历合并后的差分数组,`cover` 表示当前坐标被多少个元素的覆盖范围包含    
频率计算    `min(cover, original + numOperations)`:覆盖数受限于"原始个数+可操作次数"    
二分查找    用二分查找在 `freq_keys` 中查找当前坐标的原始频率    

---

复杂度分析

- 时间复杂度:`O(n log n)`,主要来自排序操作(排序 nums、排序 diff)
- 空间复杂度:`O(n)`,用于存储频率数组和差分数组

---

示例验证

示例 1:`nums = [1,4,5], k = 1, numOperations = 2`

- 元素 1 覆盖 `[0, 2]`,元素 4 覆盖 `[3, 5]`,元素 5 覆盖 `[4, 6]`
- 差分标记:`{0:+1, 3:-1, 3:+1, 6:-1, 4:+1, 7:-1, 1:0, 4:0, 5:0}`
- 排序合并后:`{0:+1, 1:0, 3:0, 4:+1, 5:0, 6:-1, 7:-1}`
- 遍历:
  - `x=0`: cover=1, original=0 → `min(1, 0+2)=1`
  - `x=1`: cover=1, original=1 → `min(1, 1+2)=1`
  - `x=3`: cover=1, original=0 → `min(1, 0+2)=1`
  - `x=4`: cover=2, original=1 → `min(2, 1+2)=2` ✓
  - `x=5`: cover=2, original=1 → `min(2, 1+2)=2`
  - `x=6`: cover=1, original=0 → `min(1, 0+2)=1`
- 答案 = 2

示例 2:`nums = [5,11,20,20], k = 5, numOperations = 1`

- 元素 5 覆盖 `[0,10]`,11 覆盖 `[6,16]`,20 覆盖 `[15,25]`(两个20)
- 在 `x=20` 处:cover=2(两个20都能覆盖20),original=2 → `min(2, 2+1)=2` ✓
- 答案 = 2

 

更多推荐