第454题.四数相加II
给定四个包含整数的数组列表 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 。所有整数的范围在 -2^28 到 2^28 - 1 之间,最终结果不会超过 2^31 - 1 。
例如:
输入:
- A = [ 1, 2]
- B = [-2,-1]
- C = [-1, 2]
- D = [ 0, 2]
输出:
2
解释:
两个元组如下:
- (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
- (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
思路:老实说,一开始基本没有什么思路,直接看的文字解析。看到记录数组1和2的加和后豁然开朗,有时候刷题的时候很想避免复杂度为n²的方法,有时候反而影响了自己的思路。记录了1和2的加和后,接下来只需要遍历3和4的加和,当加和为所记录的相反数时即符合题目条件,在这里需要注意之前的加和可能重复出现,所以要用dict来记录出现过的【次数】,而不是【是否出现过】,然后加和即完成,返回count。
代码如下:
class Solution:
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
hashmap = dict()
for i in nums1:
for j in nums2:
hashmap[i+j] = hashmap.get(i+j, 0) + 1 # 记录数组1和2的加和,且记录相应个数
count = 0
for i in nums3:
for j in nums4:
if -i-j in hashmap: # 要加和为0,也就是数组3和4加和等于前面记录的相反数
count += hashmap[-i-j]
return count
其他方法,使用defaultdict:
from collections import defaultdict
class Solution:
def fourSumCount(self, nums1: list, nums2: list, nums3: list, nums4: list) -> int:
rec, cnt = defaultdict(lambda : 0), 0
for i in nums1:
for j in nums2:
rec[i+j] += 1
for i in nums3:
for j in nums4:
cnt += rec.get(-(i+j), 0)
return cnt
由于默认value值即为0,所以写法上更具有可读性。
383. 赎金信
给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。
(题目说明:为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思。杂志字符串中的每个字符只能在赎金信字符串中使用一次。)
注意:
你可以假设两个字符串均只含有小写字母。
canConstruct("a", "b") -> false
canConstruct("aa", "ab") -> false
canConstruct("aa", "aab") -> true
给人找回自信的一题,难度较小。思路一开始就很清晰,ransom的字符只有从magazine中获得才满足条件,所以应该最开始将magazine中的字符记录在字典中同时记录出现过的次数。然后开始遍历ransom的字符,当ransom 的所有字符都在字典中记录过并且在ransom中出现的次数小于等于字典中的次数时,满足条件返回True。若字典中不存在该字符或者出现次数小于ransom中的次数,返回False。
代码如下:
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
hashmap = dict()
for i in magazine:
hashmap[i] = hashmap.get(i, 0) + 1
for j in ransomNote:
if hashmap.get(j, 0) == 0:
return False
else:
hashmap[j] -= 1
return True
但看完解析后发现,其实好像还是用数组好点,给出代码随想录的解释:
一些同学可能想,用数组干啥,都用map完事了,其实在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!数据量大的话就能体现出来差别了。 所以数组更加简单直接有效!
不过难度一样很简单实现,代码不贴出来了,补充一些其他方法:
Counter代码:
from collections import Counter
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
return not Counter(ransomNote) - Counter(magazine)
使用count(简单易懂):
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
for char in ransomNote:
if char in magazine and ransomNote.count(char) <= magazine.count(char):
continue
else:
return False
return True
两行代码解决的事情,所以还是要积累经验,多用用python里的标准类。。
第15题. 三数之和
思路:一开始的思路肯定是a+b记录在哈希表,然后遍历c看看是否存在-(a+b),但需要去重,妥妥的n²中非常低效的写法。(而且前面的题目解析中已经被剧透说这两题不适合哈希法,果断放弃看解析了。。),看了解析后再一次认识到自己的基础不牢,复习复健的路还很长。。
解析给的是双指针法,对自己很有启发。首先将数组进行排序,然后定义三个指针,分别是进行遍历的i指针,其次是未遍历的部分的头和尾left和right指针。
计算过程是,将三个指针指向的数值进行加和,判断是否等于0,如果等于可以直接记录;否则如果大于0,说明加和数字大了,由于已经排过序,需要让加和变小的话就需要让right指针向左移,同理,如果小于0则需要让left指针向右移,直到left和right相遇。
另外还需要考虑去重的情况,由于i在进行遍历的时候,下一次i遍历到的可能与上一次相等,这样的话会导致三元组的第一个元素和上一次的情况重复,得出的结果会是一样的,需要注意,这里的判断条件应该是i和i-1进行比较,而不是i+1和i比较,否则i和后面的元素比较去重的话,得出的结果是不会出现相同元素的,因为在排序后的数组中,这样i一定是从重复元素之中的最后一个开始的。
贴一段解析,这一部分个人认为理解和代码一样重要,需要重点掌握这里的思考过程:
去重逻辑的思考
#a的去重
说到去重,其实主要考虑三个数的去重。 a, b ,c, 对应的就是 nums[i],nums[left],nums[right]
a 如果重复了怎么办,a是nums里遍历的元素,那么应该直接跳过去。
但这里有一个问题,是判断 nums[i] 与 nums[i + 1]是否相同,还是判断 nums[i] 与 nums[i-1] 是否相同。
有同学可能想,这不都一样吗。
其实不一样!
都是和 nums[i]进行比较,是比较它的前一个,还是比较它的后一个。
如果我们的写法是 这样:
if (nums[i] == nums[i + 1]) { // 去重操作
continue;
}
那我们就把 三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。
我们要做的是 不能有重复的三元组,但三元组内的元素是可以重复的!
所以这里是有两个重复的维度。
那么应该这么写:
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
这么写就是当前使用 nums[i],我们判断前一位是不是一样的元素,在看 {-1, -1 ,2} 这组数据,当遍历到 第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到 结果集里。
这是一个非常细节的思考过程。
#b与c的去重
很多同学写本题的时候,去重的逻辑多加了 对right 和left 的去重:(代码中注释部分)
while (right > left) {
if (nums[i] + nums[left] + nums[right] > 0) {
right--;
// 去重 right
while (left < right && nums[right] == nums[right + 1]) right--;
} else if (nums[i] + nums[left] + nums[right] < 0) {
left++;
// 去重 left
while (left < right && nums[left] == nums[left - 1]) left++;
} else {
}
}
但细想一下,这种去重其实对提升程序运行效率是没有帮助的。
拿right去重为例,即使不加这个去重逻辑,依然根据 while (right > left) 和 if (nums[i] + nums[left] + nums[right] > 0) 去完成right-- 的操作。
多加了 while (left < right && nums[right] == nums[right + 1]) right--; 这一行代码,其实就是把 需要执行的逻辑提前执行了,但并没有减少 判断的逻辑。
最直白的思考过程,就是right还是一个数一个数的减下去的,所以在哪里减的都是一样的。
所以这种去重 是可以不加的。 仅仅是把去重的逻辑提前了而已。
理解后自己写了代码(更多有点像默写,有两行代码没考虑到卡了一会儿):
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
result = []
length = len(nums)
nums.sort()
for i in range(length-2):
if nums[i] > 0:
return result
if i>0 and nums[i] == nums[i-1]:
continue
left = i+1
right = length-1
while left < right:
if nums[i] + nums[left] + nums[right] == 0:
result.append([nums[i], nums[left], nums[right]])
while left+1<right and nums[left+1]==nums[left]:
left += 1
while right-1>left and nums[right-1]==nums[right]:
right -= 1
left += 1
right -= 1 # 这里两个没写,将会一直卡在满足条件加到数组中这个操作循环中,第一次在这里卡了bug
elif nums[i] + nums[left] + nums[right] < 0:
left += 1
elif nums[i] + nums[left] + nums[right] > 0:
right -= 1
return result
其他方法,使用字典:
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
result = []
nums.sort()
# 找出a + b + c = 0
# a = nums[i], b = nums[j], c = -(a + b)
for i in range(len(nums)):
# 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
if nums[i] > 0:
break
if i > 0 and nums[i] == nums[i - 1]: #三元组元素a去重
continue
d = {}
for j in range(i + 1, len(nums)):
if j > i + 2 and nums[j] == nums[j-1] == nums[j-2]: # 三元组元素b去重
continue
c = 0 - (nums[i] + nums[j])
if c in d:
result.append([nums[i], nums[j], c])
d.pop(c) # 三元组元素c去重
else:
d[nums[j]] = j
return result
第18题. 四数之和
思路:一下想到了三数之和,脑子懒得动了直接套多一层循环,同样的方法,先排序,然后循环i和j再设置left和right指针,当四数加和大了则移动right指针,小了则移动left指针,如果相等则加入结果列表,同时进行去重移动left和right指针。对i和j去重的时候也需要考虑是和前一个数进行对比。
代码实现如下:
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
result = []
nums.sort()
length = len(nums)
for i in range(length-3):
if i>0 and nums[i]==nums[i-1]:
continue
j = i+1
while j < length-2:
if j>i+1 and nums[j]==nums[j-1]:
j += 1
continue
left = j+1
right = length-1
while left < right:
if nums[i]+nums[j]+nums[left]+nums[right] < target:
left += 1
elif nums[i]+nums[j]+nums[left]+nums[right] > target:
right -= 1
else:
result.append([nums[i], nums[j], nums[left], nums[right]])
left += 1
right -= 1
while left<right and nums[left]==nums[left-1]:
left += 1
while left<right and nums[right]==nums[right+1]:
right -= 1
j += 1
return result
其他方法:使用字典
class Solution(object):
def fourSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[List[int]]
"""
# 创建一个字典来存储输入列表中每个数字的频率
freq = {}
for num in nums:
freq[num] = freq.get(num, 0) + 1
# 创建一个集合来存储最终答案,并遍历4个数字的所有唯一组合
ans = set()
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
for k in range(j + 1, len(nums)):
val = target - (nums[i] + nums[j] + nums[k])
if val in freq:
# 确保没有重复
count = (nums[i] == val) + (nums[j] == val) + (nums[k] == val)
if freq[val] > count:
ans.add(tuple(sorted([nums[i], nums[j], nums[k], val])))
return [list(x) for x in ans]
关于这个方法,一开始不太理解怎么对数组去重的,所以查了一下,其实是通过元组来进行去重的。
引用AI:
代码中的去重是通过将四元组转换为元组的方式实现的。在Python中,元组是不可变且可哈希的,这意味着它们可以作为字典的键或集合的元素。由于集合不允许重复的元素,所以当一个元组被添加到集合中时,如果已经存在一个相同的元组,它不会被再次添加。
在这段代码中,每次找到一个可能的四元组时,都会通过 tuple(sorted([...])) 将其转换为一个排序后的元组。这样做有两个目的:
- 去重:由于集合不允许重复的元素,排序后的元组确保了即使有多个相同的四元组,也只会被添加一次到集合中。
- 保持顺序:通过排序,可以确保四元组中的元素顺序一致,这样即使在不同的迭代中找到了相同的四个数字,只要它们的顺序相同,就会被认为是同一个四元组。
例如,如果数组中有 [1, 0, -1, 0, -2, 2],目标和为 0,那么 [-2, -1, 1, 2] 和 [-1, -2, 1, 2] 都满足条件,但由于它们被转换为排序后的元组 (-2, -1, 1, 2),它们在集合中被视为相同的元素,因此不会重复添加。
因此,代码通过使用集合和元组来确保结果中没有重复的四元组。
标签:四数,15,nums,int,List,随想录,元组,right,left From: https://blog.csdn.net/weixin_47681529/article/details/142314431