目录
一,3345. 最小可整除数位乘积 I
二,3346. 执行操作后元素的最高频率 I
1.差分数组
2.同向三指针 + 滑动窗口
三, 3348. 最小可整除数位乘积 II
一,3345. 最小可整除数位乘积 I
本题直接暴力枚举,题目求 >=n 的数的数位乘积可以被 t 整除,示例一告诉我们 0 可以被任何数整除,对于任何数,至多加9,它必然会出现一个数位 0,所以它至多只需要枚举9次就可以得到答案。
代码如下:
class Solution {
public int smallestNumber(int n, int t) {
for(int i=n; ; i++){
int a = i, s = 1;
while(a > 0){
s *= a%10;
a /= 10;
}
if(s == 0 || s % t == 0) return i;
}
}
}
二,3346. 执行操作后元素的最高频率 I
本题与 T3 题目一样,这里介绍两种做法:
1.差分数组
对于 nums 数组中的每个元素 x,都能通过1次操作得到 [x-k,x+k] 中的任意数,而本题求最高频率,不就是将每个元素 x 所能得到的 [x-k,x+k] 都 +1,最后看哪个数的频率最大(本题还需注意操作次数的限制),将 [x-k,x+k] 一个范围的数同时加/减,这就是差分数组。
代码如下:
class Solution {
public int maxFrequency(int[] nums, int k, int numOperations) {
TreeMap<Integer, Integer> map = new TreeMap<>();
Map<Integer, Integer> cnt = new HashMap<>();
for(int x : nums){
cnt.merge(x, 1, Integer::sum);
map.merge(x-k, 1, Integer::sum);
//因为出现频率最高的数也可能是它本身
//所以这里需要将x添加到cnt中,否则下面不会遍历到
map.merge(x, 0, Integer::sum);
map.merge(x+k+1, -1, Integer::sum);
}
int pre = 0, ans = 0;
for(int x : map.keySet()){
pre += map.get(x);
ans = Math.max(ans, Math.min(pre, numOperations + cnt.getOrDefault(x, 0)));
}
return ans;
}
}
2.同向三指针 + 滑动窗口
假如出现频率最高的数就是数组 nums 中的一个元素 x,如何计算它的出现频率?也就是计算nums 数组中有几个元素在 [x-k,x+k] 中,可以使用二分求满足条件的左端点L和右端点R,得到答案 R - L + 1,而使用二分的前提是有序,所以需要先排序。
但是要对每个元素都二分还是有点慢,注意这里已经排过序了,所以对于每个元素 x,它对应的范围 [x-k,x+k](对应合法下标 [L,R]) 也是在同步增加的,所以可以使用同向三指针来计算。
但是上述做法还没有计算出现频率最高的数不在 nums 中的情况,可以枚举x = nums[r]作为被修改的最大元素。计算元素值在 [x-2*k,x] 中的元素个数。这样就变成了一个滑窗问题。
代码如下:
class Solution {
public int maxFrequency(int[] nums, int k, int op) {
Arrays.sort(nums);
int n = nums.length;
int ans = 0, l = 0, r = 0, cnt = 0;
//最高频率的元素在nums数组中的情况
for(int i = 0; i < n; i++){
cnt++;
int x = nums[i];
if(i < n - 1 && x == nums[i+1])
continue;
while(l < n && nums[l] < x - k){
l++;
}
while(r < n && nums[r] <= x + k){
r++;
}
ans = Math.max(ans, Math.min(r - l, op + cnt));
cnt = 0;
}
if(ans >= op) return ans;
//[x-2*k, x]
//最高频率的元素不在nums数组中的情况
for(l = 0, r = 0; r < n; r++){
while(nums[l] < nums[r] - 2 * k){
l++;
}
ans = Math.max(ans, Math.min(op, r - l + 1));
}
return ans;
}
}
三, 3348. 最小可整除数位乘积 II
本题直接暴力枚举每一个位数,有点类似于数位DP。先判断各数位之积能否被 t 整除,由于每个数位都是0~9,所以只需要判断 t 能否被 2,3,5,7 整除就行,如果不能被整除,说明 t 一定有一个 > 7 的质数(必然 > 9)所以不可能存在,直接返回 -1。
在dfs暴力枚举中如何判断数的位数之积能被 t 整除,可以直接使用 t / gcd(x,t),最后判断 t == 1就行。
代码如下:
class Solution {
int[] ans;
int cnt = 0;
public String smallestNumber(String s, long t) {
long tmp = t;
for(int x : new int[]{2, 3, 5, 7}){
while(tmp % x == 0){
tmp /= x;
cnt++;
}
}
if(tmp > 1) return "-1";
cnt = Math.max(cnt - s.length() + 1, 1);
s = "0".repeat(cnt) + s;
int n = s.length();
vis = new HashSet[n];
Arrays.setAll(vis, e->new HashSet<>());
ans = new int[n];
dfs(0, t, true, s.toCharArray());
StringBuilder res = new StringBuilder();
for(int x : ans) {
if(x > 0) res.append(x);
}
return res.toString();
}
Set<Long>[] vis;
boolean dfs(int i, long t, boolean islimit, char[] s){
if(i == s.length) return t==1;
if(!islimit && !vis[i].add(t)) return false;
if(islimit && i < cnt && dfs(i+1, t, true, s)) //前导0
return true;
int low = islimit ? s[i] - '0' : 0;
for(int d=Math.max(low, 1); d<10; d++){
ans[i] = d;
if(dfs(i+1, t/gcd(t,d), islimit&&d==low, s))
return true;
}
return false;
}
long gcd(long x, long y){
return y==0?x:gcd(y, x%y);
}
}