leecode217、219和220均是解决是否存在重复元素问题,在此统一依次解答。
目录
LeetCode 217 in Python
示例:
图1 Leecode 217示例
代码:
class Solution:
def containsDuplicate(self, nums):
list = set()
for num in nums:
if num in list:
return True
list.add(num)
return False
解释:
1) 设置一个集合list,根据集合不重复存储相同元素的特性进而判断数组是否有重复元素。
LeetCode 219 in Python
示例:
图2 Leecode 219示例
代码:
class Solution:
def containsNearbyDuplicate(self, nums, k):
dict = {}
for i, num in enumerate(nums):
if num not in dict:
dict[num] = i
else:
if i - dict[num] <= k:
return True
dict[num] = i
return False
解释:
1)在需要获得相同元素下标关系时,通过设置字典记录元素第一次出现的下标,在元素第二次出现时,仅需判断当前元素位置和字典中存储的元素第一次出现的位置即可判断两者间的位置关系:
for i, num in enumerate(nums):
if num not in dict:
dict[num] = i
else:
if i - dict[num] <= k:
return True
dict[num] = i
需要注意:一个元素在数组中可能出现不止一次,在元素第二次出现时除了要判断和第一个元素间的相对位置,还需更新此时字典存储的该元素的位置,以便与后续出现相同元素时进行位置判断。
LeetCode 220 in Python
示例:
图3 Leecode 220示例
220题意可简化理解为,对于位置i的元素u,是否存在下标范围在(max(0, i - indexDiff), i)的元素值满足在[u-valueDiff, u+valueDiff]范围内。
方法一:滑动窗口+二分查找
可以设置长度为indexDiff的滑动窗口,窗口内元素有序,判断窗口内最接近nums[i]的数是否在[u-valueDiff, u+valueDiff]范围内即可。如此一来就能判断能否找到满足条件的两个元素。其中在滑动窗口中找到num[i]可以用二分查找降低算法时间复杂度。该方法时间复杂度为O(nlogk),空间复杂度为O(k)。
代码:
from sortedcontainers import SortedList
class Solution:
def containsNearbyAlmostDuplicate(self, nums, indexDiff, valueDiff):
window = SortedList()
for i in range(len(nums)):
if i > indexDiff:
window.remove(nums[i - indexDiff - 1])
window.add(nums[i])
idx = bisect.bisect_left(window, nums[i])
if idx > 0 and abs(window[idx] - window[idx - 1]) <= valueDiff:
return True
if idx < len(window) - 1 and abs(window[idx] - window[idx + 1]) <= valueDiff:
return True
return False
解释:
1)初始化window为一个有序集合:
window = SortedList()
2)在i <= indexDiff时,有序集合只需逐个加入nums对应位置的元素即可,当i > indexDiff时滑动数组容量已满,需要滑动,移出最左边的元素,加入新元素:
for i in range(len(nums)):
if i > indexDiff:
window.remove(nums[i - 1 - indexDiff])
window.add(nums[i])
3)每加入一个元素就需查找该元素在有序集合即滑动窗口中的位置,同时找到与之最接近的两个元素,判断这两个元素与新加入nums[i]的值是否满足条件,若满足则直接返回结果。
idx = bisect.bisect_left(window, nums[i])
if idx > 0 and abs(window[idx] - window[idx - 1]) <= valueDiff:
return True
if idx < len(window) - 1 and abs(window[idx + 1] - window[idx]) <= valueDiff:
return True
注意:在有序集合中,最接近nums[i]的元素一定是紧挨着其左右的两个元素。
方法二:桶排序
由于方法一使用到二分查找,因此算法时间复杂度非线性。方法二将k个数字分到k个桶中,就能实现O(1)的时间复杂度。
代码:
class Solution:
def containsNearbyAlmostDuplicate(self, nums, indexDiff, valueDiff):
def getIdx(num):
if num < 0:
return ((num + 1) // size) - 1
else:
return num // size
size = valueDiff + 1
dict = {}
for i, num in enumerate(nums):
idx = getIdx(num)
if idx in dict:
return True
l, r = idx - 1, idx + 1
if l in dict and abs(dict[l] - num) <= valueDiff:
return True
if r in dict and abs(dict[r] - num) <= valueDiff:
return True
dict[idx] = num
if i >= indexDiff:
dict.pop(getIdx(nums[i - indexDiff]))
return False
解释:
1)桶大小为valueDiff + 1,举例说明,若valueDiff=3,数组元素为[0,1,2,3],若size=valueDiff,则[0, 1, 2]和[3]会放在两个不同的桶内,但据题意,0和3两值相差也满足<=valueDiff,因此将size设置为valueDiff + 1。
2)getIdx即桶排序的映射函数,分别对正数负数分别映射装桶,正数直接num//size即能判断对应桶,而对于负数则需先num + 1再//size,随后-1。举例说明,若数组元素为[-4, -3, -2, -1],桶大小size=4,若直接//size,[-4]会和[-3,-2, -1]会分在不同的桶,故需要num + 1变为[-3, -2, -1, 0],又因为0号桶已被归为正数使用,故负数对应桶号还需-1:
def getIdx(num):
if num < 0:
return ((num + 1) // size) - 1
else:
return num // size
2)若该元素对应桶中已有元素,返回True。若对应桶没有元素则判断左右相邻桶内是否有元素且桶内元素是否与该元素满足条件,满足则返回True:
l, r = idx - 1, idx + 1
if l in dict and abs(num - dict[l]) <= valueDiff:
return True
if r in dict and abs(num - dict[r]) <= valueDiff:
return True
3)在判断完该元素后将该元素加入桶中。接着控制桶个数,如果i >= indexDiff(“=”是因为i从0开始),则说明最前面的桶要开始更新了(类似方法一窗口开时滑动),此时弹出对应桶内元素:
dict[idx] = num
if i >= indexDiff:
dict.pop(getIdx(nums[i - indexDiff]))
另附上桶排序和其他排序算法代码实现: