image.png
很久以前的了,补一下题。
当时的我好菜啊!!!

1995. 统计特殊四元组

1996. 游戏中弱角色的数量

你正在参加一个多角色游戏,每个角色都有两个主要属性:攻击防御 。给你一个二维整数数组 properties ,其中 properties[i] = [attacki, defensei] 表示游戏中第 i 个角色的属性。
如果存在一个其他角色的攻击和防御等级 都严格高于 该角色的攻击和防御等级,则认为该角色为 弱角色 。更正式地,如果认为角色 i 弱于 存在的另一个角色 j ,那么 attackj > attacki 且 defensej > defensei 。
返回 弱角色 的数量。

示例 1:
输入:properties = [[5,5],[6,3],[3,6]] 输出:0 解释:不存在攻击和防御都严格高于其他角色的角色。
示例 2:
输入:properties = [[2,2],[3,3]] 输出:1 解释:第一个角色是弱角色,因为第二个角色的攻击和防御严格大于该角色。
示例 3:
输入:properties = [[1,5],[10,4],[4,3]] 输出:1 解释:第三个角色是弱角色,因为第二个角色的攻击和防御严格大于该角色。

提示:

  • 2 <= properties.length <= 105
  • properties[i].length == 2
  • 1 <= attacki, defensei <= 105

思路:
方法1:我自己的复杂方法,
排序 + 树状数组
方法2:双关键字排序
问题是如何排?按攻击力从大到小,按防御从小到大。并记录一个当前已经遍历过的所有元素中的最大防御值。

  1. // 方法1
  2. class Solution {
  3. public int numberOfWeakCharacters(int[][] properties) {
  4. Arrays.sort(properties, (o1, o2) -> (o2[0] - o1[0]));
  5. int res = 0;
  6. int n = properties.length;
  7. BIT bit = new BIT();
  8. // System.out.println(Arrays.deepToString(properties));
  9. for (int i = 0; i < n; i++) {
  10. int j = i;
  11. while (j < n && properties[j][0] == properties[i][0]) {
  12. if (bit.getLarge(properties[j][1]) > 0)
  13. res++;
  14. j++;
  15. }
  16. for (int k = i; k < j; k++) {
  17. bit.add(properties[k][1], 1);
  18. }
  19. i = j - 1;
  20. }
  21. return res;
  22. }
  23. }
  24. class BIT {
  25. int[] a = new int[100010];
  26. int n = 100010;
  27. int lowbit(int x) {
  28. return x & (-x);
  29. }
  30. void add(int idx, int x) {
  31. for (int i = idx; i < n; i += lowbit(i))
  32. a[i] += x;
  33. }
  34. int getLarge(int x) {
  35. return get(n - 1) - get(x);
  36. }
  37. int get(int idx) {
  38. int res = 0;
  39. for (int i = idx; i > 0; i -= lowbit(i)) {
  40. res += a[i];
  41. }
  42. return res;
  43. }
  44. }
  1. //方法2
  2. class Solution {
  3. public int numberOfWeakCharacters(int[][] properties) {
  4. Arrays.sort(properties, (o1, o2) -> (o1[0] == o2[0] ? o1[1] - o2[1] : o2[0] - o1[0]));
  5. int max = 0;
  6. int cnt = 0;
  7. for (int[] p : properties) {
  8. if (max > p[1])
  9. cnt++;
  10. else
  11. max = p[1];
  12. }
  13. return cnt;
  14. }
  15. }

1997. 访问完所有房间的第一天

你需要访问 n 个房间,房间从 0 到 n - 1 编号。同时,每一天都有一个日期编号,从 0 开始,依天数递增。你每天都会访问一个房间。
最开始的第 0 天,你访问 0 号房间。给你一个长度为 n 且 下标从 0 开始 的数组 nextVisit 。在接下来的几天中,你访问房间的 次序 将根据下面的 规则 决定:

  • 假设某一天,你访问 i 号房间。
  • 如果算上本次访问,访问 i 号房间的次数为 奇数 ,那么 第二天 需要访问 nextVisit[i] 所指定的房间,其中 0 <= nextVisit[i] <= i 。
  • 如果算上本次访问,访问 i 号房间的次数为 偶数 ,那么 第二天 需要访问 (i + 1) mod n 号房间。

请返回你访问完所有房间的第一天的日期编号。题目数据保证总是存在这样的一天。由于答案可能很大,返回对 109 + 7 取余后的结果。

示例 1:
输入:nextVisit = [0,0] 输出:2 解释: - 第 0 天,你访问房间 0 。访问 0 号房间的总次数为 1 ,次数为奇数。 下一天你需要访问房间的编号是 nextVisit[0] = 0 - 第 1 天,你访问房间 0 。访问 0 号房间的总次数为 2 ,次数为偶数。 下一天你需要访问房间的编号是 (0 + 1) mod 2 = 1 - 第 2 天,你访问房间 1 。这是你第一次完成访问所有房间的那天。
示例 2:
输入:nextVisit = [0,0,2] 输出:6 解释: 你每天访问房间的次序是 [0,0,1,0,0,1,2,…] 。 第 6 天是你访问完所有房间的第一天。
示例 3:
输入:nextVisit = [0,1,2,0] 输出:6 解释: 你每天访问房间的次序是 [0,0,1,1,2,2,3,…] 。 第 6 天是你访问完所有房间的第一天。

