四数相加II
给你四个整数数组 nums1
、nums2
、nums3
和 nums4
,数组长度都是 n
,请你计算有多少个元组 (i, j, k, l)
能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
此题与两数相加类似,由于并不用输出四元组元素对应的下标,因此不需要用hashmap的value来存储下标值。如果我们用朴素算法求四数相加=0,那么时间复杂度则是O(n^4),为了降低复杂度,我们可以通过将nums1、nums2相加得到数字A的值,然后将nums3与nums4看成数字B的值。将A和B看成两数相加中的nums1和nums2,后续处理方法类似,通过将A存入mapA中,value值保存该key值的次数,然后遍历-nums3[k]-nums4[l](因为满足条件的四元组是nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
),如果(-nums3[k]-nums4[l])在mapA中,则count+=mapA[-nums3[k]-nums4[l]],由于得到A并存入mapA过程我们只遍历了nums1和nums2,时间复杂度为O(n^2),类似得到B时间复杂度也为O(n^2),判断-B是否在mapA中的复杂度为O(1),因此最后时间复杂度为O(n^2)。与朴素算法的O(n^4)相比下降了两个数量级。
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int, int> mapA;
unordered_map<int, int> mapB;
int count = 0;
for (int i = 0; i < nums1.size(); i++)
{
for (int j = 0; j < nums2.size(); j++)
{
if (mapA.find(nums1[i] + nums2[j]) != mapA.end()) mapA[nums1[i] + nums2[j]]++;
else mapA.insert(pair<int,int>(nums1[i] + nums2[j],1));
}
}
for (int i = 0; i < nums3.size(); i++)
{
for (int j = 0; j < nums4.size(); j++)
{
if (mapA.find(-nums3[i] - nums4[j]) != mapA.end()) count+= mapA[-nums3[i] - nums4[j]];
}
}
return count;
}
};
赎金信
给你两个字符串:ransomNote
和 magazine
,判断 ransomNote
能不能由 magazine
里面的字符构成。
如果可以,返回 true
;否则返回 false
。
magazine
中的每个字符只能在 ransomNote
中使用一次。
此题与字母的同构异位词解法几乎相同,唯一不同的地方是同构异位词需要两个字符串的字母频率一致,而赎金信只需magazine的字母频率大于ransomNote。因此遍历完两个数组之后,只需判断hashmap的元素是否存在大于0的,如果存在,说明该字母magazine的频率小于于ransomNote,返回false。
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int hashmap[26] = { 0 };
for (int i = 0; i < ransomNote.size(); i++)
{
hashmap[ransomNote[i] - 'a']++;
}
for (int i = 0; i < magazine.size(); i++)
{
hashmap[magazine[i] - 'a']--;
}
for (int i = 0; i < 26; i++)
{
if (hashmap[i] > 0) return false;
}
return true;
}
};
三数之和
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
此题的难点是需要判断的数字都处于一个数组中,去重比较麻烦,如果用hashmap实现比较复杂。因此通过双指针实现(每一个指针的位置不同),先将数组排序,方便后续操作,使用i从前往后遍历,然后每一次遍历开始,left指针指向i后面一个结点,right指针指向数组末尾。因为目标和值target为0(大于等于0),当i指针指向元素大于0时(由于数组排完序,后续所有元素都大于0),因此三个指针位置的值相加必定不满足条件,可以后续遍历(因为后续遍历i后移,第一个值依然大于0):即if (nums[i] > 0) return result;
除此之外还需要剪枝的遍历是 if (i > 0&& nums[i] == nums[i - 1]) continue;当i所指元素值与上一个元素值相同时,这一次遍历能满足的nums[i] + nums[left] + nums[right] == 0在上一次遍历必定存在,即两次重复,因此可以直接跳过此次遍历。需要注意的是不能写成if (i > 0&& nums[i] == nums[i + 1]) continue;即不能通过下次遍历去跳过本次遍历,因为这种写法会将三元组中相邻元素值相同的组合忽略,即我们就把 三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了,即不能直接忽略,因为此时还并未加入三元组。我们要做的是 不能有重复的三元组,但三元组内的元素是可以重复的!left和right指针的去重可以通过if (i > 0&& nums[i] == nums[i + 1]) continue;实现,因为这两个的去重是在找到三元组后操作的,如果两个元素相同,则已经加入三元组,可以直接忽略。(困扰了一会才想明白)
然后在每一轮剪枝判断结束后,我们通过left和right来不断逼近target,此时的双指针移动与滑动窗口类似,当三数之和大于0时,将right左移缩小和的值:if (nums[i] + nums[left] + nums[right] > 0) right--;当三数之和小于0时,将left右移增大和的值:if(nums[i] + nums[left] + nums[right] < 0) left++;
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); i++)
{
if (nums[i] > 0) return result;
if (i > 0&& nums[i] == nums[i - 1]) continue;
int left = i+1;
int right = nums.size() - 1;
while (left < right)
{
if (nums[i] + nums[left] + nums[right] > 0) right--;
else if(nums[i] + nums[left] + nums[right] < 0) left++;
else
{
result.push_back({ nums[i],nums[left],nums[right] });
while (right > left && nums[left] == nums[left + 1]) left++;
while (right > left && nums[right] == nums[right - 1]) right--;
left++;
right--;
}
}
}
return result;
}
};
四数之和
给你一个由 n
个整数组成的数组 nums
,和一个目标值 target
。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]]
(若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a
、b
、c
和d
互不相同nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
与三数之和的实现方法类似,也是通过双指针实现,由于需要遍历i,j指针,以及双指针因此复杂度为O(n^3)。需要注意的不同地方是剪枝判断,由于目标值target小于0,因此直接判断nums[i]>target是不对的,当target小于0时,nums[i]即使大于target,与后面小于0的数相加可能反而小于target,因此判断条件为if (nums[k] > target&&nums[k]>=0) return result;同理第二层循环条件为if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) break;其他地方与三数之和几乎一样,不再阐述。
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
for (int k = 0; k < nums.size(); k++)
{
if (nums[k] > target&&nums[k]>=0) return result;
if (k > 0 && nums[k] == nums[k - 1]) continue;
for (int i = k+1; i < nums.size(); i++)
{
if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) break;
if (i > k+1 && nums[i] == nums[i - 1]) continue;
int left = i + 1;
int right = nums.size() - 1;
while (left < right)
{
if ((long)nums[k]+nums[i] + nums[left] + nums[right] > target) right--;
else if ((long)nums[k]+nums[i] + nums[left] + nums[right] < target) left++;
else
{
result.push_back({ nums[k],nums[i],nums[left],nums[right] });
while (right > left && nums[left] == nums[left + 1]) left++;
while (right > left && nums[right] == nums[right - 1]) right--;
left++;
right--;
}
}
}
}
return result;
}
};