LeetCode 分类练习(四):查找2

目录

  • LeetCode 分类练习(四):查找2
    • 一、双指针(快慢指针、对撞指针)
      • 1.快慢指针
      • 2.对撞指针
      • 3.滑动窗口法
    • 二、滑动数组
    • 三、实战1(对撞指针)
      • 案例1: 1 Two Sum
      • 案例2: 15 3Sum
      • 案例3: 18 4Sum
      • 案例4: 16 3Sum Closest
      • 案例5: 454 4SumⅡ
      • 案例6: 49 Group Anagrams
      • 案例7: 447 Number of Boomerangs
      • 案例8: 149 Max Points on a Line
    • 四、实战2(滑动数组)
      • 案例1: 219 Contains Dupliccate Ⅱ
      • 案例2: 220 Contains Dupliccate Ⅲ
    • 【参考资料】

一、双指针(快慢指针、对撞指针)

  双指针,指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个相同方向(快慢指针)或者相反方向(对撞指针)的指针进行扫描,从而达到相应的目的

  双指针是一种思想,一种技巧或一种方法,并不是什么特别具体的算法,在二分查找等算法中经常用到这个技巧。具体就是用两个变量动态存储两个或多个结点,来方便我们进行一些操作。通常用在线性的数据结构中,比如链表和数组,有时候也会用在图算法中。

  在我们遇到像数组,链表这类数据结构的算法题目的时候,应该要想得到双指针的套路来解决问题。特别是链表类的题目,经常需要用到两个或多个指针配合来记忆链表上的节点,完成某些操作。链表这种数据结构也是树形结构和图的原型,所以有时候在关于图和树形结构的算法题目中也会用到双指针。

  当你遇到此类数据结构,尝试使用双指针来解题的时候,可以从以下几个双指针类题目的套路入手进行思考。

1.快慢指针

  快慢指针也是双指针,但是两个指针从同一侧开始遍历数组,将这两个指针分别定义为快指针(fast)和慢指针(slow),两个指针以不同的策略移动,直到两个指针的值相等(或其他特殊条件)为止,如fast每次增长两个,slow每次增长一个。

  类似于龟兔赛跑,两个链表上的指针从同一节点出发,其中一个指针前进速度是另一个指针的两倍。利用快慢指针可以用来解决某些算法问题,比如

  1. 计算链表的中点:快慢指针从头节点出发,每轮迭代中,快指针向前移动两个节点,慢指针向前移动一个节点,最终当快指针到达终点的时候,慢指针刚好在中间的节点。
  2. 判断链表是否有环:如果链表中存在环,则在链表上不断前进的指针会一直在环里绕圈子,且不能知道链表是否有环。使用快慢指针,当链表中存在环时,两个指针最终会在环中相遇。
  3. 判断链表中环的起点:当我们判断出链表中存在环,并且知道了两个指针相遇的节点,我们可以让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。
  4. 求链表中环的长度:只要相遇后一个不动,另一个前进直到相遇算一下走了多少步就好了
  5. 求链表倒数第k个元素:先让其中一个指针向前走k步,接着两个指针以同样的速度一起向前进,直到前面的指针走到尽头了,则后面的指针即为倒数第k个元素。(严格来说应该叫先后指针而非快慢指针)

2.对撞指针

  对撞指针是指在有序数组中,将指向最左侧的索引定义为左指针(left),最右侧的定义为右指针(right),然后从两头向中间进行数组遍历

  对撞数组适用于有序数组,也就是说当你遇到题目给定有序数组时,应该第一时间想到用对撞指针解题。

• 对撞指针是双指针算法之一。
• 对撞指针从两端向中间迭代数组。一个指针从始端开始,另一个从末端开始。
• 对撞指针的终止条件是两个指针相遇
• 对撞指针常用于排序数组

  伪代码大致如下:

function fn (list) {var left = 0;var right = list.length - 1;//遍历数组while (left <= right) {left++;// 一些条件判断 和处理... ...right--;}
}

3.滑动窗口法

  两个指针,一前一后组成滑动窗口,并计算滑动窗口中的元素的问题

  这类问题一般包括:

1)字符串匹配问题
2)子数组问题

  一般都是排好序的数组或链表,否则无序的话这两个指针的位置也没有什么意义。特别注意两个指针的循环条件在循环体中的变化,小心右指针跑到左指针左边去了。常用来解决的问题有:

1)二分查找问题
2)n数之和问题:比如两数之和问题,先对数组排序然后左右指针找到满足条件的两个数。如果是三数问题就转化为一个数和另外两个数的两数问题。以此类推。

二、滑动数组

  数组是最常用的数据结构之一,对数组的下标进行特殊处理,使每一次操作仅保留若干有用信息,新的元素不断循环刷新,看上去数组的空间被滑动地利用,此模型我们称其为滑动数组。其主要达到压缩存储的作用,一般常用在DP类题目中。因为DP题目是一个自下而上的扩展过程,我们常常用到是连续的解,而每次用到的只是解集中的最后几个解,所以以滑动数组形式能大大减少内存开支。

  滑动数组是DP中的一种编程思想。简单的理解就是让数组滑动起来,每次都使用固定的几个存储空间,来达到压缩,节省存储空间的作用。起到优化空间,主要应用在递推或动态规划中(如01背包问题)。因为DP题目是一个自底向上的扩展过程,我们常常需要用到的是连续的解,前面的解往往可以舍去。所以用滑动数组优化是很有效的。利用滑动数组的话在N很大的情况下可以达到压缩存储的作用。

三、实战1(对撞指针)

案例1: 1 Two Sum

  【题目描述

  给出一个整型数组nums,返回这个数组中两个数字的索引值i和j,使得nums[i] + nums[j]等于一个给定的target值,两个索引不能相等。

如:nums= [2,7,11,15],target=9 返回[0,1]

  【解题思路

  需要考虑:

  1. 开始数组是否有序;
  2. 索引从0开始计算还是1开始计算?
  3. 没有解该怎么办?
  4. 有多个解怎么办?保证有唯一解。

  1.暴力法O(n^2)
  时间复杂度为O(n^2),第一遍遍历数组,第二遍遍历当前遍历值之后的元素,其和等于target则return。