提示:

  • n == nextVisit.length
  • 2 <= n <= 105
  • 0 <= nextVisit[i] <= i

思路:
DP问题,暴力不行,得用前缀和做优化

  1. class Solution {
  2. public int firstDayBeenInAllRooms(int[] nextVisit) {
  3. int n = nextVisit.length;
  4. final int MOD = (int)(1e9 + 7);
  5. int[] f = new int[n]; //f[i] 表示当前下标为i,跳转到i + 1所需的天数
  6. f[0] = 2;
  7. int[] pre = new int[n]; //pre[i] 表示从起点跳到i + 1共需的天数
  8. pre[0] = 2;
  9. for (int i = 1; i < n - 1; i++) {
  10. int left = nextVisit[i] > 0 ? pre[nextVisit[i] - 1] : 0;
  11. f[i] = (pre[i - 1] - left + MOD + 2) % MOD;
  12. pre[i] = (pre[i - 1] + f[i]) % MOD;
  13. }
  14. // System.out.println(f[0] + " " + (n - 1));
  15. return pre[n - 2];
  16. }
  17. }

1998. 数组的最大公因数排序

给你一个整数数组 nums ,你可以在 nums 上执行下述操作 任意次

  • 如果 gcd(nums[i], nums[j]) > 1 ,交换 nums[i] 和 nums[j] 的位置。其中 gcd(nums[i], nums[j]) 是 nums[i] 和 nums[j] 的最大公因数。

如果能使用上述交换方式将 nums 按 非递减顺序 排列,返回 true ;否则,返回 false 。

示例 1:
输入:nums = [7,21,3] 输出:true 解释:可以执行下述操作完成对 [7,21,3] 的排序: - 交换 7 和 21 因为 gcd(7,21) = 7 。nums = [21,7,3] - 交换 21 和 3 因为 gcd(21,3) = 3 。nums = [3,7,21]
示例 2:
输入:nums = [5,2,6,2] 输出:false 解释:无法完成排序,因为 5 不能与其他元素交换。
示例 3:
输入:nums = [10,5,9,3,15] 输出:true 解释: 可以执行下述操作完成对 [10,5,9,3,15] 的排序: - 交换 10 和 15 因为 gcd(10,15) = 5 。nums = [15,5,9,3,10] - 交换 15 和 3 因为 gcd(15,3) = 3 。nums = [3,5,9,15,10] - 交换 10 和 15 因为 gcd(10,15) = 5 。nums = [3,5,9,10,15]

提示:

  • 1 <= nums.length <= 3 * 104
  • 2 <= nums[i] <= 105

思路:
并查集 + 数论
关键结论:将所有不互质的数连边,同一连通块内的数一定可以通过交换变成任意顺序(当然也可以变成从小到大)。
故考虑每个数,将其所有公因子放在一个连通块内(1除外)
拷贝一份原数组并排序
遍历数组,比较每个位置上的新旧数组中的数字是否位于同一连通块内,是则继续,否则返回false。

  1. class Solution {
  2. int[] fa = new int[100010];
  3. public boolean gcdSort(int[] nums) {
  4. int n = nums.length;
  5. for (int i = 0; i < fa.length; i++)
  6. fa[i] = i;
  7. for (int x : nums) {
  8. int d = x;
  9. for (int i = 2; i <= d / i; i++) {
  10. boolean flag = false;
  11. while (x % i == 0) {
  12. flag = true;
  13. x /= i;
  14. }
  15. if (flag)
  16. merge(i, d);
  17. }
  18. if (x > 1)
  19. merge(d, x);
  20. }
  21. int[] back = new int[n];
  22. System.arraycopy(nums, 0, back, 0, n);
  23. Arrays.sort(nums);
  24. for (int i = 0; i < n; i++) {
  25. int x = nums[i], y = back[i];
  26. if (find(x) != find(y))
  27. return false;
  28. }
  29. return true;
  30. }
  31. void merge(int a, int b) {
  32. int pa = find(a), pb = find(b);
  33. if (pa != pb)
  34. fa[pa] = pb;
  35. }
  36. int find(int x) {
  37. return x == fa[x] ? x : (fa[x] = find(fa[x]));
  38. }
  39. }

类似于本题的还有:

Acwing 4084. 号码牌 并查集
1202. 交换字符串中的元素 并查集
Acwing 4083. 最大公约数 考虑问题的不同方式
1. 对每个数进行因式分解,时间复杂度O(NlogN),空间复杂度O(N)
2. 计数,考虑每个因子有多少个倍数