每日算法-250527
每日算法 - 250527
2364. 统计坏数对的数目
题目

思路与解题过程
核心思想:转换问题 + 哈希表计数
题目定义“坏数对” (i, j) 为满足 i < j 且 j - i != nums[j] - nums[i] 的数对。
 直接统计“坏数对”可能比较复杂,我们可以考虑其补集——“好数对”。
 “好数对” (i, j) 满足 i < j 且 j - i == nums[j] - nums[i].
对“好数对”的条件进行变形:
 j - i == nums[j] - nums[i]
 nums[i] - i == nums[j] - j
令 d[x] = nums[x] - x。那么,“好数对”的条件就变成了 i < j 且 d[i] == d[j].
解法:直接统计坏数对
虽然可以通过“总数对 - 好数对”来计算,但我们也可以直接统计“坏数对”。
 遍历数组 nums,对于每个元素 nums[i](及其下标 i):
- 计算 key = nums[i] - i.
- 我们想知道,在 nums[i]之前(即下标p < i),有多少个元素nums[p]满足nums[p] - p == key。这些(p, i)会构成“好数对”。
- 设 map存储了之前遇到的nums[x] - x的值及其出现的次数。val = map.getOrDefault(key, 0)就表示在nums[i]之前,有多少个下标p使得nums[p] - p == key.
- 对于当前的 nums[i], 总共有i个可能的配对下标p(从0到i-1)。
- 其中,有 val个下标p与当前i构成了“好数对”。
- 因此,与当前 i构成的“坏数对”的数目就是i - val。我们将这个数目累加到总结果ret中。
- 然后,更新 map:将key(即nums[i] - i) 的计数加 1,map.put(key, val + 1). 这为后续的元素nums[j](其中j > i) 提供了信息。
这种方法在遍历到 nums[i] 时,计算的是所有以 i 为较大下标 j 的坏数对 (p, i) 的数量。
复杂度
- 时间复杂度:  O ( N ) O(N) O(N), 其中  N N N 是数组 nums的长度。我们只需要遍历数组一次。哈希表的插入和查找操作平均时间复杂度为 O ( 1 ) O(1) O(1)。
- 空间复杂度:  O ( N ) O(N) O(N), 最坏情况下,哈希表可能需要存储  N N N 个不同的 nums[i] - i的值。
Code
class Solution {public long countBadPairs(int[] nums) {long ret = 0;Map<Integer, Integer> map = new HashMap<>(nums.length);for (int i = 0; i < nums.length; i++) {int key = nums[i] - i;int val = map.getOrDefault(key, 0);ret += (long)i - val; map.put(key, val + 1);}return ret;}
}
2874. 有序三元组中的最大值 II
题目

思路与解题过程
核心思想:枚举中间元素 + 前缀最大值 + 后缀最大值
我们要找的是三元组 (i, j, k) 使得 i < j < k,并且 (nums[i] - nums[j]) * nums[k] 的值最大。
 题目约束 1 <= nums[x] <= 10^6,所以 nums[k] 始终是正数。
 为了使乘积最大,我们需要:
- nums[k]尽可能大。
- nums[i] - nums[j]尽可能大(且为正)。这意味着- nums[i]要大,- nums[j]要小。
解法步骤:
-  枚举中间元素 j:
 我们可以遍历所有可能的j(从1到n-2,其中n是数组长度)。
-  优化 nums[i]和nums[k]的选择:- nums[i]必须从- nums[0...j-1]中选取。为了使- nums[i] - nums[j]最大,- nums[i]应为- nums[0...j-1]中的最大值。
- nums[k]必须从- nums[j+1...n-1]中选取。为了使整个表达式最大 (因为- nums[k]是正数),- nums[k]应为- nums[j+1...n-1]中的最大值。
 
-  预计算/动态计算: - 后缀最大值 suffixMax: 我们可以预先计算一个数组suffixMax,其中suffixMax[p]表示nums[p...n-1]中的最大值。这样,对于给定的j,nums[k]的最佳选择就是suffixMax[j+1]。
- 前缀最大值 prevMax: 当我们遍历j从左到右时,可以动态维护nums[0...j-1]的最大值。
 令prevMax为max(nums[0], nums[1], ..., nums[j-1]).
 
- 后缀最大值 
-  遍历与更新: - 初始化 ret = 0。
- 初始化 prevMax = nums[0].
- 遍历 j从1到n-2:- 当前的 prevMax就是max(nums[0...j-1]).
- nums[k]的最佳选择是- suffixMax[j+1].
- 如果 prevMax > nums[j](确保nums[i] - nums[j]为正,从而得到正的乘积),则计算current_value = (long)(prevMax - nums[j]) * suffixMax[j+1].
- 更新 ret = Math.max(ret, current_value).
- 更新 prevMax = Math.max(prevMax, nums[j]),为下一次迭代(当j变成j+1时)做准备。此时,当前的nums[j]将成为前缀的一部分。
 
- 当前的 
 
- 初始化 
注意: 乘积可能超过 int 的范围,所以计算时需要使用 long 类型。
复杂度
- 时间复杂度:  O ( N ) O(N) O(N). - 计算 suffixMax数组需要 O ( N ) O(N) O(N).
- 主循环遍历 j需要 O ( N ) O(N) O(N).
 
- 计算 
- 空间复杂度:  O ( N ) O(N) O(N), 主要用于存储 suffixMax数组。
Code
class Solution {public long maximumTripletValue(int[] nums) {long ret = 0;int n = nums.length;int[] suffixMax = new int[n];suffixMax[n - 1] = nums[n - 1];for (int i = n - 2; i >= 0; i--) {suffixMax[i] = Math.max(nums[i], suffixMax[i + 1]);}int prevMax = nums[0]; for (int j = 1; j < n - 1; j++) {if (prevMax > nums[j]) {long currentVal = (long)(prevMax - nums[j]) * suffixMax[j + 1];ret = Math.max(ret, currentVal);}prevMax = Math.max(prevMax, nums[j]);}return ret;}
}