class Solution:def twoSum(self, nums: List[int], target: int) -> List[int]:len_nums = len(nums)for i in range(len_nums):for j in range(i+1,len_nums):if nums[i] + nums[j] == target:return [i,j]

  2.排序+指针对撞(O(n)+O(nlogn)=O(n))
  在数组篇的LeetCode 167题中,也遇到了找到两个数使得它们相加之和等于目标数,但那是对于排序的情况,因此也可以使用上述的思路来完成。
因为问题本身不是有序的,因此需要对原来的数组进行一次排序,排序后就可以用O(n)的指针对撞进行解决。
  但是问题是,返回的是数字的索引,如果只是对数组的值进行排序,那么数组原来表示的索引的信息就会丢失,所以在排序前要进行些处理。
  错误代码示例–只使用dict来进行保存:

class Solution:def twoSum(self, nums: List[int], target: int) -> List[int]:record = dict()for index in range(len(nums)):record[nums[index]] = index nums.sort()l,r = 0,len(nums)-1while l < r:if nums[l] + nums[r] == target:return [record[nums[l]],record[nums[r]]]elif nums[l] + nums[r] < target:l += 1else:r -= 1

  当遇到相同的元素的索引问题时,会不满足条件:

如:[3,3] 6

  在排序前先使用一个额外的数组拷贝一份原来的数组,对于两个相同元素的索引问题,使用一个bool型变量辅助将两个索引都找到,总的时间复杂度为O(n)+O(nlogn) = O(nlogn)

class Solution:def twoSum(self, nums: List[int], target: int) -> List[int]:record = dict()nums_copy = nums.copy()sameFlag = True;nums.sort()l,r = 0,len(nums)-1while l < r:if nums[l] + nums[r] == target:breakelif nums[l] + nums[r] < target:l += 1else:r -= 1res = []for i in range(len(nums)):if nums_copy[i] == nums[l] and sameFlag:res.append(i)sameFlag = Falseelif nums_copy[i] == nums[r]:res.append(i)return res

  小套路:
  如果只是对数组的值进行排序,那么数组原来表示的索引的信息就会丢失的情况,可以在排序前:

  更加pythonic的实现
  通过list(enumerate(nums))开始实现下标和值的绑定,不用专门的再copy加bool判断。

nums = list(enumerate(nums))
nums.sort(key = lambda x:x[1])
i,j = 0, len(nums)-1
while i < j:if nums[i][1] + nums[j][1] > target:j -= 1elif nums[i][1] + nums[j][1] < target:i += 1else:if nums[j][0] < nums[i][0]:nums[j],nums[i] = nums[i],nums[j]return num[i][0],nums[j][0]

  拷贝数组 + bool型变量辅助

  3.查找表–O(n)
  遍历数组过程中,当遍历到元素v时,可以只看v前面的元素,是否含有target-v的元素存在。

  1. 如果查找成功,就返回解;
  2. 如果没有查找成功,就把v放在查找表中,继续查找下一个解。

  即使v放在了之前的查找表中覆盖了v,也不影响当前v元素的查找。因为只需要找到两个元素,只需要找target-v的另一个元素即可。

class Solution:def twoSum(self, nums: List[int], target: int) -> List[int]:record = dict()for i in range(len(nums)):complement = target - nums[i]# 已经在之前的字典中找到这个值if record.get(complement) is not None:res = [i,record[complement]]return resrecord[nums[i]] = i

  只进行一次循环,故时间复杂度O(n),空间复杂度为O(n)

  补充思路
  通过enumerate来把索引和值进行绑定,进而对value进行sort,前后对撞指针进行返回。

class Solution:def twoSum(self, nums: List[int], target: int) -> List[int]:nums = list(enumerate(nums))# 根据value来排序nums.sort(key = lambda x:x[1])l,r = 0, len(nums)-1while l < r:if nums[l][1] + nums[r][1] == target:return nums[l][0],nums[r][0]elif nums[l][1] + nums[r][1] < target:l += 1else:r -= 1

案例2: 15 3Sum

  【题目描述

  给出一个整型数组,寻找其中的所有不同的三元组(a,b,c),使得a+b+c=0。
  注意:答案中不可以包含重复的三元组。

如:nums = [-1, 0, 1, 2, -1, -4],结果为:[[-1, 0, 1],[-1, -1, 2]]

  【解题思路

  1. 数组不是有序的;
  2. 返回结果为全部解,多个解的顺序是否需要考虑?–不需要考虑顺序
  3. 什么叫不同的三元组?索引不同即不同,还是值不同?–题目定义的是,值不同才为不同的三元组
  4. 没有解时怎么返回?–空列表

  【分析实现
  因为上篇中已经实现了Two Sum的问题,因此对于3Sum,首先想到的思路就是,开始固定一个k,然后在其后都当成two sum问题来进行解决,但是这样就ok了吗?

  没有考虑重复元素导致错误

  直接使用Two Sum问题中的查找表的解法,根据第一层遍历的i,将i之后的数组作为two sum问题进行解决。

class Solution:def threeSum(self, nums: [int]) -> [[int]]:res = []for i in range(len(nums)):num = 0 - nums[i]record = dict()for j in range(i + 1, len(nums)):complement = num - nums[j]# 已经在之前的字典中找到这个值if record.get(complement) is not None:res_lis = [nums[i], nums[j], complement]res.append(res_lis)record[nums[j]] = ireturn res

  但是这样会导致一个错误,错误用例如下:

输入:[-1,0,1,2,-1,-4]
输出:[[-1,1,0],[-1,-1,2],[0,-1,1]]预期结果:[[-1,-1,2],[-1,0,1]]

  代码在实现的过程中没有把第一次遍历的i的索引指向相同元素的情况排除掉,于是出现了当i指针后面位置的元素有和之前访问过的相同的值,于是重复遍历。

  那么可以考虑,开始时对nums数组进行排序,排序后,当第一次遍历的指针k遇到下一个和前一个指向的值重复时,就将其跳过。为了方便计算,在第二层循环中,可以使用对撞指针的套路:

