当前位置: 首页 > news >正文

执行操作后元素的最高频率1 2(LeetCode 3346 3347)

1 介绍

本文会介绍执行操作后元素的最高频率1 & 2的思路以及解法,使用的代码包括C++JavaGo

2 执行操作后元素的最高频率1

2.1 原题

2.2 思路

由于排序不影响结果,并且排序能更好的符合题目的“增加范围”特性,因此先排序处理。

题目本质上是找一个数xx可以在nums中也可以不在nums中,求[x-k,x+k]的最大长度。

虽然x可以在nums中,也可以不在nums中,但是x的边界是确定的,最小值是min(nums),最大值是max(nums)。因为假如能调整出了nums的边界,为什么不直接取边界值?比如nums=[2,4],k=3,numsOperations=2,如果调整到[5,5],为什么不调整到[4,4]

换句话说,x可以取到nums中最小值到最大值之间的值。

所以,思路就是:

  • 排序
  • 遍历[min(nums),max(nums)]中的每个数x
  • 判断nums有多少个数能调整得到x

由于调整的范围是[-k,+k],可以使用二分去处理。

C++中,二分可以使用lower_bound()以及upper_bound(),遍历范围中每个数x,接着:

  • ranges::lower_bound(nums,x-k):找到nums中大于等于x-k的位置
  • ranges::upper_bound(nums,x+k):找到nums中大于x+k的位置

两者相减,就得到了“有多少个数能调整得到x的数量”。

剩下还有一个问题是如何处理numsOperations,因为处理的次数是有限制的,所以是两者取最小值:

int res = 0;
for (int x = nums.front(); x <= nums.back(); ++x) {auto l = ranges::lower_bound(nums, x - k);auto r = ranges::upper_bound(nums, x + k);// 计算结果,两者取最小值res=max(res,min(numOperations, static_cast<int>(r - l)));
}

对吗?

并不对,忽略了一个非常重要的东西,就是nums[i]自己。

x变成x本身是不需要消耗numsOperations的(严格意义上来说是不需要变),因此处理r-l的时候,需要减去这部分。同时,取min之后需要把这部分加回来,因为min本质上就是求能调整得到x的数量,但是没算上x本身。

完整代码如下:

class Solution {
public:int maxFrequency(vector<int> &nums, int k, int numOperations) {// 排序ranges::sort(nums);// 用于计数unordered_map<int, int> m;for (int x: nums) {++m[x];}// 存储结果int res = 0;// 遍历[min(nums),max(nums)]范围内的所有数,因为排序了,所以就是遍历[nums.front(),nums.back()]for (int x = nums.front(); x <= nums.back(); ++x) {// 找到大于等于x-k的位置auto l = ranges::lower_bound(nums, x - k);// 找到大于x+k的位置auto r = ranges::upper_bound(nums, x + k);// r-l就是相等于[-k,+k]的长度// 由于是迭代器,并且写在一起了,手动转换一下int// 然后减去x本身的数量,再与numOperations取最小值,表示能有多少个数能通过调整得到x// 最后累加上x本身的数量,与res比较取最大值res = max(res, min(numOperations, static_cast<int>(r - l) - m[x]) + m[x]);}return res;}
};

如果没有lower_bound()upper_bound()怎么处理?

手动写个二分就行了,参考下面的Java代码。

2.3 Java版本

由于upper_bound(x+k)等价于lower_bound(x+k+1),所以只需要选择实现upper_bound或者lower_bound即可,这里实现的是lower_bound

import java.util.*;public class Solution {private int lowerBound(int[] nums, int target) {int l = 0;int r = nums.length - 1;while (l <= r) {int m = (l + r) >> 1;// 如果小于target,l增大,保证nums[l]>=targetif (nums[m] < target) {l = m + 1;} else {r = m - 1;}}return l;}public int maxFrequency(int[] nums, int k, int numOperations) {Arrays.sort(nums);Map<Integer, Integer> map = new HashMap<>();for (int x : nums) {map.merge(x, 1, Integer::sum);}int res = 0;for (int max = nums[nums.length - 1], x = nums[0]; x <= max; x++) {int l = lowerBound(nums, x - k);// upper_bound(x+k)等价于lower_bound(x+k+1)int r = lowerBound(nums, x + k + 1);int xCount = map.getOrDefault(x, 0);res = Math.max(res, Math.min(numOperations, r - l - xCount) + xCount);}return res;}
}

2.4 Go版本

go有标准库sort.Search()可以使用,用来模拟lower_bound()upper_bound(),参考代码用法。

func maxFrequency(nums []int, k int, numOperations int) int {slices.Sort(nums)res, m := 0, make(map[int]int)for _, x := range nums {m[x]++}for x := nums[0]; x <= nums[len(nums)-1]; x++ {l, r := sort.Search(len(nums), func(i int) bool {return nums[i] >= x-k}), sort.Search(len(nums), func(i int) bool {return nums[i] > x+k})res = max(res, min(numOperations, r-l-m[x])+m[x])}return res
}

3 执行操作后元素的最高频率2

3.1 原题

3.2 思路

第二题的题目描述和第一题是一致的,不同的只是范围:

knums的范围从1e5变成了1e9

一种简单的方法是,使用第一题的思路,不过不是遍历nums范围中的所有数,而是遍历nums中的每一个数以及加上-k+k

代码如下:

class Solution {
public:int maxFrequency(vector<int> &nums, int k, int numOperations) {int res = 0;unordered_map<int, int> m;// 排序,后面需要二分ranges::sort(nums);// 计数for (int x: nums) {++m[x];}// 这个的逻辑和第一题的一致,只不过是写成了单独函数的形式auto get_result = [&](int x) -> void {// 注意这里需要加一下转换,不然会爆intconst auto l = ranges::lower_bound(nums, static_cast<long>(x) - k);const auto r = ranges::upper_bound(nums, static_cast<long>(x) + k);res = max(res, m[x] + min(static_cast<int>(r - l) - m[x], numOperations));};// 主要修改是这个地方// 从遍历[min(nums),max(nums)]到遍历nums中的每一个数x,以及x-k和x+kfor (int x: nums) {get_result(x);get_result(x + k);get_result(x - k);}return res;}
};

这个思路本质上也是和第一题一样是枚举,只不过枚举的范围,从[min(nums),max(nums)]变成了nums中的每一个数x,以及x-kx+k

为什么这个思路可以呢?

考虑任意一个x,目标就是最大化落在[x-k,x+k]的元素个数,当移动x时,覆盖的区间滑动。覆盖的元素个数只会在区间边界(也就是x-kx+k)碰到某个nums[i]时才会发生变化,所以最大值一定出现在下面三者之一:

  • x本身
  • x-k,左区间端点
  • x+k,右区间端点

这个算法的时间复杂度是O(n log n),耗时如下:

一个非常简单高效的小优化是,由于遍历顺序的问题,x-k会被之前的某个x遍历过,因此不需要再次遍历x-k,也就是优化如下:

for (int x: nums) {get_result(x);get_result(x + k);// 不需要x-k了,已经遍历过// get_result(x - k);
}

耗时如下,优化比较明显,短了差不多400ms:

3.3 差分

另一个思路是差分,差分的思想是,直接计算出[x-k,x+k]之间的数量。

每当遍历到nums中的一个x的时候:

  • 累加差分数组中的x-k,表示从x-k开始,就多了一个数
  • 累减差分数组中的x+k+1,表示直到x+k(包含),x-k的累加效果就没有了

由于x的取值范围是1e9,不能使用常规数组,在C++中,需要使用map

处理差分之后,遍历差分数组中的每个数对,累加当前的值作为sumsum就是[x-k,x)的数量(不需要计算(x,x+k],由于遍历顺序的问题后面会计算),然后与numOperations比较得到结果。

class Solution {
public:int maxFrequency(vector<int> &nums, int k, int numOperations) {unordered_map<int, int> m;map<int, int> diff;for (int x: nums) {// 如果没有包含,设置为0if (!diff.contains(x)) {diff[x] = 0;}// ++[x-k],表示从x-k开始所有的数值都增加1++diff[x - k];// --[x+k+1],表示到前面++[x-k]的效果到x+k为止,后面就不会再加--diff[x + k + 1];++m[x];}int res = 0;int sum = 0;// 遍历diff中的每对数for (auto &[x,d]: diff) {// sum表示对于当前的x,[x-k,x)的数量// 不需要计算(x,x+k],因为后面会遍历到sum += d;// res的计算逻辑和上面一致res = max(res, min(numOperations, sum-m[x])+m[x]);}return res;}
};

时间消耗:

这个做法虽然没有排序,但是实际上map里面会有排序,时间复杂度是O(n log n)

3.4 同向三指针

这个思路来自灵神,详见出处。

这个方法思路是:

  • 如果答案xnums中,使用同向三指针处理
  • 如果答案x不在nums中,使用滑动窗口处理

如果xnums中,使用两个指针lr,分别指向:

  • 大于等于x-k的位置
  • 大于x+k的位置

这样,直接进行r-l就能得到[x-k,x+k]的长度,将该长度和numOperations比较,得到结果。

如果x不在nums中,使用滑动窗口计算[x,x+2*k]的长度,然后计算得到结果。

详见代码:

class Solution {
public:int maxFrequency(vector<int> &nums, int k, int numOperations) {// 先排序ranges::sort(nums);// 存储结果int res = 0;int n = static_cast<int>(nums.size());// 计数unordered_map<int, int> m;for (int x: nums) {++m[x];}// 同向三指针,i,l和rfor (int i = 0, l = 0, r = 0; i < n; ++i) {// l指向第一个>=x-k的位置while (l < n && nums[l] < nums[i] - k) {++l;}// r指向第一个>x+k的位置while (r < n && nums[r] <= nums[i] + k) {++r;}// 这个计算逻辑和前面一致,不再解释res = max(res, min(numOperations, r - l - m[nums[i]]) + m[nums[i]]);}// 滑动窗口for (int i = 0, r = 0; i < n; ++i) {// 得到当前的[x,x+2*k]的长度while (r < n && nums[r] - nums[i] <= 2 * k) {++r;}// r-i就是长度,和numOperations作比较,取minres = max(res, min(numOperations, r - i));}return res;}
};

耗时如下:

这段代码还能再优化一下,第一个优化是,后面的滑窗在某些条件下不需要计算。

这个条件就是res>=numOperations,因为滑窗计算的结果最大就是numOperations

第二个优化是,计数的时候可以不需要一个额外的unordered_map<int,int>,因为已经排序了,并且计数只有在三指针的时候使用,在后面的滑窗没有使用,可以在遍历的时候累加计数。

优化后的代码如下:

class Solution {
public:int maxFrequency(vector<int> &nums, int k, int numOperations) {// 排序ranges::sort(nums);int res = 0;const int n = static_cast<int>(nums.size());// 三指针for (int i = 0, l = 0, r = 0; i < n;) {// 指向>=x-kwhile (l < n && nums[l] < nums[i] - k) {++l;}// 指向>x+kwhile (r < n && nums[r] <= nums[i] + k) {++r;}// 跳过相同的xint j = i + 1;for (; j < n && nums[j] == nums[i]; ++j) {}// 直接计算cnt,不需要通过unordered_map<int,int>计算const int cnt = j - i;res = max(res, min(numOperations, r - l - cnt) + cnt);// 跳过相同的xi = j;}// 如果res>=numOperations,直接返回// 因为滑窗计算的结果最大就是numOperationsif (res >= numOperations) {return res;}for (int i = 0, r = 0; i < n; ++i) {while (r < n && nums[r] - nums[i] <= 2 * k) {++r;}res = max(res, min(numOperations, r - i));}return res;}
};

时间消耗如下:

可以看到,虽然都是O(n log n)的时间复杂度,但是这个时间消耗最短。

3.5 Java版本

3.5.1 方法1

import java.util.*;public class Solution {private int numOperations;private Map<Integer, Integer> map;private int[] nums;private int k;private int res = 0;private int lowerBound(int target) {int l = 0;int r = nums.length - 1;while (l <= r) {int m = (l + r) >> 1;if (nums[m] < target) {l = m + 1;} else {r = m - 1;}}return l;}private void getResult(int x) {int l = lowerBound(x - k);int r = lowerBound(x + k + 1);int xCount = map.getOrDefault(x, 0);res = Math.max(res, Math.min(numOperations, r - l - xCount) + xCount);}public int maxFrequency(int[] nums, int k, int numOperations) {Arrays.sort(nums);this.numOperations = numOperations;this.map = new HashMap<>();this.nums = nums;this.k = k;for (int x : nums) {map.merge(x, 1, Integer::sum);}for (int x : nums) {getResult(x);getResult(x - k);getResult(x + k);}return res;}
}

3.5.2 方法2

import java.util.*;public class Solution {public int maxFrequency(int[] nums, int k, int numOperations) {Map<Integer, Integer> diff = new TreeMap<>();Map<Integer, Integer> map = new HashMap<>();for (int x : nums) {diff.putIfAbsent(x, 0);diff.merge(x - k, 1, Integer::sum);diff.merge(x + k + 1, -1, Integer::sum);map.merge(x, 1, Integer::sum);}int res = 0;int sum = 0;for (Map.Entry<Integer, Integer> entry : diff.entrySet()) {int key = entry.getKey();int value = entry.getValue();int xCount = map.getOrDefault(key, 0);sum += value;res = Math.max(res, Math.min(numOperations, sum - xCount) + xCount);}return res;}
}

3.5.3 方法3

import java.util.*;public class Solution {public int maxFrequency(int[] nums, int k, int numOperations) {Arrays.sort(nums);int n = nums.length;int res = 0;for (int i = 0, l = 0, r = 0; i < n; ) {while (l < n && nums[l] < nums[i] - k) {++l;}while (r < n && nums[r] <= nums[i] + k) {++r;}int j = i + 1;while (j < n && nums[j] == nums[i]) {++j;}int xCount = j - i;res = Math.max(res, Math.min(numOperations, r - l - xCount) + xCount);i = j;}if (res >= numOperations) {return res;}for (int i = 0, r = 0; i < n; i++) {while (r < n && nums[r] - nums[i] <= 2 * k) {++r;}res = Math.max(res, Math.min(numOperations, r - i));}return res;}
}

3.6 Go版本

3.6.1 方法1

func maxFrequency(nums []int, k int, numOperations int) int {slices.Sort(nums)res, m := 0, make(map[int]int)for _, x := range nums {m[x]++}getResult := func(x int) {l, r := sort.Search(len(nums), func(i int) bool {return nums[i] >= x-k}), sort.Search(len(nums), func(i int) bool {return nums[i] > x+k})res = max(res, min(numOperations, r-l-m[x])+m[x])}for _, x := range nums {getResult(x)getResult(x + k)getResult(x - k)}return res
}

3.6.2 方法2

由于Go没有内置的类似C++map,这里使用的是无序的,然后统计完差分之后手动排序。

func maxFrequency(nums []int, k int, numOperations int) int {slices.Sort(nums)res, m, diff, sum := 0, make(map[int]int), make(map[int]int), 0for _, x := range nums {m[x]++_, contains := diff[x]if !contains {diff[x] = 0}diff[x-k]++diff[x+k+1]--}var sortedKeys []intfor keys := range diff {sortedKeys = append(sortedKeys, keys)}slices.Sort(sortedKeys)for _, x := range sortedKeys {sum += diff[x]res = max(res, min(numOperations, sum-m[x])+m[x])}return res
}

3.6.3 方法3

func maxFrequency(nums []int, k int, numOperations int) int {slices.Sort(nums)res, n := 0, len(nums)for i, l, r := 0, 0, 0; i < n; {for l < n && nums[l] < nums[i]-k {l++}for r < n && nums[r] <= nums[i]+k {r++}j := i + 1for j < n && nums[j] == nums[i] {j++}cnt := j - ires, i = max(res, min(numOperations, r-l-cnt)+cnt), j}if res >= numOperations {return res}for i, r := 0, 0; i < n; i++ {for r < n && nums[r] <= nums[i]+2*k {r++}res = max(res, min(numOperations, r-i))}return res
}

4 总结

本文主要介绍了三种方法:

  • 枚举+排序+二分
  • 差分
  • 同向三指针+滑窗

三种方法本质上都是基于排序的,但是找到[x-k,x+k]长度的方式各不相同,其中第三种方法由于实现方式简单,容易优化,耗时最短。

5 附录

  • 执行操作后元素的最高频率1
  • 执行操作后元素的最高频率2
  • 灵神题解-同向三指针
http://www.dtcms.com/a/511649.html

相关文章:

  • Java 大视界 -- Java 大数据在智慧交通停车场智能管理与车位预测中的应用实践
  • 版本设计网站100个关键词
  • 网站前置审批工程建设服务平台
  • 共聚焦显微镜(LSCM)的针孔效应
  • STM32CubeMX
  • 网站实现搜索功能四川建设安全协会网站
  • spark组件-spark core(批处理)-rdd特性-内存计算
  • 算法练习:双指针专题
  • 关于comfyui的triton安装(xformers的需求)
  • 爬虫+Redis:如何实现分布式去重与任务队列?
  • 烘焙食品网站建设需求分析wordpress生成静态地图
  • 区块链——Solidity编程
  • OpenSSH安全升级全指南:从编译安装到中文显示异常完美解决
  • 数据结构的演化:从线性存储到语义关联的未来
  • 爱博精电AcuSys 电力监控系统赋能山东有研艾斯,铸就12英寸大硅片智能配电新标杆
  • 基于AI与云计算的PDF操作工具开发技术探索
  • LeetCode 404:左叶子之和(Sum of Left Leaves)
  • 中小企业网站建设论文高端制作网站技术
  • 电子报 网站开发平面设计培训机构排行
  • 无人系统搭载毫米波雷达的距离测算与策略执行详解
  • Adobe Acrobat软件优化配置,启用字体平滑和默认单页连续滚动
  • 测试题-3
  • win10 win11搜索框空白解决方案
  • Linux系统:多线程编程中的数据不一致问题与线程互斥理论
  • 遇到oom怎么处理?
  • jenkins流水线项目部署
  • 网口学习理解
  • 企业网站 阿里云招聘网站开发
  • 证书兼职的人才网站高明网站设计
  • 用c语言写一个nes游戏模拟器