「脸书」我接着刷 41 ~ 第一个缺失的正书
基本信息
- 题号:41
- 题厂:脸书
- 难度系数:高
一个无序数组里混合正数、负数,0,问数组确实的第一个正数是啥??
例如 nums = [1,2,0] 返回 3,因为 1、2 均存在 nums = [3,4,-1,1] 返回 2,1 存在,但是没有 2
1 排序
- 可以先将数组「排序」,再遍历一遍数组。当前元素小于等于 0,自动过滤;遇到正数后往下计数,如果接不上,说明遇上缺失正数,即可返回。
- 遍历完结后依然没有返回,说明这是一个完整的数组。即数组长度为 2,包含元素也为 1 和 2,所以返回 3(数组长度 + 1)。
class Solution: def firstMissingPositive(self, nums: List[int]) -> int: nums.sort() res = 1 for n in nums: if n > 0 and n > res: return res elif n > 0 and n == res: res += 1 return res
2 extra space mark
- 如果不采用排序,另一种解法是创建一个和数组长度对等的空数组。
- 遍历数组,遇上正数,在对应 「val - 1」 的 index 处标记。
- 再次遍历数组,遇上标记好的 mark 后即可返回……
class Solution: def firstMissingPositive(self, nums: List[int]) -> int: container = [0] * len(nums) for n in nums: if 0 < n <= len(nums): container[n - 1] = n for i in range(len(container)): if container[i] == 0: return i + 1 return len(container) + 1
以上 1 、 2 两种解法,顶多就算一道正常难度,甚至偏简单的考题。最多十分种即可解决问题。根本不算难题,本题的难点在于:时间复杂度 O(n);空间复杂度O(1)。
当我们采用 1 号排序算法时,复杂度为 O(nlogn);采用 2 号解法时,虽然满足了时间复杂度 O(n),但空间复杂度又上升到了 O(n)
解题思路
在满足时间空间复杂度的前提下解此题,可以参考 #448。这里运用正负号在输入数组内进行标记,从而同时满足时间复杂度 O(n)空间复杂度 O(1)的题目要求。
除了正负号,还要运用 val 和 index 的数学关联性。如果当前数组长度为 n,如果数组内不出现缺失正数的情况是,元素 1 ~ n 递增……
class Solution: def firstMissingPositive(self, nums: List[int]) -> int: # 第一轮遍历,将所有负数标记为 0 for i in range(len(nums)): if nums[i] < 0: nums[i] = 0 # 第二轮遍历,如果遇上正数,需要在对应「n - 1」的位置用负数标记 for n in nums: val = abs(n) if 0 < val <= len(nums): if nums[val - 1] > 0: nums[val - 1] = -1 * nums[val - 1] elif nums[val - 1] == 0: nums[val - 1] = -1 * val # 第三轮遍历,遇上正书,说明该位置空缺,即可返回跳出循环 for i in range(len(nums)): if nums[i] >= 0: return i + 1 return len(nums) + 1
Constraints
1 <= nums.length <= 105
-231 <= nums[i] <= 231 - 1
做题前要和考官讨论取值范围,数字可否重复出现等
测试
- n = 1 时
- 1 ~ n 全部对应出现无缺失时
- 当有缺失时:正负数大混合,只有正数但不为 1 ~ n 连续
- ……
Big O
时间复杂度:O(n)
- 空间复杂度: O(1)
总结
- 这是一道扩展性极大的题,非常具有研究价值
- 做出 1、2 解法者,为入门级码工;但要奋斗「歌脸软麻年薪百万者」,需熟练最优解且思维灵活懂得扩展
- 虽然这是一道难题,但充分体现了难题由中档题升级变形的思想。