每日算法-250430
每日算法 - 2025年4月30日
记录下今天解决的两道题目。
870. 优势洗牌 (Advantage Shuffle)
题目描述
解题思路与方法
核心思想:贪心策略 (田忌赛马)
这道题的目标是对于 nums1
中的每个元素,找到 nums2
中一个比它小的元素进行配对(如果可能),使得优势配对的数量最大化。如果 nums1
中的某个元素找不到 nums2
中可以战胜的对手,那么就用它去对付 nums2
中最强的对手,以保存 nums1
中更强的元素去对付 nums2
中可以战胜的对手。这类似于经典的“田忌赛马”策略。
具体步骤:
- 排序
nums1
:将nums1
升序排序,这样我们可以从小到大处理nums1
中的元素。 - 排序
nums2
的索引:我们不能直接排序nums2
,因为需要保留其原始元素的下标信息以构建最终结果ret
。因此,我们创建一个索引数组indexs
,存储0
到n-1
。然后根据nums2
中对应索引位置的元素值对indexs
进行升序排序。排序后,indexs[0]
指向nums2
中最小元素的原始索引,indexs[n-1]
指向nums2
中最大元素的原始索引。 - 双指针分配:
- 使用两个指针
left
和right
分别指向indexs
数组的开始(对应nums2
最小值)和结束(对应nums2
最大值)。 - 使用一个指针
i
遍历排序后的nums1
(从i=0
开始)。 - 比较
nums1[i]
(当前nums1
中最小的可用元素) 和nums2[indexs[left]]
(当前nums2
中最小的未匹配元素)。 - 如果
nums1[i] > nums2[indexs[left]]
:说明nums1[i]
可以战胜nums2
中当前最小的对手。这是一个优势匹配。我们将nums1[i]
分配给nums2
中这个最小对手的原始位置,即ret[indexs[left]] = nums1[i]
。然后移动left
指针 (left++
),考虑nums2
中下一个最小的对手。 - 如果
nums1[i] <= nums2[indexs[left]]
:说明nums1[i]
连nums2
中当前最小的对手都无法战胜。根据贪心策略,这个nums1[i]
应该去对付nums2
中最强的对手(反正也打不过,不如消耗掉对方最强的),以保留nums1
中更强的元素。我们将nums1[i]
分配给nums2
中当前最大对手的原始位置,即ret[indexs[right]] = nums1[i]
。然后移动right
指针 (right--
),考虑nums2
中下一个最大的对手。 - 无论哪种情况,
nums1[i]
都已经被使用,所以移动i
指针 (i++
) 处理nums1
中的下一个元素。
- 使用两个指针
- 循环继续:重复步骤 3,直到
left > right
,此时所有nums1
中的元素都已分配完毕。 - 返回结果数组
ret
。
复杂度分析
- 时间复杂度: O ( N log N ) O(N \log N) O(NlogN)。主要是排序
nums1
和indexs
数组所需的时间。双指针遍历过程是 O ( N ) O(N) O(N) 的。 - 空间复杂度: O ( N ) O(N) O(N)。需要额外的空间存储
indexs
数组和结果数组ret
。
Code
class Solution {public int[] advantageCount(int[] nums1, int[] nums2) {int n = nums2.length;Integer[] indexs = new Integer[n];for (int i = 0; i < n; i++) {indexs[i] = i;}Arrays.sort(nums1);Arrays.sort(indexs, (a, b) -> (nums2[a] - nums2[b]));int left = 0, right = n - 1;int i = 0;int[] ret = new int[n];while (left <= right) {int index = 0;if (nums1[i] <= nums2[indexs[left]]) {index = indexs[right];right--;} else {index = indexs[left];left++;}ret[index] = nums1[i];i++;}return ret;}
}
3402. 使每一列严格递增的最少操作次数 (Minimum Operations to Make Columns Strictly Increasing)
题目描述
解题思路与方法
核心思想:贪心策略
题目要求我们用最少的操作次数使得网格 grid
的每一列都严格递增。对于每一列,我们需要确保 grid[j][i] > grid[j-1][i]
对所有 j > 0
成立。
为了使操作次数最少,当发现 grid[j][i] <= grid[j-1][i]
时,我们应该将 grid[j][i]
增加到刚好满足严格递增条件的最小值,即 grid[j-1][i] + 1
。
具体步骤:
- 初始化总操作次数
ret = 0
。 - 遍历每一列:外层循环遍历列索引
i
从0
到grid[0].length - 1
。 - 遍历每一行的元素(从第二行开始):内层循环遍历行索引
j
从1
到grid.length - 1
。 - 检查条件:在每一列内部,比较当前元素
grid[j][i]
和它正上方的元素grid[j-1][i]
。 - 执行操作:
- 如果
grid[j][i] <= grid[j-1][i]
,说明不满足严格递增条件。 - 计算需要增加的值:
increase = (grid[j-1][i] + 1) - grid[j][i]
。 - 将这个增加的值累加到总操作次数
ret
中。 - 关键:更新
grid[j][i]
的值 为grid[j-1][i] + 1
。这一步非常重要,因为下一行的元素grid[j+1][i]
需要和 更新后 的grid[j][i]
进行比较。
- 如果
- 遍历完所有列和行后,
ret
就是所需的最小总操作次数。 - 返回
ret
。
复杂度分析
- 时间复杂度: O ( R × C ) O(R \times C) O(R×C),其中 R 是网格的行数,C 是网格的列数。我们需要遍历网格中的每个元素一次(除了第一行)。
- 空间复杂度: O ( 1 ) O(1) O(1)。我们是在原地修改
grid
(虽然题目可能没要求必须原地修改,但这样做不影响结果且节省空间),只需要常数级别的额外空间存储变量ret
、i
、j
等。
Code
class Solution {public int minimumOperations(int[][] grid) {int ret = 0;for (int i = 0; i < grid[0].length; i++) {for (int j = 1; j < grid.length; j++) {if (grid[j][i] <= grid[j - 1][i]) {ret += (grid[j - 1][i] + 1 - grid[j][i]);grid[j][i] = grid[j - 1][i] + 1;}}}return ret;}
}