Day7,3h,补了一下昨天的……
第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+b+c+d=0,先理解成(a+b)=-(c+d),因为题目只是求有多少个符合条件的元组,那我需要一个地方存储a+b的所有值,还有出现的次数;c+d同理,然后遍历两个表,使得量表项中值相加为0,然后出现的次数相乘(组合操作)
其中涉及到通过值寻找次数的操作,所以使用的数据结构应该是键-值,key为相加值,value为次数
class Solution(object):
def fourSumCount(self, nums1, nums2, nums3, nums4):
ab = {}
cd = {}
n = len(nums1)
for i in range(n):
for j in range(n):
numab = nums1[i] + nums2[j]
numcd = nums3[i] + nums4[j]
if numab not in ab.keys():
ab[numab] = 1
else:
ab[numab] += 1
if numcd not in cd.keys():
cd[numcd] = 1
else:
cd[numcd] += 1
count = 0
for key in ab.keys():
if (0-key) in cd.keys():
count += ab[key] * cd[0-key]
return count
代码写了,超时了……用例通过130/132,卡在一个n比较大的用例上
看了一下题解,发现自己还是在犯了前几天一样的错误
非必要,不创建新的变量。不仅是因为变量占用空间,更是因为变量的冗余也会导致计算过程的冗余。
必须要创建ab和cd分别用于存储a+b和c+d的计算和吗?如果只用一个数组存储,遍历计算a+b是存进结果,遍历计算c+d时进行比较和结果的处理,即省去了一个变量的同时,也省去了上文遍历字典ab、cd的过程。
看题解才发现python按key字典可以直接写if num in dict……
class Solution(object):
def fourSumCount(self, nums1, nums2, nums3, nums4):
d = {}
for n1 in nums1:
for n2 in nums2:
n12 = n1 + n2
if n12 in d:
d[n12] += 1
else:
d[n12] = 1
count = 0
for n3 in nums3:
for n4 in nums4:
n34 = n3 + n4
if (0-n34) in d:
count += d[(0-n34)]
return count
383. 赎金信
给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。
(题目说明:为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思。杂志字符串中的每个字符只能在赎金信字符串中使用一次。)
跟前几天那个242.有效的字母异位词是完全一样的……于是进行一个复刻,在讨论242题的时候已经讨论过,由于这道题限定于26个小写字母,相比于使用字典,用数组自建哈希是更明智的,用时更短。
参考代码随想录的示例版本,来学习一下return的思路,不用使用遍历去进行两个数组的对比的方法
class Solution(object):
def canConstruct(self, ransomNote, magazine):
ransom_count = [0] * 26
magazine_count = [0] * 26
for c in ransomNote:
ransom_count[ord(c) - ord('a')] += 1
for c in magazine:
magazine_count[ord(c) - ord('a')] += 1
return all(ransom_count[i] <= magazine_count[i] for i in range(26))
第15题. 三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
自己想
需要在一个数组里满足a+b+c = 0 相当于找a+b = -c,并且题目需要返回的是abc三个值的具体数值(所以前面第一题四数相加的思路或许不行)。思考起来反而有点像前两天看到的两数之和的题目“找出两个数使得和为target”,把target换成题目中的-c即可。
如果使用如四数相加的数据结构,字典,不太现实,因为字典不能通过value找到key,满足不了输出abc三个值的具体数值的要求。
那就使用排序后进行双指针的思路,先排序,双指针分别指头尾,大了移动尾指针,小了移动头指针。可是如何定义结束呢?那么可能还需要第三个指针指向数组中的“潜在的c”。
那么现在这个问题就转化为了,让数组中的所有数都成为一次“二数相加”问题中的tartget,然后移动头尾指针寻得答案。
由于tartget是在数组中被指出的,如果头或尾指针与tartget指针相遇,那么说明这个“潜在的target”是不可能存在的,于是移动target指针进行下一轮计算。
我的思路从未如此清晰,开写!
class Solution(object):
def threeSum(self, nums):
n = len(nums)
nums = sorted(nums)
result = []
for target in range(1,n-1): #target的下标遍历范围为1~(n-2)
left = 0
right = n-1
while left != target and right != target:
if nums[left] + nums[right] + nums[target] > 0:
right -= 1
elif nums[left] + nums[right] + nums[target] < 0:
left += 1
else:
result.append([nums[left],nums[right],nums[target]])
right -=1
left +=1
return result
好的,报错了,看了一下报错的用例是因为结果result中出现了重复的元组。
确实,遍历数组中的元素轮流当作target的话,如果元素有重复,那么结果元组也会有重复,所以在遍历target前先检查一下这个target是否与上一轮相同(因为nums已经被排序过);同理,如果left和right有重复的话也是,所以如果检查到指向的数与上一步相同,应该往下走一步
class Solution(object):
def threeSum(self, nums):
n = len(nums)
nums = sorted(nums)
result = []
for target in range(1,n-1): #target的下标遍历范围为1~(n-2)
left = 0
right = n-1
if nums[target] == nums[target-1]:
left = target -1
while left != target and right != target:
if nums[left] + nums[right] + nums[target] > 0:
right -= 1
elif nums[left] + nums[right] + nums[target] < 0:
left += 1
else:
if left != 0 and nums[left] == nums[left-1]:
left +=1
elif right !=n-1 and nums[right] == nums[right+1]:
right -= 1
else:
result.append([nums[left],nums[right],nums[target]])
right -=1
left +=1
return result
同样地,查看题解对比一下思路。
发现题解给出的思路跟我像的不太一样,我的思路是把数组中的每一个数当作一个target去遍历执行一次“二数之和”,直到遍历完成;题解给出的思路则是,每次都缩小寻找三数的范围,直到范围缩到最小,或者其中最小数大于0,
区别可以看图,很明显,题解(第二种)的解法要更好一些,因为第一种方法中,每一次都要对整个数组长度的内容进行搜索,而题解方法每次搜索的范围都在减少,也便于在最小的数大于0时对其进行剪枝
class Solution(object):
def threeSum(self, nums):
n = len(nums)
nums = sorted(nums)
result = []
for i in range(0,n):
if nums[i] > 0 :
return result
if i>0 and nums[i] == nums[i-1]:
i += 1
continue
left = i + 1
right = n-1
while right > left:
s = nums[i] + nums[left] + nums[right]
if s < 0:
left += 1
elif s > 0:
right -= 1
else:
result.append([nums[i], nums[left], nums[right]])
# 跳过相同的元素以避免重复
while right > left and nums[right] == nums[right - 1]:
right -= 1
while right > left and nums[left] == nums[left + 1]:
left += 1
right -= 1
left += 1
return result
第18题. 四数之和
题意:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
a + b + c + d = target,相当于a + b + c =- (d+ target),也就是在三数之和的外面再嵌套一层对d的循环即可。
class Solution(object):
def fourSum(self, nums, target):
n = len(nums)
nums = sorted(nums)
result = []
for i in range(n):
if nums[i] > target and target > 0:
break #剪枝1
if i>0 and nums[i]==nums[i-1]:
continue #去重1
for j in range(i+1,n):
if nums[i] + nums[j] > target and target > 0:
break #剪枝2
if j>(i+1) and nums[j]==nums[j-1]:
continue #去重2
left = j+1
right = n-1
while right > left:
s = nums[i]+nums[j]+nums[left]+nums[right]
if s >target:
right -= 1
elif s <target:
left += 1
else:
result.append([nums[i],nums[j],nums[left],nums[right]])
while right > left and nums[left]==nums[left+1]:
left += 1
while right > left and nums[right]==nums[right-1]:
right -= 1
left += 1
right -= 1
return result
容易错误的是剪枝1和剪枝2的变化,在三数之和中这里剪枝的依据是target为0,如果最小的数都大于0了,那么再怎么加数字都计算不出结果了。但是在这里target可能是负数,如果最小的数(假设是-4)小于target(假设是-5),(-4)+(-1)仍然可以达到target,所以这个剪枝的前置条件是target要大于0