# 对撞指针套路
l,r = 0, len(nums)-1
while l < r:if nums[l] + nums[r] == target:return nums[l],nums[r]elif nums[l] + nums[r] < target:l += 1else:r -= 1

  其中需要注意的是,在里层循环中,也要考虑重复值的情况,因此当值相等时,再次移动指针时,需要保证其指向的值和前一次指向的值不重复,因此可以:

# 对撞指针套路
l,r = 0, len(nums)-1
while l < r:sum = nums[i] + nums[l] + nums[r]if sum == target:res.append([nums[i],nums[l],nums[r])l += 1r -= 1while l < r and nums[l] == nums[l-1]: l += 1while l < r and nums[r] == nums[r+1]: r -= 1elif sum < target:l += 1else:r -= 1

  再调整下遍历的范围,因为设了3个索引:i,l,r。边界情况下,r索引指向len-1, l指向len-2,索引i遍历的边界为len-3,故for循环是从0到len-2。
  代码实现如下:

  代码实现

class Solution:def threeSum(self, nums: [int]) -> [[int]]:nums.sort()res = []for i in range(len(nums)-2):# 因为是排序好的数组,如果最小的都大于0可以直接排除if nums[i] > 0: break# 排除i的重复值if i > 0 and nums[i] == nums[i-1]: continuel,r = i+1, len(nums)-1while l < r:sum = nums[i] + nums[l] + nums[r]if sum == 0:res.append([nums[i],nums[l],nums[r]])l += 1r -= 1while l < r and nums[l] == nums[l-1]: l += 1while l < r and nums[r] == nums[r+1]: r -= 1elif sum < 0:l += 1else:r -= 1return res

  小套路

  1. 采用for + while的形式来处理三索引;
  2. 当数组不是有序时需要注意,有序的特点在哪里,有序就可以用哪些方法解决?无序的话不便在哪里?
  3. 对撞指针套路:
# 对撞指针套路
l,r = 0, len(nums)-1
while l < r:if nums[l] + nums[r] == target:return nums[l],nums[r]elif nums[l] + nums[r] < target:l += 1else:r -= 1
  1. 处理重复值的套路:先转换为有序数组,再循环判断其与上一次值是否重复:
# 1.
for i in range(len(nums)):if i > 0 and nums[i] == nums[i-1]: continue# 2.
while l < r:
while l < r and nums[l] == nums[l-1]: l += 1

  1)解法1(暴力求解)

class Solution:def threeSum(self, nums: List[int]) -> List[List[int]]:res = set()d = {}for v in sorted(nums):if v in d:d[v] += 1else:d[v] = 1for a in d:d[a] -= 1if a > 0:breakfor b in d:if d[b]:d[b] -= 1c = -a -bif c >= b and d.get(c, 0):res.add(tuple(sorted((a, b, c))))d[b] += 1return [list(item) for item in res]


  2)解法2(哈希)

class Solution:def threeSum(self, nums):nums.sort()ans = []for i,num in enumerate(nums[:-2]):if i > 0 and num == nums[i-1]:continuetwo = self.twoSum(-num,nums[i+1:])if two != []:for t in two:ans.append([num]+t)return ansdef twoSum(self,target,nums):hash_table = {}ans = []for i,num in enumerate(nums):if target - num in hash_table:if [target-num,num] not in ans:ans.append([target-num,num])hash_table[num] = ireturn ans

案例3: 18 4Sum

  【题目描述

  给出一个整形数组,寻找其中的所有不同的四元组(a,b,c,d),使得a+b+c+d等于一个给定的数字target。

如:nums = [1, 0, -1, 0, -2, 2],target = 0结果为:[[-1,  0, 0, 1],[-2, -1, 1, 2],[-2,  0, 0, 2]]

  【解题思路

  4Sum可以当作是3Sum问题的扩展,注意事项仍是一样的,同样是不能返回重复值得解。首先排序。接着从[0,len-1]遍历i,跳过i的重复元素,再在[i+1,len-1]中遍历j,得到i,j后,再选择首尾的l和r,通过对撞指针的思路,四数和大的话r–,小的话l++,相等的话纳入结果list,最后返回。

  套用3Sum得代码,在其前加一层循环,对边界情况进行改动即可:

  1. 原来3个是到len-2,现在外层循环是到len-3;
  2. 在中间层得迭代中,当第二个遍历得值在第一个遍历得值之后且后项大于前项时,认定为重复;
  3. 加些边界条件判断:当len小于4时,直接返回;当只有4个值且长度等于target时,直接返回本身即可。

  代码实现如下:

class Solution:def fourSum(self, nums: List[int], target: int) -> List[List[int]]:nums.sort()res = []if len(nums) < 4: return resif len(nums) == 4 and sum(nums) == target:res.append(nums)return resfor i in range(len(nums)-3):if i > 0 and nums[i] == nums[i-1]: continuefor j in range(i+1,len(nums)-2):if j > i+1 and nums[j] == nums[j-1]: continuel,r = j+1, len(nums)-1while l < r:sum_value = nums[i] + nums[j] + nums[l] + nums[r]if sum_value == target:res.append([nums[i],nums[j],nums[l],nums[r]])l += 1r -= 1while l < r and nums[l] == nums[l-1]: l += 1while l < r and nums[r] == nums[r+1]: r -= 1elif sum_value < target:l += 1else:r -= 1return res

  还可以使用combinations(nums, 4)来对原数组中得4个元素全排列,在开始sort后,对排列得到得元素进行set去重。但单纯利用combinations实现会超时。

  超出时间限制

class Solution:def fourSum(self, nums: List[int], target: int) -> List[List[int]]:nums.sort()from itertools import combinationsres = []for i in combinations(nums, 4):if sum(i) == target:res.append(i)res = set(res)return res

  1)解法1(哈希)

class Solution:def fourSum(self, nums: List[int], target: int) -> List[List[int]]:result = []res = {}if not nums or len(nums) < 4:return resultnums.sort()for k in range(len(nums)-3):target1 = target-nums[k]if len(nums)-k>=4:for i in range(k+1,len(nums)):# if i > 0 and nums[i-1]==nums[i]:#     continue d = {}target2 = target1 -nums[i]if len(nums)-i>=3:for j in range(i+1,len(nums)):if target2 - nums[j] in d:item = [nums[k],nums[i],nums[j],target2-nums[j]]item.sort()key = ''.join([str(x) for x in item ])if key not in res:result.append(item)res[key] = 1d[nums[j]] = jreturn result


  2)解法2(暴力求解)

class Solution:def fourSum(self, nums: List[int], target: int) -> List[List[int]]:fin_list=[]nums.sort()length = len(nums)for i in range(0,length-3):for j in range(i + 1,length-2):L = j + 1R = length -1while L < R:if nums[i] + nums[j] + nums[L] + nums[R] == target:fin_list.append([nums[i],nums[j],nums[L],nums[R]])L += 1R -= 1elif nums[i] + nums[j] + nums[L] + nums[R] > target:R -= 1else:L +=1 new_list = []for item in fin_list:if item not in new_list:new_list.append(item)return new_list

案例4: 16 3Sum Closest

  【题目描述

  给出一个整形数组,寻找其中的三个元素a,b,c,使得a+b+c的值最接近另外一个给定的数字target。

如:给定数组 nums = [-1,2,1,-4], 和 target = 1.与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).

  【解题思路

  这道题也是2sum,3sum等题组中的,只不过变形的地方在于不是找相等的target,而是找最近的。

  那么开始时可以随机设定一个三个数的和为结果值,在每次比较中,先判断三个数的和是否和target相等,如果相等直接返回和。如果不相等,则判断三个数的和与target的差是否小于这个结果值时,如果小于则进行则进行替换,并保存和的结果值。

  伪代码

# 先排序
nums.sort()
# 随机选择一个和作为结果值
res = nums[0] + nums[1] + nums[2]
# 记录这个差值
diff = abs(nums[0]+nums[1]+nums[2]-target)
# 第一遍遍历
for i in range(len(nums)):# 标记好剩余元素的l和rl,r = i+1, len(nums-1)while l < r:if 后续的值等于target:return 三个数值得和else:if 差值小于diff:更新diff值更新res值if 和小于target:将l移动else:(开始已经排除了等于得情况,要判断和大于target)将r移动

  3Sum问题两层遍历得套路代码

nums.sort()
res = []
for i in range(len(nums)-2):l,r = i+1, len(nums)-1while l < r:sum = nums[i] + nums[l] + nums[r]if sum == 0:res.append([nums[i],nums[l],nums[r]])elif sum < 0:l += 1else:r -= 1

  代码实现

class Solution:def threeSumClosest(self, nums: List[int], target: int) -> int:nums.sort()diff = abs(nums[0]+nums[1]+nums[2]-target)res = nums[0] + nums[1] + nums[2]for i in range(len(nums)):l,r = i+1,len(nums)-1t = target - nums[i]while l < r:if nums[l] + nums[r] == t:return nums[i] + telse:if abs(nums[l]+nums[r]-t) < diff:diff = abs(nums[l]+nums[r]-t)res = nums[i]+nums[l]+nums[r]if nums[l]+nums[r] < t:l += 1else:r -= 1return res

  时间复杂度为O(n^2),空间复杂度为O(1)。

  1)解法1(暴力求解)

class Solution:def threeSumClosest(self, nums: List[int], target: int) -> int:n= len(nums)minnum=float('inf')for i in range(n-2):for j in range(i+1,n-1):for k in range(j+1,n):if nums[i]+nums[j]+nums[k]==target:return targetif abs(nums[i]+nums[j]+nums[k]-target)<abs(minnum):minnum=nums[i]+nums[j]+nums[k]-targetreturn minnum+target

案例5: 454 4SumⅡ

  【题目描述

  给出四个整形数组A,B,C,D,寻找有多少i,j,k,l的组合,使得A[i]+B[j]+C[k]+D[l]=0。其中,A,B,C,D中均含有相同的元素个数N,且0<=N<=500;

输入:A = [ 1, 2] B = [-2,-1] C = [-1, 2] D = [ 0, 2]输出:2

  【解题思路

  这个问题同样是Sum类问题得变种,其将同一个数组的条件,变为了四个数组中,依然可以用查找表的思想来实现。

  首先可以考虑把D数组中的元素都放入查找表,然后遍历前三个数组,判断target减去每个元素后的值是否在查找表中存在,存在的话,把结果值加1。那么查找表的数据结构选择用set还是dict?考虑到数组中可能存在重复的元素,而重复的元素属于不同的情况,因此用dict存储,最后的结果值加上dict相应key的value,代码如下:

  1.O(n^3)代码

from collections import Counter
record = Counter()
# 先建立数组D的查找表
for i in range(len(D)):record[D[i]] += 1
res = 0
for i in range(len(A)):for j in range(len(B)):for k in range(len(C)):num_find = 0-A[i]-B[j]-C[k]if record.get(num_find) != None:res += record(num_find)
return res

  但是对于题目中给出的数据规模:N<=500,如果N为500时,n^3的算法依然消耗很大,能否再进行优化呢?

  根据之前的思路继续往前走,如果只遍历两个数组,那么就可以得到O(n^2)级别的算法,但是遍历两个数组,那么还剩下C和D两个数组,上面的值怎么放?

  对于查找表问题而言,很多时候到底要查找什么,是解决的关键。对于C和D的数组,可以通过dict来记录其中和的个数,之后遍历结果在和中进行查找。代码如下:

  2.O(n^2)级代码

class Solution:def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:from collections import Counterrecord = Counter()for i in range(len(A)):for j in range(len(B)):record[A[i]+B[j]] += 1res = 0for i in range(len(C)):for j in range(len(D)):find_num = 0 - C[i] - D[j]if record.get(find_num) != None:res += record[find_num]return res

  再使用Pythonic的列表生成式和sum函数进行优化,如下:

class Solution:def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:record = collections.Counter(a + b for a in A for b in B)return sum(record.get(- c - d, 0) for c in C for d in D)

  1)解法1(字典法)

class Solution:def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:dic = collections.Counter(a + b for a in A for b in B)return sum(dic.get(- c - d, 0) for c in C for d in D)


  2)解法2(哈希)

class Solution:def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:ans = 0CDmap = collections.defaultdict(int)for c in C:for d in D:CDmap[c+d] += 1for a in A:for b in B:if -a-b in CDmap:ans += CDmap[-a-b]return ans

案例6: 49 Group Anagrams

  【题目描述

  给出一个字符串数组,将其中所有可以通过颠倒字符顺序产生相同结果的单词进行分组。

示例:输入: ["eat", "tea", "tan", "ate", "nat", "bat"],输出:[["ate","eat","tea"],["nat","tan"],["bat"]]说明:所有输入均为小写字母。不考虑答案输出的顺序。

  【解题思路

  在之前LeetCode 242的问题中,对字符串t和s来判断,判断t是否是s的字母异位词。当时的方法是通过构建t和s的字典,比较字典是否相同来判断是否为异位词。

  在刚开始解决这个问题时,我也局限于了这个思路,以为是通过移动指针,来依次比较两个字符串是否对应的字典相等,进而确定异位词列表,再把异位词列表添加到结果集res中。于是有:

  错误思路

nums = ["eat", "tea", "tan", "ate", "nat", "bat"]from collections import Counter
cum = []
for i in range(len(nums)):l,r = i+1,len(nums)-1i_dict = Counter(nums[i])res = []if nums[i] not in cum:res.append(nums[i])while l < r:l_dict = Counter(nums[l])r_dict = Counter(nums[r])if i_dict == l_dict and l_dict == r_dict:res.append(nums[l],nums[r])l += 1r -= 1elif i_dict == l_dict:res.append(nums[l])l += 1elif i_dict == r_dict:res.append(nums[r])r -= 1else:l += 1print(res)cum.append(res)
......................................

  这时发现长长绵绵考虑不完,而且还要注意指针的条件,怎样遍历才能遍历所有的情况且判断列表是否相互间包含。。。

  于是立即开始反思是否哪块考虑错了?回顾第一开始的选择数据结构,在dict和list中,自己错误的选择了list来当作数据结构,进而用指针移动来判断元素的情况。而没有利用题目中不变的条件。

  题目的意思,对异位词的进行分组,同异位词的分为一组,那么考虑对这一组内什么是相同的,且这个相同的也能作为不同组的判断条件。
  不同组的判断条件,就可以用数据结构dict中的key来代表,那么什么相同的适合当作key呢?

  这时回顾下下LeetCode 242,当时是因为异位字符串中包含的字符串的字母个数都是相同的,故把字母当作key来进行判断是否为异位词。

  但是对于本题,把每个字符串的字母dict,再当作字符串数组的dict的key,显然不太合适,那么对于异位词,还有什么是相同的?

  显然,如果将字符串统一排序,异位词排序后的字符串,显然都是相同的。那么就可以把其当作key,把遍历的数组中的异位词当作value,对字典进行赋值,进而遍历字典的value,得到结果list。

  需要注意的细节是,字符串和list之间的转换:

  1. 默认构造字典需为list的字典;
  2. 排序使用sorted()函数,而不用list.sort()方法,因为其不返回值;
  3. 通过’’.join(list),将list转换为字符串;
  4. 通过str.split(’,’)将字符串整个转换为list中的一项;
class Solution:def groupAnagrams(self, strs: List[str]) -> List[List[str]]:from collections import defaultdictstrs_dict = defaultdict(list)res = []for str in strs:key = ''.join(sorted(list(str)))strs_dict[key] += str.split(',')for v in strs_dict.values():res.append(v)return res

  再将能用列表生成式替换的地方替换掉,代码实现如下:

class Solution:def groupAnagrams(self, strs: List[str]) -> List[List[str]]:from collections import defaultdictstrs_dict = defaultdict(list)for str in strs:key = ''.join(sorted(list(str)))strs_dict[key] += str.split(',')return [v for v in strs_dict.values()]

  1)解法1(哈希表)

class Solution:def groupAnagrams(self, strs: List[str]) -> List[List[str]]:r = dict()for item in strs:cur_sum = 0for c in item:cur_sum += hash(c)if cur_sum not in r.keys():r[cur_sum] = [item]else:r[cur_sum].append(item)return list(r.values())


  2)解法2(字典法)

class Solution:def groupAnagrams(self, strs: List[str]) -> List[List[str]]:dic = {} #res = []for word in strs:dic.setdefault(str(sorted(word)),[]).append(word)for val in dic.values():res.append(val)return res

案例7: 447 Number of Boomerangs

  【题目描述

  给出一个平面上的n个点,寻找存在多少个由这些点构成的三元组(i,j,k),使得i,j两点的距离等于i,k两点的距离。

  其中n最多为500,且所有的点坐标的范围在[-10000,10000]之间。

输入:[[0,0],[1,0],[2,0]]输出:2解释:两个结果为: [[1,0],[0,0],[2,0]] 和 [[1,0],[2,0],[0,0]]

  【解题思路

  原始思路

  题目的要求是:使得i,j两点的距离等于i,k两点的距离,那么相当于是比较三个点之间距离的,那么开始的思路就是三层遍历,i从0到len,j从i+1到len,k从j+1到len,然后比较三个点的距离,相等则结果数加一。

  显然这样的时间复杂度为O(n^3),对于这道题目,能否用查找表的思路进行解决优化?

  查找表

  之前的查找表问题,大多是通过构建一个查找表,而避免了在查找中再内层嵌套循环,从而降低了时间复杂度。那么可以考虑在这道题中,可以通过查找表进行代替哪两层循环。

  当i,j两点距离等于i,k时,用查找表的思路,等价于:对距离key(i,j或i,k的距离),其值value(个数)为2。

  那么就可以做一个查找表,用来查找相同距离key的个数value是多少。遍历每一个节点i,扫描得到其他点到节点i的距离,在查找表中,对应的键就是距离的值,对应的值就是距离值得个数。

  在拿到对于元素i的距离查找表后,接下来就是排列选择问题了:

  1. 如果当距离为x的值有2个时,那么选择j,k的可能情况有:第一次选择有2种,第二次选择有1种,为2*1;
  2. 如果当距离为x的值有3个时,那么选择j,k的可能的情况有:第一次选择有3种,第二次选择有2种,为3*2;
  3. 那么当距离为x的值有n个时,选择j,k的可能情况有:第一次选择有n种,第二次选择有n-1种。

  距离

  对于距离值的求算,按照欧式距离的方法进行求算的话,容易产生浮点数,可以将根号去掉,用差的平方和来进行比较距离。

  实现代码如下:

