第七天✊ε=( o`ω′)ノ✊,编程语言:c++
目录
454.四数相加
文档讲解:代码随想录四数相加
视频讲解:手撕四数相加
题目:
初看:第一想法,四个整数数组,可以通过四个循环遍历所有的情况。显然这种方法的时间复杂度为O(n^4),时间复杂度过高。第二个想法是先将两个数组加和,并存储到一个新的数组,然后再加入一个数组进行新的遍历,最后再遍历最后一个数组,总时间复杂度还是O(n^4),且空间复杂度过大,不可取。
学习:可以采取将其中两个数组先进行遍历,并把所有加和情况存储到一个哈希表内,之后再通过两个循环遍历剩下两个数组的所有情况,以此查找可能的组合。能够将时间复杂度降低为O(n^2)。其中由于本题是找到所有的可能性,不用去重,因此哈希表可以采取unordered_map结构,key为加和值,value为key出现的次数。之后在每次找到合适的值时,count += value,即可得到所有的可能组合数。
代码:
//时间复杂度O(n^2)
//空间复杂度O(n^2)
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int,int> sum; //key表示nums1和nums2的元素之和,value表示相同的和出现的次数
for (int a : nums1) {
for (int b : nums2) {
sum[a + b]++; //unoredered_map重载了[]运算符,即使没有以key为键的键值对,也会使用该键向当前容器中插入一个新键值对
}
}
int count = 0; //统计最终的个数
for (int c : nums3) {
for (int d : nums4) {
auto it = sum.find(0-(c + d)); //返回一个指向目标值的迭代器
if(it != sum.end()) {
count += it->second;
}
}
}
return count;
}
};
收获:
- 本题是四个独立的数组,因此可以采用哈希表的方式先存储两个数组的和。但如果是一个数组里面找四个元素,可能就不适合使用哈希表来进行求解。
- 本题使用哈希表的方式降低时间复杂度,属于是空间换时间的方式,这种思想很重要,需要多学习。
383.赎金信
文档讲解: 代码随想录赎金信
初看:本题和242.有效的字母异位词有所相同,后者是找到字母种类个数完全一样的,本题则是magazine包含ransomNote中的所有字母,但本质都是查找自身的元素在不在对方那。出现查找元素的情况就可以尝试使用哈希法来进行求解。
代码:
1.先创建一个容量为26的数组,作为一个哈希表。(因为字符串都由小写字母组成,连续且数量一定,可通过与'a'相减,映射到数组下标内)
2.分别将两个字符串加入到数组内,其中因为是求magazine是否包含ransomNote的全部字母,所以对magazine循环,数组内元素++,对ransomNote循环,数组内元素--,最后如果数组内存在负数,则说明magazine内缺少足够的字母,返回false。
//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
//由小写字母构成,则可以通过设计一个26容量的数组构造哈希表
int sum[26] {0};
if (ransomNote.size() > magazine.size()) {
return false;
}
for (char it : magazine) {
sum[it - 'a']++;
}
for (char it : ransomNote) {
sum[it - 'a']--;
}
for (int i = 0; i < 26; i++) {
if(sum[i] < 0) return false;
}
return true;
}
};
15.三数之和
文档讲解:代码随想录三数之和
视频讲解:手撕三数之和
题目:
思路:
- 哈希法:使用两层for循环确定a和b的数值,之后使用哈希法来确定0-(a+b)是否在数组里出现过。但要注意本题不可以包含重复的三元组,因此还需要进行去重。
- 双指针法:本题使用哈希法并不方便进行去重,同时还需要创建一个哈希表。采用双指针法能够更高效的一些。
双指针法代码:
//时间复杂度:O(n^2)
//空间复杂度:O(1)
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
//先对数组进行排序,使用双针法,也便于对a进行去重
sort(nums.begin(), nums.end());
int n = nums.size();
for(int i = 0; i < n - 2; i++) {
if (nums[i] > 0) return ans; //排序后如果最小值都大于0,则一定不能组成和为0的三元组
if (i > 0 && nums[i] == nums[i-1]) { //对a进行去重,这里也可以使用while,对i进行++,但要主要防止i出界
continue;
}
//创建左右指针
int left = i + 1;
int right = nums.size() - 1;
while(left < right) {
if (nums[i] + nums[left] + nums[right] > 0) { //和大于0,值应该减小
right--;
}
else if (nums[i] + nums[left] + nums[right] < 0) { //和小于0,值应该增大
left++;
}
else {
ans.push_back({nums[i], nums[left], nums[right]});
while(left < right && nums[left] == nums[left+1]) left++; //对j进行去重
while(left < right && nums[right] == nums[right-1]) right--; //对k进行去重
left++; //进行新一轮循环
right--;
}
}
}
return ans;
}
};
哈希法代码:
//时间复杂度O(n^2)
//空间复杂度O(n)
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
// 找出a + b + c = 0
// a = nums[i], b = nums[j], c = -(a + b)
for (int i = 0; i < nums.size(); i++) {
// 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
if (nums[i] > 0) {
break;
}
if (i > 0 && nums[i] == nums[i - 1]) { //三元组元素a去重
continue;
}
unordered_set<int> set;
for (int j = i + 1; j < nums.size(); j++) {
if (j > i + 2
&& nums[j] == nums[j-1]
&& nums[j-1] == nums[j-2]) { // 三元组元素b去重
continue;
}
int c = 0 - (nums[i] + nums[j]);
if (set.find(c) != set.end()) {
result.push_back({nums[i], nums[j], c});
set.erase(c);// 三元组元素c去重
} else {
set.insert(nums[j]);
}
}
}
return result;
}
};
18.四数之和
文档讲解:代码随想录四数之和
视频讲解:手撕四数之和
题目:
思路:和三数之和基本是一个思路,多增加一层循环,总体的时间复杂度为O(n^3)。
代码:
//时间复杂度O(n^3)
//空间复杂度O(1)
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/4) break;
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[i] > 0) break;
if (i > k + 1 && nums[i] == nums[i-1]) continue; //是大于k+1不是大于1
int left = i + 1;
int right = nums.size() - 1;
while (right > left) {
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(vector<int>{nums[k], nums[i], nums[left], nums[right]});
while (right > left && nums[right] == nums[right-1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
right--;
left++;
}
}
}
}
return result;
}
};
哈希表总结
文档讲解: 代码随想录哈希表总结
哈希表常见的三种哈希结构:数组、set(集合)、map(映射)。
1.数组作为哈希表:当所需要开辟的空间大小较小,且一定,则可以采用数组作为哈希表,例如242.有效的字母异位词和383.赎金信,都是小写字母,开辟长度为26的数组即可。
2.set作为哈希表: 当没有限制数值的大小,就无法创建一个数组来做哈希表。此时就需要使用set来作为哈希表。c++中提供了如下三种可用的数据结构:set、multiset、unordered_set。其中set和multiset底层实现是红黑树,unordered_set的底层实现是哈希。
3.map作为哈希表:
set和数组来做哈希法还是存在一些局限:数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费;set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x和y的下标。所以set也不能用。
此时可能就需要使用map来作为哈希表,map是一种<key,value>的结构,本题可以用key保存数值,用value在保存数值所在的下标。所以使用map最为合适。