这是Leetcode第220题,给定一个整数数组,问是否存在两个数差距最多为(t),两数的间隔最大为(k)。
很明显,这里需要用到一个大小为(K)的窗口,每次判断窗口内是否存在每个数差值满足(<=t)的条件。每一次都进行朴素的判断,需要用(O(K))的时间复杂度,主体循环一次就是(O(NK))。则,我们优化的目标是,怎么在(O(1))时间判断窗口内是否存在满足条件的数。
这里使用了hash Map的思路,更准确地讲,是桶排序的思想。将每个数(n)映射到(n//t)的key上,如果(t)等于零,则是(n)本身,每次判断(key-1)和(key+1)上面的数即可。具体代码如下:
class Solution:
def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
n=len(nums)
if t==0 and n==len(set(nums)):
return False
dic = {}
for i, v in enumerate(nums):
# t == 0 is a special case where we only have to check the bucket
# that v is in.
key, offset = (v // t, 1) if t else (v, 0)
for idx in range(key - offset, key + offset + 1):
if idx in dic and abs(dic[idx] - nums[i]) <= t:
return True
dic[key] = v
if i >= k:
# Remove the bucket which is too far away. Beware of zero t.
del dic[nums[i - k] // t if t else nums[i - k]]
return False
使用空间来降低时间复杂度是一个很常见的套路了。但更多时候,我们需要思考如下使用空间记录更多的信息,来帮助我们排除干扰,降低时间复杂度。
扩展:
[Leetcode]164. Maximum Gap 最大间距
求一个无序数组排序后相邻数的差距的最小值,如果使用基于比较的排序绝对是(O(nlogn)) 的。使用桶在这道题是有天然优势的,最大gap肯定是出现在后一个有效桶的min与前一个有效桶的max之间,不用去比较桶内元素的大小。
具体代码如下:
class Solution:
def maximumGap(self, nums: List[int]) -> int:
if len(nums) <= 1 :
return 0
minValue = 2**31-1
maxValue = -2**31
for num in nums:
minValue = min(minValue, num)
maxValue = max(maxValue, num)
bucket_range = (maxValue - minValue) // len(nums) + 1
bucket_num = ((maxValue - minValue) // bucket_range) + 1
hashmapMax = {}
hashmapMin = {}
for i in range(len(nums)):
bucket_id = (nums[i]-minValue) // bucket_range
if not bucket_id in hashmapMax:
hashmapMax[bucket_id] = nums[i]
hashmapMin[bucket_id] = nums[i]
else:
hashmapMax[bucket_id] = max(hashmapMax[bucket_id],nums[i])
hashmapMin[bucket_id] = min(hashmapMin[bucket_id],nums[i])
prev = 0
res = 0
for i in range(1,bucket_num):
if not i in hashmapMax:
continue
if not prev in hashmapMax:
continue
res = max(res, hashmapMin[i] - hashmapMax[prev])
prev = i
return res
[Leetcode]128. Longest Consecutive Sequence 最长连续序列
给定一个未排序的整数数组,找出最长连续序列的长度。要求算法的时间复杂度为 (O(n))。利用Map存储元素nums[i]的值以及其所在连续序列的长度,此时基本只有两种情况:
- 数组中出现过元素nums[i]-1或nums[i]+1,意味着当前元素可以归入左或右序列,那么此时假如左右序列的长度分别为left、right,那么显然加入nums[i]后,这整段序列的长度为 1+left+right,而由于这一整段序列中,只可能在左右两端扩展,所以只需要更新左右两端的value值即可。
- 数组中未出现过元素nums[i]-1或nums[i]+1,意味着当前元素所在的连续序列就是自身(只有自己一个元素)。
另外,注意去重即可
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
nums_set = set(nums)
dic = {}
res = 0
for num in nums_set:
left = dic.get(num-1,0)
right = dic.get(num+1,0)
cur = left+right+1
dic[num-left] = cur
dic[num+right] = cur
res = max(res,cur)
return res