class Solution:def numberOfBoomerangs(self, points: List[List[int]]) -> int:res = 0from collections import Counterfor i in points:record = Counter()for j in points:if i != j:record[self.dis(i,j)] += 1for k,v in record.items():res += v*(v-1)return resdef dis(self,point1,point2):return (point1[0]-point2[0]) ** 2 + (point1[1]-point2[1]) ** 2

  优化

  对实现的代码进行优化:

  1. 将for循环遍历改为列表生成式;
  2. 对sum+=的操作,考虑使用sum函数。
  3. 对不同的函数使用闭包的方式内嵌;
class Solution:def numberOfBoomerangs(self, points: List[List[int]]) -> int:from collections import Counterdef f(x1, y1):# 对一个i下j,k的距离值求和d = Counter((x2 - x1) ** 2 + (y2 - y1) ** 2 for x2, y2 in points)return sum(t * (t-1) for t in d.values())# 对每个i的距离进行求和return sum(f(x1, y1) for x1, y1 in points)

  1)解法1(字典)

class Solution:def numberOfBoomerangs(self, points: List[List[int]]) -> int:res=0for i in points:dicts={}for j in points:if i==j:continuedicts[(i[0]-j[0])**2+(i[1]-j[1])**2]=dicts.get((i[0]-j[0])**2+(i[1]-j[1])**2,0)+1for i in dicts.values():res+=i*(i-1)return res


  2)解法2(哈希表)

class Solution:def numberOfBoomerangs(self, points: List[List[int]]) -> int:result = 0for s in points:d = {}for t in points:l = (s[0]-t[0])**2+(s[1]-t[1])**2if l in d:d[l] += 1else:d[l] = 1for k in d:result += d[k]*(d[k]-1)return result

案例8: 149 Max Points on a Line

  【题目描述

  给定一个二维平面,平面上有 n 个点,求最多有多少个点在同一条直线上。

示例 1:输入: [[1,1],[2,2],[3,3]]输出: 3示例 2:输入: [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]输出: 4

  【解题思路

  本道题目的要求是:看有多少个点在同一条直线上,那么判断点是否在一条直线上,其实就等价于判断i,j两点的斜率是否等于i,k两点的斜率。

  回顾上道447题目中的要求:使得i,j两点的距离等于i,k两点的距离,那么在这里,直接考虑使用查找表实现,即查找相同斜率key的个数value是多少。

  在上个问题中,i和j,j和i算是两种不同的情况,但是这道题目中,这是属于相同的两个点, 因此在对遍历每个i,查找与i相同斜率的点时,不能再对结果数res++,而应该取查找表中的最大值。如果有两个斜率相同时,返回的应该是3个点,故返回的是结果数+1。

  查找表实现套路如下:

class Solution:def maxPoints(self,points):res = 0from collections import defaultdictfor i in range(len(points)):record = defaultdict(int)for j in range(len(points)):if i != j:record[self.get_Slope(points,i,j)] += 1for v in record.values():res = max(res, v)return res + 1def get_Slope(self,points,i,j):return (points[i][0] - points[j][0]) / (points[i][1] - points[j][1])

  但是这样会出现一个问题,即斜率的求算中,有时会出现直线为垂直的情况,故需要对返回的结果进行判断,如果分母为0,则返回inf,如下:

def get_Slope(self,points,i,j):if points[i][1] - points[j][1] == 0:return float('Inf')else:return (points[i][0] - points[j][0]) / (points[i][1] - points[j][1])

  再次提交,发现对于空列表的测试用例会判断错误,于是对边界情况进行判断,如果初始长度小于等于1,则直接返回len:

if len(points) <= 1:return len(points)

  再次提交,对于相同元素的测试用例会出现错误,回想刚才的过程,当有相同元素时,题目的要求是算作两个不同的点,但是在程序运行时,会将其考虑为相同的点,return回了inf。但在实际运行时,需要对相同元素的情况单独考虑。

  于是可以设定samepoint值,遍历时判断,如果相同时,same值++,最后取v+same的值作为结果数。

  考虑到如果全是相同值,那么这时dict中的record为空,也要将same值当作结果数返回,代码实现如下:

class Solution:def maxPoints(self,points):if len(points) <= 1:return len(points)res = 0from collections import defaultdictfor i in range(len(points)):record = defaultdict(int)samepoint = 0for j in range(len(points)):if points[i][0] == points[j][0] and points[i][1] == points[j][1]:samepoint += 1else:record[self.get_Slope(points,i,j)] += 1for v in record.values():res = max(res, v+samepoint)res = max(res, samepoint)return resdef get_Slope(self,points,i,j):if points[i][1] - points[j][1] == 0:return float('Inf')else:return (points[i][0] - points[j][0]) / (points[i][1] - points[j][1])

  时间复杂度为O(n^2),空间复杂度为O(n)。

  1)解法1(字典法)

class Solution:def maxPoints(self, points: List[List[int]]) -> int:if len(points) < 2:return len(points)def xielv(point1, point2):if point1[0] == point2[0]:return Noneelse:return (point1[1] - point2[1]) * 1000 / (point1[0] - point2[0]) * 1000s = []for i in points:res = 0record = {}samepoints = 0for j in points:if i[0] == j[0] and i[1] == j[1]:samepoints += 1else:Slope = xielv(i, j)record[Slope] = record.get(Slope, 0) + 1if record:for v in record.values():res = max(res, v)res += samepointselse:res = samepointss.append(res)return max(s)

