LeetCode 2540. 最小公共值 - 题解教程
1. 题目链接与难度
2540. 最小公共值 | 难度:简单
- 题目类型:数组、双指针
- 核心考点:利用有序特性优化查找
2. 白话题意重述
想象你手里有两叠已经按从小到大排好序的卡片:
- 第一叠卡片:
nums1 = [1, 2, 3] - 第二叠卡片:
nums2 = [2, 4]
你的任务是:找出这两叠卡片中,同时出现在两叠里的最小数字。
如果两个数组没有共同的数字,就返回 -1。
💡 关键点:
- 两叠卡片都已经按"非降序"排好了(就是从小到大,允许重复)
- "公共"的意思是:这个数字在两个数组中都至少出现一次
- 要找的是"最小"的公共值
3. 解法全景与决策
3.1 直观暴力解法
最自然的想法是:先记录第一个数组里有哪些数字,然后检查第二个数组。
具体步骤:
- 把
nums1的所有数字放进一个"备忘录"(比如哈希表) - 遍历
nums2,找出所有同时出现在"备忘录"里的数字 - 从这些公共数字中,挑出最小的那个
手动模拟:
nums1 = [1, 2, 3], nums2 = [2, 4]
Step 1: 记录 nums1 的所有数字
备忘录 = {1, 2, 3}
Step 2: 遍历 nums2
- 看到 2:2 在备忘录里!记录 2
- 看到 4:4 不在备忘录里,跳过
Step 3: 公共数字有 [2],最小的就是 2
代码思路(Python):
python
def getCommon(nums1, nums2):
seen = set(nums1) # 放进备忘录
common = []
for num in nums2:
if num in seen:
common.append(num)
return min(common) if common else -1
3.2 空间优化的契机
哈希表解法的时间复杂度是 O(m + n)------这已经是最优的了,因为每个元素至少要被看一次,不可能更快。
但是 ,我们来分析一下空间开销:哈希表需要额外存储 nums1 中的所有元素,空间复杂度是 O(m)。
🤔 一个自然的问题产生了:如果面试官问"能不能用 O(1) 空间来实现?",我们该怎么办?
回想一下这道题给我们的额外信息 :两个数组都是有序的。这个信息我们还没用上!
3.3 核心优化思路
关键洞察 :既然两个数组都是有序的,我们可以不用哈希表,直接用双指针"同步遍历"。
为什么有序就能不用哈希表?
在哈希表解法中,我们需要哈希表来"快速判断":nums2[j] 是否在 nums1 中出现过。这本质上是一个"查找"操作。
但如果两个数组都是有序的,查找可以更简单------我们让两个指针对齐着走,而不是用额外的空间来存储"我已经看过什么"。
生活类比:
想象你和朋友各拿着一本按拼音排序的通讯录,你们想找出共同的好友。
哈希表做法:你把你的通讯录全部背下来(O(m) 空间),然后一个个问朋友"这个人在不在你的本子里?"
对齐做法:你们同时翻开第一页,比较这两个名字:
- 如果你的名字排在前面,说明"你的朋友"不太可能出现在朋友那本的后半部分(拼音更靠后),所以你翻到下一页
- 如果朋友的名字排在前面,同理,朋友翻到下一页
- 如果恰好一样,bingo!这就是共同好友,而且一定是拼音最靠前的那个
为什么这能工作?
- 当
nums1[i] < nums2[j]时,nums1[i]不可能和nums2[j]或它后面的任何元素相等(因为nums2是递增的) - 所以我们可以安心地跳过
nums1[i],继续比较nums1[i+1]和nums2[j] - 反之亦然
ASCII 图示:
初始状态:
nums1 = [1, 2, 3]
↑
i=0
nums2 = [2, 4]
↑
j=0
比较 nums1[0]=1 和 nums2[0]=2
1 < 2,说明 1 不可能是公共元素(因为 nums2[0] 已经是 2 了,nums2 后面只有更大的数)
所以移动 i:
nums1 = [1, 2, 3]
↑
i=1
nums2 = [2, 4]
↑
j=0
比较 nums1[1]=2 和 nums2[0]=2
相等!找到公共元素,返回 2
核心思想:不需要"记忆"已经看过什么,只需要"对齐着走",利用有序性保证我们不会错过任何可能的公共元素。
4. 代码实现
理解了上面的思路后,代码就很好写了:
python
class Solution:
def getCommon(self, nums1: List[int], nums2: List[int]) -> int:
# 双指针分别指向两个数组的起始位置
i, j = 0, 0
# 同时遍历两个数组
while i < len(nums1) and j < len(nums2):
if nums1[i] == nums2[j]:
# 找到公共元素,直接返回(因为数组有序,这是最小的)
return nums1[i]
elif nums1[i] < nums2[j]:
# nums1 的当前元素更小,移动 i 指针
i += 1
else:
# nums2 的当前元素更小,移动 j 指针
j += 1
# 遍历结束还没找到公共元素
return -1
代码解释:
- 初始化双指针 :
i指向nums1的开头,j指向nums2的开头 - 比较并移动 :
- 如果
nums1[i] == nums2[j],找到了!直接返回(因为数组有序,第一个找到的就是最小的) - 如果
nums1[i] < nums2[j],说明nums1[i]太小了,不可能再和nums2[j]后面的元素匹配,所以i += 1 - 反之,
j += 1
- 如果
- 遍历结束 :如果
while循环结束还没返回,说明没有公共元素,返回-1
复杂度分析:
- 时间复杂度 :O(m + n),其中 m 和 n 分别是两个数组的长度。每个指针最多移动 m 和 n 步。
- 通俗解释:当 m = 1万,n = 1万时,最多进行 2万 次比较,完全没问题
- 空间复杂度:O(1),只用了两个指针变量,不需要额外的数据结构
5. 易错点与防坑指南
易错点 1:忘记处理"没有公共元素"的情况
错误示例:
python
def getCommon(nums1, nums2):
i, j = 0, 0
while i < len(nums1) and j < len(nums2):
if nums1[i] == nums2[j]:
return nums1[i]
elif nums1[i] < nums2[j]:
i += 1
else:
j += 1()
问题 :如果循环结束还没找到公共元素,函数会默认返回 None ,但题目要求返回 -1。
正确做法 :在 while 循环后显式返回 -1。
易错点 2:误以为需要找到"所有"公共元素再取最小值
一个很自然的尝试是:
"我得先找到所有公共元素,然后再用
min()找最小的。"
为什么这是多余的?
因为两个数组都是有序的 !当我们第一次找到 nums1[i] == nums2[j] 时,这个元素一定是最小的公共元素。
证明:
- 假设存在更小的公共元素
x,且x < nums1[i] - 因为
nums1是有序的,x一定在nums1[i]的前面 - 但我们的
i指针是从头开始移动的,如果x在nums1中存在,我们不可能跳过它 - 矛盾!所以第一次找到的公共元素就是最小的
启示:不需要找"所有"公共元素,找到第一个就可以直接返回了。
6. 思考题与同类扩展
思考题
变体问题 :如果两个数组是降序排序的,你会怎么调整代码?
点击查看思路提示
可以从数组的末尾开始遍历(双指针初始化为 i = len(nums1) - 1, j = len(nums2) - 1),比较逻辑类似,但移动方向相反。
同类题目推荐
-
- 推荐理由:同样的"两个数组+公共元素"问题,但这次要找出所有公共元素(不重复)。可以用哈希表或双指针。
-
- 推荐理由:同样利用双指针在一个有序(或按某种顺序)的结构中高效匹配,是双指针技巧的经典应用。
总结 :这道题的核心在于利用有序特性,用双指针实现 O(m + n) 时间、O(1) 空间的优雅解法。下次遇到"两个有序数组"的问题,记得先想想双指针!