242. 有效的字母异位词
算法思想:哈希,排序
哈希思想:重点就是迅速查找,通过key和值的迅速匹配,所以当题目是与 “唯一性” 有关时可用
用哈希表维护字母出现的次数
方法一:哈希
我的做法:用了map
class Solution {
public:
bool isAnagram(string s, string t) {
if(s.size()!=t.size()) //如果没有这句会"a","a,b"错误
return false;
map<char,int> a;
for(int i=0;i<s.size();i++){
a[s[i]]++;
}
for(int i=0;i<t.size();i++){
if(a.count(t[i])==1){
a[t[i]]--;
}
}
for(int i=0;i<a.size();i++)
{
if(a[i]!=0)
return false;
}
return true;
}
};
官方
使用的是数组来表示哈希,一个26个元素的数组,将每个字母-‘a’,对应到数组的位置下标。
class Solution {
public:
bool isAnagram(string s, string t) {
vector<int> a(26);
for(int i=0;i<s.size();i++){
a[s[i]-'a']++;
}
for(int i=0;i<t.size();i++){
a[t[i]-'a']--;
}
for(int i=0;i<a.size();i++)
{
if(a[i]!=0)
return false;
}
return true;
}
};
方法二:排序
题目等价于排序后看两个字符串是否相等.
sort对string排序: sort( str.begin(), str.end() );
class Solution {
public:
bool isAnagram(string s, string t) {
sort(s.begin(),s.end());
sort(t.begin(),t.end());
if(s==t)
return true;
return false;
}
};
383. 赎金信
这里用数组比用undered_map简单多了
数组直接天然顺序0-25 就是a-z,直接一对一比较,map还要找key再比较
结论:string类型查找时,只有小写字母,使用数组当哈希很方便!
注意:
该观点错误:【map不能重复插入,计算次数,每次还要判断是不是第一次插入】map遇到key值相同的字符时,可以直接mp[key]++, 计算次数。,而insert不会进行覆盖,每次需要判断是否已经存在,如果存在,则[key]++,不存在插入{key,0},见下方的引出问题:
引出问题:map出现相同key值时,会怎么处理?
当使用insert进行相同key值插入时,不会进行覆盖,原理:insert源码是先进行遍历了,如果出现相同key值,就直接返回,如果没有则插入;
当使用[ ],是重载的,所以可以进行相同key值的覆盖。
哈希数组:
vector<int>r(26);
vector<int>m(26);
for(int i=0;i<magazine.size();i++){
m[magazine[i]-'a']++;
}
for(int j=0;j<ransomNote.size();j++){
r[ransomNote[j]-'a']++;
}
for(int i=0;i<26;i++){
if(m[i]<r[i])
{
return false;
}
}
return true;
哈希map:
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
//方法一:先排序后遍历
//方法二:magazine存储到哈希,遍历ransomNote,再在magazine中是否全可以查找得到
// if(magazine.size()<ransomNote.size())
// return false;
// sort(ransomNote.begin(),ransomNote.end());
// sort(magazine.begin(),magazine.end());
// for(int i=0;i<magazine.size();i++){
// if(magazine[i]==ransomNote[i])
// return false;
// }
// return true;
unordered_map<char,int>mp;
unordered_map<char,int>mp1;
for(int i=0;i<magazine.size();i++)
{
//key唯一
mp[magazine[i]]++;
}
for(int i=0;i<ransomNote.size();i++){
mp1[ransomNote[i]]++;
}
for(auto it: mp1){
if(mp.count(it.first)==0)
return false;
if(it.second>mp[it.first]){
return false;
}
}
return true;
//不是m中的个数只要>=0即可,不是不等于0,因为m包含r,m中的次数可能比r多,只要m次数>=r可以
}
};
349. 两个数组的交集
原题目链接
算法思路:排序+双指针
我的做法:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
sort(nums1.begin(),nums1.end());
sort(nums2.begin(),nums2.end());
int len1=nums1.size();
int len2=nums2.size();
vector<int> v;
set<int> s;
set<int>:: iterator it;
// int minlen=len1<len2?len1:len2;
int p=0,q=0;
while(p<len1&&q<len2){
if(nums1[p]==nums2[q]){
// v.push_back(nums1[p++]);
s.insert(nums1[p++]);
q++;
}else if(nums1[p]<nums2[q])
{
p++;
}
else{
q++;
}
}
for(it=s.begin();it!=s.end();it++){
v.push_back(*it);
}
return v;
}
};
冗余点:
唯一性判断可以通过有序这个特性,不用再设置set,因为每次加入到vector里的元素也是有序的。
官方:
因为是有序的,通过比较插入vector元素的值和v.back()是否相等来确定插入元素的唯一性。注意当size()==0时,即没有元素,调用v.back()会报错!
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
sort(nums1.begin(), nums1.end());
sort(nums2.begin(), nums2.end());
int length1 = nums1.size(), length2 = nums2.size();
int index1 = 0, index2 = 0;
vector<int> intersection;
while (index1 < length1 && index2 < length2) {
int num1 = nums1[index1], num2 = nums2[index2];
if (num1 == num2) {
// 保证加入元素的唯一性
if (!intersection.size() || num1 != intersection.back()) {
intersection.push_back(num1);
}
index1++;
index2++;
} else if (num1 < num2) {
index1++;
} else {
index2++;
}
}
return intersection;
}
};
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/intersection-of-two-arrays/solution/liang-ge-shu-zu-de-jiao-ji-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
时间复杂度:O(mlogm+nlogn)
其中 m 和 n分别是两个数组的长度。对两个数组排序的时间复杂度分别是 O(m log m)和 O(nlog n),双指针寻找交集元素的时间复杂度是 O(m+n),因此总时间复杂度是O(mlogm+nlogn)。
空间复杂度:O(log m+log n)
其中 m 和 n 分别是两个数组的长度。空间复杂度主要取决于排序使用的额外空间。
随想录:
使用unordered_set
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_set; // 存放结果
unordered_set<int> nums_set(nums1.begin(), nums1.end());
for (int num : nums2) {
// 发现nums2的元素 在nums_set里又出现过
if (nums_set.find(num) != nums_set.end()) {
result_set.insert(num);
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};
注意:
但是要注意,使用数组来做哈希的题目,是因为题目都限制了数值的大小。而这道题目没有限制数值的大小,就无法使用数组来做哈希表了。
而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。
202. 快乐数
原题链接
算法思路: 重点是找到循环出口!!看清楚快乐数和不是快乐数的返回条件是什么!
快乐数:最后结果为1
不快乐数:无限循环且值不可能为1
所以查找是否重复出现使用哈希,undered_set
class Solution {
public:
bool isHappy(int n) {
unordered_set<int> s;
int sum=0;
while(1){
sum+=pow(n%10,2);
n=n/10;
if(n==0) //如果没有n==0,sum还没计算完,即计算到中间某一步,就开始查找了
{
if(sum==1)
return true;
if(s.count(sum)==0) {
s.insert(sum);
n=sum;
sum=0;
}
else
return false;
}
}
}
};
bug点
- 求一个数各个位的值,语句顺序影响结果!
1. 两数之和
原题链接
思路:暴力,哈希
哈希:
哈希可以用于查找是否出现,通过等式a+b=target来查找,此时,b=target-a;
使用unordered_set
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
map<int,int> mp;
// for(int i=0;i<nums.size();i++)
// {
// mp[nums[i]]=i;
// }
for(int i=0;i<nums.size();i++){
auto it=mp.find(target-nums[i]);
if(it!=mp.end())
{
return {it->second,i};
}
mp[nums[i]]=i;
}
return {};
}
};
bug点:
- 之前的思路先将数组存储到undered_set里,再遍历,在哈希表中查找,看是否出现了target-nums[i],这个时候,有两个错误点,一是map第一次遍历存储时,只能插入不相同的元素,如[3,3], map只能插入一个3;二是插入进去后,下一次找target-a时,如果是6-3=3这种,就会返回自己的坐标,如【3,2,4】 6
- 记住函数最后一定要返回值,否则报错
随想录
用的unordered_map
其他试错:
不能用双指针,首先,要返回的是原来的坐标,如果先排序再用两数之和,那么返回的left和right就是排序后的坐标。
如果不排序来求那么不能保证可以遍历到所有情况。
错误的双指针如下:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int left=0,n=nums.size();
int right=n-1;
sort(nums.begin(),nums.end());
while(left<=right){
int sum=nums[left]+nums[right];
if(sum==target)
{
return {left,right};
}else if(sum>target)
right--;
else left++;
}
return {0,0}; //记住必须要有返回值
}
};
454. 四数相加 II
算法思路:
由等式a+b=-(c+d),因为题目只要返回个数,所以就不用记录下标。将a+b的和存储起来,再遍历第三,四个数组看三+四的相反数是否存在和的数组中。
本来想着使用unordered_set, 但是可能和会重复,所以使用unordered_map,key为和,值为出现该和的次数。
遍历后两个数组时,就将sum加起来。
我的代码:
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int,int>mp;
int sum=0;
for(int i=0;i<nums1.size();i++){
for(int j=0;j<nums2.size();j++)
{
// mp.insert(pair<int,int>(nums1[i]+nums2[j]));
mp[nums1[i]+nums2[j]]++;
}
}
for(int i=0;i<nums1.size();i++)
for(int j=0;j<nums1.size();j++){
if(mp.count(-(nums3[i]+nums4[j])))
sum+=mp[-(nums3[i]+nums4[j])];
}
return sum;
}
};
15 . 三数之和
本题目用哈希麻烦
方法一:排序+双指针
二三重循环可以并行!所以出现了双指针
当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,将枚举的时间复杂度从 O(N^2)减少至 O(N),如本题和为固定值0,a,和值不变,b增大,c减小
本题目思路:
使用排序+双指针的大框架,加一些剪枝操作,如第一个数字如果是大于0 的,那么后面不可能三数之和为0,直接return 。
难点是去重操作,要答案不同,所以每当遍历到一个新的数字,都必须与前一个比较,看是否相同,三个数字都要去重。最容易出现bug的是出现死循环,当等于题目条件时,必须left,right都移动。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
//与两数之和不同的是,两数之和的和是固定的target,本题目的c是不固定的,在数组里内
//由此,可以想到,遍历该数组,每次都固定和不变,剩下的两个用双指针,找到后加入vector,这一点与两数之和的不同,因为不用输出下标,所以可以用双指针
int n=nums.size();
unordered_set<int> s;
vector<vector<int>>v;
int sum=0;
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size();i++){
if(nums[i]>0) //这里直接return ,否则不通过 1 1 -2
return v;
if(i>0&&nums[i]==nums[i-1])
continue;
int left=i+1,right=n-1;
while(left<right){
if(nums[left]+nums[right]+nums[i]<0){
//去重
left++;
}else if(nums[left]+nums[right]+nums[i]>0){
right--;
}else{
v.push_back({nums[i],nums[left],nums[right]});
//继续在里面寻找,否则找到一次后会死循环
left++;
right--;
while(left<right&&nums[left]==nums[left-1])
left++;
while(left<right&&nums[right]==nums[right+1])//right是和后一个比较,因为后一个是已经判断过的
right--;
}
}
}
return v;
}
};
bug点
- while(left<right && …) 记住只要出现left++,right–,就要判断一下left<right
- 相等时内缩 left,right, 否则死循环
- 去重操作!right是与right+1作比较
- if(i>0&&nums[i]==nums[i-1]) 当与前一个对比时,避免越界,可以限制短路条件i>0
18. 四数之和
原题目链接
与三数之和解法相似,只不过剪枝操作不同,注意这里target可能为负数,所以不要nums[i]>target这个剪枝操作。
官方答案还增加了一个<target,直接一下子判断四个,则continue本层循环
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
int n=nums.size();
sort(nums.begin(),nums.end());
vector<vector<int>> v;
int left,right;
for(int i=0;i<n;i++){
// if(nums[i]>target)
// return v; 如果target小于0那也不行
if(i>0&&nums[i]==nums[i-1])
continue;
for(int j=i+1;j<n;j++){
// if(nums[j]>target)
// return v; 不应该,若第一个为负数,则两者相加可能是小于target的
if(j>i+1&&nums[j]==nums[j-1])
continue;
int sum=nums[i]+nums[j];
left=j+1;
right=n-1;
// if(sum>target)
// return v; [-3,-2,-1,0,0,1,2,3] 0 少了[-1,0,0,1]
while(left<right){
if((long)sum+nums[left]+nums[right]>target)
{
right--;
}else if((long)sum+nums[left]+nums[right]<target)
{
left++;
}else
{
v.push_back({nums[i],nums[j],nums[left],nums[right]});
left++;
right--;
while(left<right&&nums[left]==nums[left-1])
left++;
while(left<right&&nums[right]==nums[right+1])
right--;
}
}
}
}
return v;
}
};