四、实战2(滑动数组)

案例1: 219 Contains Dupliccate Ⅱ

  【题目描述

  ==给出一个整形数组nums和一个整数k,是否存在索引i和j,使得nums[i]nums[j],且i和J之间的差不超过k。

示例1:输入: nums = [1,2,3,1], k = 3输出: true示例 2:输入: nums = [1,2,3,1,2,3], k = 2输出: false

  【解题思路

  翻译下这个题目:在这个数组中,如果有两个元素索引i和j,它们对应的元素是相等的,且索引j-i是小于等于k,那么就返回True,否则返回False。
因为对于这道题目可以用暴力解法双层循环,即:

for i in range(len(nums)):for j in range(i+1,len(nums)):if i == j:return True
return False

  故这道题目可以考虑使用滑动数组来解决:

  固定滑动数组的长度为K+1,当这个滑动数组内如果能找到两个元素的值相等,就可以保证两个元素的索引的差是小于等于k的。如果当前的滑动数组中没有元素相同,就右移滑动数组的右边界r,同时将左边界l右移。查看r++的元素是否在l右移过后的数组里,如果不在就将其添加数组,在的话返回true表示两元素相等。

  因为滑动数组中的元素是不同的,考虑用set作为数据结构:

class Solution:def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:record = set()for i in range(len(nums)):if nums[i] in record:return Truerecord.add(nums[i])if len(record) == k+1:record.remove(nums[i-k])return False

  时间复杂度为O(n),空间复杂度为O(n)。

  1)解法1(哈希表)

class Solution:def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:numDict = {}for i,j in enumerate(nums):if numDict.get(j) is not None:numDict[j] -= iif abs(numDict[j]) <= k:return Trueelse:numDict[j] = ielse:numDict[j] = ireturn False


  2)解法2(字典法)

class Solution:def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:d = {}for index,n in enumerate(nums):d.setdefault(n,[]).append(index)for poses in d.values():if len(poses)<=1: passfor i in range(len(poses)-1):if poses[i+1]-poses[i] <= k: return Truereturn False

案例2: 220 Contains Dupliccate Ⅲ

  【题目描述

  给定一个整数数组,判断数组中是否有两个不同的索引 i 和 j,使得nums [i] 和nums [j]的差的绝对值最大为 t,并且 i 和 j 之间的差的绝对值最大为 ķ。

示例 1:输入: nums = [1,2,3,1], k = 3, t = 0输出: true示例 2:输入: nums = [1,0,1,1], k = 1, t = 2输出: true示例 3:输入: nums = [1,5,9,1,5,9], k = 2, t = 3输出: false

  【解题思路

  相比较上一个问题,这个问题多了一个限定条件,条件不仅索引差限定k,数值差也限定为了t。

  将索引的差值固定,于是问题和上道一样,同样转化为了固定长度K+1的滑动窗口内,是否存在两个值的差距不超过 t,考虑使用滑动窗口的思想来解决。

  在遍历的过程中,目的是要在“已经出现、但还未滑出滑动窗口”的所有数中查找,是否有一个数与滑动数组中的数的差的绝对值最大为 t。对于差的绝对值最大为t,实际上等价于所要找的这个元素v的范围是在v-t到v+t之间,即查找“滑动数组”中的元素有没有[v-t,v+t]范围内的数存在。

  因为只需证明是否存在即可,这时判断的逻辑是:如果在滑动数组查找比v-t大的最小的元素,如果这个元素小于等于v+t,即可以证明存在[v-t,v+t]

  那么实现过程其实和上题是一致的,只是上题中的判断条件是在查找表中找到和nums[i]相同的元素,而这题中的判断条件是查找比v-t大的最小的元素,判断其小于等于v+t,下面是实现的框架:

class Solution:def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:record = set()for i in range(len(nums)):if 查找的比v-t大的最小的元素 <= v+t:return Truerecord.add(nums[i])if len(record) == k+1:record.remove(nums[i-k])return False

  接下来考虑,如何查找比v-t大的最小的元素呢?

  【注:C++中有lower_bound(v-t)的实现,py需要自己写函数】

  当然首先考虑可以通过O(n)的解法来完成,如下:

def lower_bound(self,array,v):array = list(array)for i in range(len(array)):if array[i] >= v:return ireturn -1

  但是滑动数组作为set,是有序的数组。对于有序的数组,应该第一反应就是二分查找,于是考虑二分查找实现,查找比v-t大的最小的元素:

def lower_bound(self, nums, target):low, high = 0, len(nums)-1while low<high:mid = int((low+high)/2)if nums[mid] < target:low = mid+1else:high = midreturn low if nums[low] >= target else -1

  整体代码实现如下,时间复杂度为O(nlogn),空间复杂度为O(n):

class Solution:def containsNearbyAlmostDuplicate(self, nums, k, t) -> bool:record = set()for i in range(len(nums)):if len(record) != 0:rec = list(record)find_index = self.lower_bound(rec,nums[i]-t)if find_index != -1 and rec[find_index] <= nums[i] + t:return Truerecord.add(nums[i])if len(record) == k + 1:record.remove(nums[i - k])return Falsedef lower_bound(self, nums, target):low, high = 0, len(nums)-1while low<high:mid = int((low+high)/2)if nums[mid] < target:low = mid+1else:high = midreturn low if nums[low] >= target else -1

  当然。。。在和小伙伴一起刷的时候,这样写的O(n^ 2)的结果会比上面要高,讨论的原因应该是上面的步骤存在着大量set和list的转换导致,对于py,仍旧是考虑算法思想实现为主,下面是O(n^2)的代码:

class Solution:def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:if t == 0 and len(nums) == len(set(nums)):return Falsefor i in range(len(nums)):for j in range(1,k+1):if i+j >= len(nums): breakif abs(nums[i+j]-nums[i]) <= t: return Truereturn False

  小套路

  二分查找实现,查找比v-t大的最小的元素:

def lower_bound(self, nums, target):low, high = 0, len(nums)-1while low<high:mid = int((low+high)/2)if nums[mid] < target:low = mid+1else:high = midreturn low if nums[low] >= target else -1

  二分查找实现,查找比v-t大的最小的元素:

def upper_bound(nums, target):low, high = 0, len(nums)-1while low<high:mid=(low+high)/2if nums[mid]<=target:low = mid+1else:#>high = midpos = highif nums[low]>target:pos = lowreturn -1

  1)解法1(桶排序)

class Solution:def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:if t < 0 : return Falsebucket = dict()size = t + 1for i, num in enumerate(nums):idx = num // size  # 由于有特意用于维护索引要求的代码,因此下列三行代码仅考虑数值要求即可if idx in bucket: return Trueif idx - 1 in bucket and abs(num - bucket[idx - 1]) <= t: return Trueif idx + 1 in bucket and abs(num - bucket[idx + 1]) <= t: return Truebucket[idx] = numif i >= k: bucket.pop(nums[i-k]//size)return False

【参考资料】

  1. 编程实践(LeetCode 分类练习)
  2. 力扣(LeetCode)平台

LeetCode 分类练习(四):查找2相关推荐

  1. leetcode分类刷题笔记

    leetcode分类刷题笔记--基于python3 写在前面 1.做题如果实在想不出时间复杂度比较优的解法,可以先写出暴力解法,尝试在其基础上优化 2.排序.双指针.二分等--经常可以优化时间复杂度 ...

  2. Leetcode分类解析:组合算法

    Leetcode分类解析:组合算法 所谓组合算法就是指:在解决一些算法问题时,需要产生输入数据的各种组合.排列.子集.分区等等,然后逐一确认每种是不是我们要的解.从广义上来说,组合算法可以包罗万象,甚 ...

  3. 深度学习 第3章线性分类 实验四 pytorch实现 Logistic回归 上篇

    目录: 第3章 线性分类 3.1 基于Logistic回归的二分类任务 3.1.1 数据集构建 3.1.2 模型构建 1. Logistic函数 2. Logistic回归算子 3.1.3 损失函数 ...

  4. 深度学习 第3章线性分类 实验四 pytorch实现 Softmax回归 鸢尾花分类任务 下篇

    目录: 第3章 线性分类 3.3 实践:基于Softmax回归完成鸢尾花分类任务 3.3.1 数据处理 3.3.1.1 数据集介绍 3.3.1.2 数据清洗 1. 缺失值分析 2. 异常值处理 3.3 ...

  5. 地铁闸门会夹伤人吗_西安地铁率先推出分类垃圾箱 四种类型你会放吗?

    垃圾分类就是新时尚[西安地铁率先推出分类垃圾箱 四种类型你会放吗?]近日,不少上海人已被垃圾分类"逼疯",各类段子刷爆网络."你是什么垃圾?"直击灵魂深处的拷问 ...

  6. # LeetCode集锦(四) - 第13题 Roman To Integer

    LeetCode集锦(四) - 第13题 Roman To Integer 问题 Roman numerals are represented by seven different symbols: ...

  7. 【Groovy】MOP 元对象协议与元编程 ( 方法注入 | 使用 @Category 注解进行方法注入 | 分类注入方法查找优先级 )

    文章目录 一.使用 @Category 注解进行方法注入 二.分类注入方法查找优先级 三.完整代码示例 一.使用 @Category 注解进行方法注入 @Category 注解原型如下 : @Docu ...

  8. 实验四 查找和排序算法实现

    广州大学学生实验报告 开课实验室:计算机科学与工程实验(电子楼418A) 学院 计算机科学与网络工程学院 实验课程 数据结构实验 实验项目 实验四 查找和排序算法实现 一.实验目的: 1.各种排序算法 ...

  9. 文本分类入门(四)训练Part 1

    文本分类入门(四)训练Part 1 训练,顾名思义,就是training(汗,这解释),简单的说就是让计算机从给定的一堆文档中自己学习分类的规则(如果学不对的话,还要,打屁屁?). 开始训练之前,再多 ...

最新文章

  1. Eclipse搭建android环境及Genymotion模拟器安装问题解决方法
  2. php点击按钮创建文件,PHP 文件操作类(创建文件并写入) 生成日_php
  3. ABAP并发编程到底能提高多少性能
  4. java 计时器_Java多线程并发容器之并发倒计时器
  5. 可能是全网最好的MySQL重要知识点/面试题总结
  6. Image-based Lighting approaches and parallax-corrected cubemap
  7. php7 fastcgi安装,安装windows 下 php7+nginx+fastcgi
  8. 关于telnet的安装
  9. python vim插件_有哪些好用到爆的vim插件?
  10. H5页面唤起指定app或跳转到应用市场
  11. yudian温控表a1温度怎么补偿_yudian温控表a1说明书
  12. 基于celeba数据集和pytorch框架实现dcgan的人脸图像生成
  13. 【沃顿商学院学习笔记】公益创业——01开始公益创业Starting a Social Enterprise
  14. 世界杯海信再出圈,三星:“谈不上愉悦”
  15. 如何评估工时和开发计划
  16. struts使用下拉列表框[LabelValueBean的使用]
  17. 【ML】随机森林(Random Forest,RF)
  18. SpringBootJpa — @Id 和 @GeneratedValue 注解详解
  19. Android广播:BroadcastQueue: Background execution not allowed: receiving
  20. Ubuntu下快速关机的方法

热门文章

  1. PCB导入添加Logo图标
  2. 微信小程序-编辑器插件
  3. c语言程序冒号的作用是什么,C语言里面的冒号
  4. 云宏刘建平:细说中小企业如何上云
  5. 天原笔记(2)——气团与锋
  6. try{}里有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会被执行,是在return前还是后
  7. MP3Play项目实战 (1)
  8. Springboot企业内部交流系统9r309计算机毕业设计-课程设计-期末作业-毕设程序代做
  9. Docker学习--Docker镜像的管理操作
  10. 线上实时监测推广效果,App投放渠道数据分析