每日算法-250526
每日算法学习记录 - 25.05.26
今天记录两道完成的算法题,分享一下解题思路和代码。
面试题 16.24. 数对和
题目描述:
设计一个算法,找出数组中两数之和为指定值的所有整数对。一个数只能属于一个数对。
💡 思路
排序 + 双指针
⚙️ 解题过程
这个问题要求我们找出数组中所有和为 target
的数对,并且每个数字只能使用一次。
- 排序:首先对数组进行排序。这样做的目的是便于我们使用双指针法,从两端向中间查找。
- 双指针:
- 初始化左指针
left
指向数组的开头(索引0
)。 - 初始化右指针
right
指向数组的末尾(索引nums.length - 1
)。
- 初始化左指针
- 遍历和判断:
- 当
left < right
时,持续迭代:- 计算当前左右指针指向的两个数
num1 = nums[left]
和num2 = nums[right]
的和currentSum = num1 + num2
。 - 如果
currentSum == target
:找到了一个数对。将(num1, num2)
加入结果列表。因为每个数只能用一次,所以两个指针都需要移动,left++
并且right--
,以寻找下一对可能的数对。 - 如果
currentSum < target
:说明当前和太小了,需要增大和。由于数组已排序,左边的数较小,右边的数较大,所以应将左指针left++
向右移动,尝试一个更大的num1
。 - 如果
currentSum > target
:说明当前和太大了,需要减小和。应将右指针right--
向左移动,尝试一个更小的num2
。
- 计算当前左右指针指向的两个数
- 当
- 返回结果:当
left >= right
时,遍历结束,返回收集到的所有数对。
📈 复杂度
- 时间复杂度: O ( N log N ) O(N \log N) O(NlogN)
Arrays.sort(nums)
需要 O ( N log N ) O(N \log N) O(NlogN) 时间。- 双指针遍历本身需要 O ( N ) O(N) O(N) 时间。
- 因此,总时间复杂度由排序决定。
- 空间复杂度: O ( log N ) O(\log N) O(logN) 或 O ( N ) O(N) O(N) (取决于排序算法的实现)
💻 Code
class Solution {public List<List<Integer>> pairSums(int[] nums, int target) {// 1. 排序数组Arrays.sort(nums);List<List<Integer>> resultPairs = new ArrayList<>();int left = 0;int right = nums.length - 1;// 2. 双指针遍历while (left < right) {int num1 = nums[left];int num2 = nums[right];int currentSum = num1 + num2;if (currentSum == target) {// 找到了一个数对resultPairs.add(Arrays.asList(num1, num2));// 移动双指针,继续寻找其他数对left++;right--;} else if (currentSum < target) {// 和太小,移动左指针left++;} else {// 和太大,移动右指针right--;}}return resultPairs;}
}
3371. 识别数组中的最大异常值
题目描述:
给你一个整数数组 nums
。数组中唯一一个不是众数的数字被称为「异常值」。数组的其余所有数字都是相等的「特殊数字」。请你返回「异常值」。
💡 思路
哈希表计数 + 数学推导/枚举
⚙️ 解题过程
题目指出,数组中有一个 “异常值”,其余所有数字都是相等的 “特殊数字”。设特殊数字的值为 s
,异常值为 o
。如果数组长度为 N
,那么有 N-1
个 s
和 1
个 o
。
数组总和 totalSum = (N-1) * s + o
。
- 预处理:
- 计算数组
nums
中所有元素的总和sum
。 - 使用哈希表
map
存储每个数字及其出现的次数。
- 计算数组
- 枚举与验证:
- 初始化一个结果变量
ret
为一个足够小的值。 - 遍历数组中的每一个数
candidateS
。我们假设这个candidateS
就是那个 “特殊数字”s
。 - 根据公式
2s + o = sum
(其中s
是candidateS
,o
是exceptionVal
),可以推导出潜在的异常值exceptionVal = sum - 2 * candidateS
。
- 初始化一个结果变量
- 判断与更新:
- 检查计算出的
exceptionVal
是否存在于数组中。 - 如果
exceptionVal
存在:- 情况一:
exceptionVal == candidateS
- 这意味着推断出的异常值和我们假设的特殊数字是同一个值。
- 此时,要满足 "两个特殊数字
candidateS
和一个异常值exceptionVal
,这确保了至少有两个这样的数字。map.get(candidateS) > 1
是一个必要的条件,确保我们能取出candidateS
作为特殊值,同时exceptionVal
(也等于candidateS
) 也能在数组中找到。 - 如果条件满足,更新
ret = Math.max(ret, exceptionVal)
。
- 情况二:
exceptionVal != candidateS
- 这意味着异常值和特殊数字是不同的。
- 我们假设的
candidateS
是特殊数字,exceptionVal
是异常值。 - 我们只需要
exceptionVal
存在于数组中即可(已通过map.containsKey(exceptionVal)
检查)。同时,candidateS
作为特殊数字也必须存在 (我们是从nums
中选取的,所以它肯定存在)。 - 如果条件满足,更新
ret = Math.max(ret, exceptionVal)
。
- 情况一:
- 检查计算出的
- 返回结果:遍历结束后,
ret
中存储的就是找到的最大的符合条件的异常值。
📈 复杂度
- 时间复杂度: O ( N ) O(N) O(N)
- 计算总和和构建哈希表需要 O ( N ) O(N) O(N) 时间。
- 遍历数组(或哈希表的键集,最坏情况 N N N 个不同元素)进行检查,每次哈希表操作为 O ( 1 ) O(1) O(1) 平均时间。总共是 O ( N ) O(N) O(N)。
- 空间复杂度: O ( N ) O(N) O(N)
- 哈希表在最坏情况下可能需要存储 N N N 个不同的数。
💻 Code
class Solution {public int getLargestOutlier(int[] nums) {int largestOutlier = -2001; Map<Integer, Integer> counts = new HashMap<>();long totalSum = 0; for (int x : nums) {totalSum += x;counts.put(x, counts.getOrDefault(x, 0) + 1);}for (int specialNumCandidate : nums) { int potentialOutlier = (int) (totalSum - 2 * (long)specialNumCandidate);if (counts.containsKey(potentialOutlier)) {if (potentialOutlier == specialNumCandidate) {if (counts.get(potentialOutlier) > 1) {largestOutlier = Math.max(largestOutlier, potentialOutlier);}} else {largestOutlier = Math.max(largestOutlier, potentialOutlier);}}}return largestOutlier;}
}