454. 四数相加 II
[题目链接] (leetcode.cn/problems/4s…)
暴力方法 时间复杂度 O(n^4) 空间复杂度 O(1)
哈希方法 时间复杂度 O(n^2) 空间复杂度 O(n^2)
要求: 给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
0 <= i, j, k, l < nnums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
思路
本题适用于map哈希表,首先遍历A、B两个数组,将所有可能的和 出现次数 存放在map中,再遍历C、D两个数组,查找map中是否存在0—(C+D),存在value个,count+value。
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @param {number[]} nums3
* @param {number[]} nums4
* @return {number}
*/
var fourSumCount = function(nums1, nums2, nums3, nums4) {
//先遍历A、B两个数组,将所有可能的和 出现次数 存放在map中
//for of 遍历key for in 遍历value
let map = new Map()
let count = 0
for(let n1 of nums1){
for(let n2 of nums2){
map.set(n1+n2, map.get(n1+n2)+1 || 1)
}
}
//再遍历C、D两个数组时,查找map中是否存在0—(C+D),存在几个,count+几
for(let n3 of nums3){
for(let n4 of nums4){
if(map.has(0-(n3+n4))){
count += map.get(0-(n3+n4))
}
}
}
return count
};
383. 赎金信
要求:给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。
思路
本题与242. 有效的字母异位词类似,提议为:看字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a,且字符串a中字符只能使用一次,不能重复。
/**
* @param {string} ransomNote
* @param {string} magazine
* @return {boolean}
*/
var canConstruct = function(ransomNote, magazine) {
let map = new Array(26).fill(0)
let base = 'a'.charCodeAt()
for(let s of magazine){
map[s.charCodeAt()-base]++
}
for(let s of ransomNote){
if(!map[s.charCodeAt()-base]) return false
map[s.charCodeAt()-base]--
}
return true
};
15. 三数之和
要求:给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
思路
1.双指针法:首先将数组nums排序,便于去重;然后一层for循环i由0开始遍历,然后再定义left=i+1、right=nums.length-1开始从头尾往中间遍历,类似于二分查找方法,若nums[i]+nums[left]+nums[right]>0,则说明三数之和大了,left应该往右移动,若三叔之和<0,则right应该向左移动。
本题难点在于去重,需要考虑三个数的去重,即nums[i],nums[left],nums[right]:
(1)nums[i]重复,由于i在for循环中,所以直接跳过这个循环就行,但究竟是判断 nums[i] 与 nums[i + 1]是否相同,还是判断 nums[i] 与 nums[i-1] 是否相同?如果判断nums[i] == nums[i+1] ,那么就会把[-1, -1, 2]这种情况跳过,所以要判断nums[i] == nums[i-1] 。
(2)而nums[left]、nums[right]的去重则需要在三数之和=0的时候,在去重完成后需要left++、right--,开始下一次循环。
时间复杂度 O(n^2) 空间复杂度 O(1)
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function(nums) {
nums.sort((a,b)=>(a-b))
let res= []
for(let i=0; i<nums.length; i++){
if(nums[i]>0){
return res
}
//对a去重
if(i>0 && nums[i]==nums[i-1])continue
let left=i+1, right=nums.length-1
while(left<right){
if(nums[i]+nums[left]+nums[right] > 0){
right--
}else if(nums[i]+nums[left]+nums[right] < 0){
left++
}else{
res.push([nums[i], nums[left], nums[right]])
//对b、c去重
while(left<right && nums[left] == nums[left+1]) left++
while(left<right && nums[right] == nums[right-1]) right--
left++
right--
}
}
}
return res
};
2.哈希解法:两层for循环就可以确定 a 和 b 的数值了,可以使用哈希法来确定 0-(a+b) 是否在 数组里出现过,其实这个思路是正确的,但是我们有一个非常棘手的问题,就是题目中说的不可以包含重复的三元组。
把符合条件的三元组放进vector中,然后再去重,这样是非常费时的,很容易超时,也是这道题目通过率如此之低的根源所在。
时间复杂度 O(n^2) 空间复杂度 O(1)
var threeSum = function(nums) {
let res = []
nums.sort()
for(let i=0; i<nums.length; i++){
if(nums[i]>0) break
if(i>0 && nums[i] == nums[i-1]){
continue
}
let set = new Set()
for(let j=i+1;j<nums.length;j++){
//b去重
if(j>i+2 && nums[j] == nums[j-1] && nums[j-1] == nums[j-2]){
continue
}
let c= 0- (nums[i]+nums[j])
if(set.has(c)){
res.push([nums[i], nums[j], c])
set.delete(c) //c去重
}else{
set.add(nums[j])
}
}
}
return res
18. 四数之和
要求:给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < na、b、c和d互不相同nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
思路
本题整体思路跟15. 三数之和类似,但是在剪枝的时候有一些细节需要注意,不要判断nums[k] > target 就返回了,三数之和 可以通过 nums[i] > 0 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。逻辑需要变成nums[i] > target && (nums[i] >=0 || target >= 0)。
时间复杂度 O(n^3) 空间复杂度 O(1)
/**
* @param {number[]} nums
* @param {number} target
* @return {number[][]}
*/
var fourSum = function(nums, target) {
let res = []
nums.sort((a,b)=> (a-b))
for(let i=0; i<nums.length; i++){
//剪枝
if(nums[i]>target && nums[i]>=0 ) break
if(i>0 && nums[i] == nums[i-1]) continue
for(let j=i+1; j<nums.length; j++){
//剪枝
if(nums[i]+nums[j] > target && nums[i] +nums[j] >= 0) break
if(j>i+1 && nums[j] == nums[j-1]) continue
let left = j+1, right = nums.length-1
while(left<right){
if(nums[i]+nums[j]+nums[left]+nums[right] > target){
right--
}else if(nums[i]+nums[j]+nums[left]+nums[right] < target){
left++
}else{
res.push([nums[i], nums[j], nums[left], nums[right]])
while(left<right && nums[left] == nums[left+1]) left++
while(left<right && nums[right] == nums[right-1]) right--
left++
right--
}
}
}
}
return res
};