文章目录
- 2183. 统计可以被 K 整除的下标对数目⭐⭐⭐⭐⭐
- 思路——数论(一个数乘上另一个数x是k的倍数,x最小是多少?)
- 代码1——统计每个数的因子
- 代码2——统计k的因子
- 2245. 转角路径的乘积中最多能有几个尾随零
- 思路(因子2和5的个数 + 前缀和)⭐⭐⭐⭐⭐
- 代码
- 2281. 巫师的总力量和⭐⭐⭐⭐⭐
- 思路——贡献法(单调栈求左右端点) + 前缀和的前缀和
- 代码
- 2310. 个位数字为 K 的整数之和
- 解法——枚举集合的大小
- 代码1——自己写的
- 代码2——力扣官解
https://leetcode.cn/circle/discuss/G0n5iY/
2183. 统计可以被 K 整除的下标对数目⭐⭐⭐⭐⭐
2183. 统计可以被 K 整除的下标对数目
提示:
1 <= nums.length <= 10^5
1 <= nums[i], k <= 10^5
思路——数论(一个数乘上另一个数x是k的倍数,x最小是多少?)
对于一个固定的数字 nums[j] ,要想和 nums[i] 配对,那么 nums[i] 必须是某个数字 x 的倍数。
那么 x 最小是多少呢? (为了找到所有符合条件的 nums[i] ,我们需要找到最小的 x)
从数论的结论来看, x = k g c d ( n u m s [ j ] , k ) x = \frac{k}{gcd(nums[j], k)} x=gcd(nums[j],k)k,这里 k 就是题目中的 k。
为什么呢?可以从因子的角度去考虑,如果 nums[j] 和 k 有一些公因子,那么可以从 k 中除去这些公因子,这样 x 会变小,那么除去最大公因子是最优的。
代码1——统计每个数的因子
我们可以不断枚举 nums[j],在这个过程中记录前面枚举过的 x 的倍数有多少个记为 cnt[x],那么枚举到 nums[j] 的时候就给答案加上多少。
class Solution {final static int mx = 100001;// 记录每个数字的所有因子static List<List<Integer>> divisors = new ArrayList(mx);static {for (int i = 0; i < mx; ++i) divisors.add(new ArrayList());for (int i = 1; i < mx; ++i) {for (int j = i; j < mx; j += i) {// j是i的倍数,所以把i放进j的因子列表里divisors.get(j).add(i);}}}public long countPairs(int[] nums, int k) {long ans = 0;Map<Integer, Integer> cnt = new HashMap();for (int num: nums) {ans += cnt.getOrDefault(k / gcd(num, k), 0);for (int d: divisors.get(num)) {cnt.merge(d, 1, Integer::sum);}}return ans;}public static int gcd(int a, int b) {return b == 0? a: gcd(b, a % b);}
}
代码2——统计k的因子
注意到 x 是 k 的因子,因此可以将代码 1 中统计 num 的因子改为 统计 num 是 k 的哪些因子的倍数,这可以通过 枚举 k 的所有因子 来判断。
class Solution {public long countPairs(int[] nums, int k) {// 统计k的因子List<Integer> divisors = new ArrayList();for (int d = 1; d * d <= k; ++d) {if (k % d == 0) {divisors.add(d);if (d * d < k) divisors.add(k / d);}}long ans = 0;Map<Integer, Integer> cnt = new HashMap();for (int num: nums) {ans += cnt.getOrDefault(k / gcd(num, k), 0);for (int d: divisors) {if (num % d == 0) cnt.merge(d, 1, Integer::sum);}}return ans;}public static int gcd(int a, int b) {return b == 0? a: gcd(b, a % b);}
}
2245. 转角路径的乘积中最多能有几个尾随零
2245. 转角路径的乘积中最多能有几个尾随零
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 10^5
1 <= m * n <= 10^5
1 <= grid[i][j] <= 1000
思路(因子2和5的个数 + 前缀和)⭐⭐⭐⭐⭐
计算出每个数字含有多少个 2 和 多少个 5,最后尾随 0 的个数就是 2 和 5 的数量的最小值。
代码
class Solution {static int[][] c25 = new int[1001][2];static {// 预处理,递推出每个数的因子2和因子5的个数for (int i = 2; i <= 1000; ++i) {if (i % 2 == 0) c25[i][0] = c25[i / 2][0] + 1;if (i % 5 == 0) c25[i][1] = c25[i / 5][1] + 1;}}public int maxTrailingZeros(int[][] grid) {int m = grid.length, n = grid[0].length, ans = 0;int[][][] s = new int[m][n + 1][2];for (int i = 0; i < m; ++i) {for (int j = 0; j < n; ++j) {// 每行的因子2或5的前缀和数量s[i][j + 1][0] = s[i][j][0] + c25[grid[i][j]][0];s[i][j + 1][1] = s[i][j][1] + c25[grid[i][j]][1];}}for (int j = 0; j < n; ++j) { // 枚举每一列// 从上往下,枚举左拐还是右拐for (int i = 0, s2 = 0, s5 = 0; i < m; ++i) {s2 += c25[grid[i][j]][0];s5 += c25[grid[i][j]][1];int left = Math.min(s2 + s[i][j][0], s5 + s[i][j][1]);int right = Math.min(s2 + s[i][n][0] - s[i][j + 1][0], s5 + s[i][n][1] - s[i][j + 1][1]);ans = Math.max(ans, Math.max(left, right));}// 从下往上,枚举左拐还是右拐for (int i = m - 1, s2 = 0, s5 = 0; i >= 0; --i) {s2 += c25[grid[i][j]][0];s5 += c25[grid[i][j]][1];int left = Math.min(s2 + s[i][j][0], s5 + s[i][j][1]);int right = Math.min(s2 + s[i][n][0] - s[i][j + 1][0], s5 + s[i][n][1] - s[i][j + 1][1]);ans = Math.max(ans, Math.max(left, right));}}return ans;}
}
注意学习 递推出每个数的因子2和因子5的个数 的方法。
即:
static int[][] c25 = new int[1001][2];
static {// 预处理,递推出每个数的因子2和因子5的个数for (int i = 2; i <= 1000; ++i) {if (i % 2 == 0) c25[i][0] = c25[i / 2][0] + 1;if (i % 5 == 0) c25[i][1] = c25[i / 5][1] + 1;}
}
2281. 巫师的总力量和⭐⭐⭐⭐⭐
2281. 巫师的总力量和
提示:
1 <= strength.length <= 105
1 <= strength[i] <= 109
思路——贡献法(单调栈求左右端点) + 前缀和的前缀和
笔者认为本题的主要难点在于 前缀和的前缀和求法。
关于贡献法可见:【算法】贡献法相关题目练习
关于前缀和的前缀和的计算可见:https://leetcode.cn/problems/sum-of-total-strength-of-wizards/solutions/1510399/dan-diao-zhan-qian-zhui-he-de-qian-zhui-d9nki/ 或 下图计算过程。
注意这里说的所有子数组指的是所有包括元素 strength[i] 的子数组。
代码
class Solution {public int totalStrength(int[] strength) {final int mod = (int)1e9 + 7;int n = strength.length;int[] s = new int[n + 1], ss = new int[n + 2];// 前缀和的前缀和for (int i = 0; i < n; ++i) {s[i + 1] = (s[i] + strength[i]) % mod; ss[i + 2] = (ss[i + 1] + s[i + 1]) % mod;}int[] left = new int[n], right = new int[n];Arrays.fill(left, -1);Arrays.fill(right, n);Deque<Integer> stk = new ArrayDeque();for (int i = 0; i < n; ++i) {while (!stk.isEmpty() && strength[i] <= strength[stk.peek()]) right[stk.pop()] = i;if (!stk.isEmpty()) left[i] = stk.peek();stk.push(i);}long ans = 0;for (int i = 0; i < n; ++i) {int l = left[i] + 1, r = right[i] - 1;// 前缀和的前缀和推出的公式long tot = ((long)(i - l + 1) * (ss[r + 2] - ss[i + 1]) - (long)(r - i + 1) * (ss[i + 1] - ss[l])) % mod;ans = (ans + tot * strength[i]) % mod;}return (int)(ans + mod) % mod;}
}
2310. 个位数字为 K 的整数之和
2310. 个位数字为 K 的整数之和
提示:
0 <= num <= 3000
0 <= k <= 9
解法——枚举集合的大小
我们可以知道集合的大小不会超过 10,因为 11 个 个位是 k 的数字相乘,最后的个位数字还是 k ,没有影响。
代码1——自己写的
class Solution {public int minimumNumbers(int num, int k) {if (num == 0) return 0;int n = num % 10, ans = 1;while (ans < 11 && (ans * k % 10 != n)) ++ans;return ans <= 10 && ans * k <= num? ans: -1;}
}
自己写的代码丑陋了一下,因为循环写的不好所以需要对结果加一些额外的判断。
代码2——力扣官解
class Solution {public int minimumNumbers(int num, int k) {if (num == 0) return 0;for (int i = 1; i <= 10; ++i) {if (k * i <= num && (num - k * i) % 10 == 0) return i;}return -1;}
}
官解的答案优雅很多,依次判断 1 ~ 10 是否满足答案,满足就返回,不满足就最后返回 -1。