- 删除有序数组中的重复项
题目描述:给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。
由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。
将最终结果插入 nums 的前 k 个位置后返回 k 。
不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
解题思路:开始记录数组开始的第一个元素值,使用一个变量i不停去遍历数组,直到当前遍历的值与记录的值不一样时,则把该元素值插入到头部的头部,插入的位置则是由另一个变量j来记录确定。最后j的值便是数组中不重复元素的长度,返回j+1即可完成题目要求。(因为j是从0开始的,所以返回的值应该是j+1)
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int i=1,j=0;
int temp=nums[0];
while(i<nums.size())
{
if(nums[i]!=temp)
{
nums[++j]=nums[i];
temp=nums[i];
}
i++;
}
return j+1;
}
};
该方法的时间复杂度为O(n),空间复杂度为O(1),实现了题目要就的“就地”。
思路一的代码改进
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int n=nums.size();
int j=0;
for(int i=0;i<n;i++)
{
if(nums[i]!=nums[j])
{
nums[++j]=nums[i];
}
}
return j+1;
}
官方提供的代码
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int n=nums.size();
int j=0;
for(int i=0;i<n;i++)
{
if(i==0||nums[i]!=nums[i-1])
{
nums[j]=nums[i];
j++;
}
}
return j;
}
};
- 两数之和
题目描述:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
解题思路一:该思路比较直接,也比较笨拙。没遍历一个元素时,便记录该值与目标值的差,接下来便在该值后面的所有元素中寻找是否存在该元素(为什么只需要遍历该元素的后面元素,是因为当遍历到该元素时,已经说明该元素与前面的元素相加的值不等于目标值),若存在,返回两个元素的下标。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> find;
for(int i=0;i<nums.size();i++)
{
int temp=target-nums[i];
for(int j=i+1;j<nums.size();j++)
{
if(nums[j]==temp)
{ return{i,j};
}
}
}
return {};
}
};
官方解法:构建一个映射表来存放数据元素以及索引值,其中key是数组中的元素值,value是数组中的元素索引。接下来遍历整个数组,将每个数组元素与目标值求差,将差值拿到映射表中去查找,如果存在该key值,则直接返回当前元素的位置以及该key值对应的value值,否则将该元素当组key值,元素在数组中的位置当做value插入映射表中。如果不存在满足要求的元素,则返回空值。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
map<int,int>IntHash;
for(int i=0;i<nums.size();i++)
{
int temp=target-nums[i];
if(IntHash.find(temp)!=IntHash.end())
return {IntHash[temp],i};
else IntHash[nums[i]]=i;
}
return {};
}
};
15.三数之和
题目描述:给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
思路:数组里面有两类障碍,一类是数组重复,若不消除重复元素很可能导致相同结果重复输出;另一类是元素无序,如果有序的话,那么可以根据当前三值之和来判断正确的元素寻找方向。所以,在开始的时候首先对输入数组进行排序,然后开始遍历排序后的数组,如果当前元素值和上一个元素值相等则直接跳过(导致重复输出),接下来去当前元素的右方去找符合要求的元素(为什么是右方尼?其实跟第二天的题解中原理相同,如果再去查找左方元素会导致进行重复的过程,毫无意义。)当右方边界上的两个元素的值与当前遍历的值得元素和等于0,则这三个元素正式我们要找的正确输出信息,此时需要注意,当前正在遍历的元素不止与这一对元素的和为0,再往里面找很可能还有满足要求的元素,所以不能停止遍历,但同时我们还是需要去消除重复元素的干扰,继续向里面进行遍历,如果三值元素和大于0,则说明右边的元素大了,该缩小右域的边界,同理,若小于0,则应缩小左域的边界,最后将满足要求的所有元素输出。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(),nums.end());
if(nums[0]>0) return result;
for(int i=0;i<nums.size();i++)
{
if(i>0&&nums[i]==nums[i-1])
continue;
int left=i+1;
int right=nums.size()-1;
while(left<right)
{
int sum=nums[i]+nums[left]+nums[right];
if(sum==0)
{
result.push_back({nums[i],nums[left],nums[right]});
while(left<right&&nums[right]==nums[right-1]) right--;
while(left<right&&nums[left]==nums[left+1]) left++;
right--;
left++;
}
else if(sum>0) right--;
else left++;
}
}
return result;
}
};
18.四数之和
题目描述:给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
思路:整体思路与三数之和相同,只不过三数之和中我们固定一个值i,用双指针left和right去找其他两个满足条件的数,而在四数之和中骂我们可以固定两个值i和j,然后再用双指针法去找其他两个值,使得四值加起来的值等于target。思路很简单,但值得注意的是,在三指针中,因为提前给数组排过序,我们判断如何数组最小值大于target时,该输入值便没有满足题目要求的项,那是因为三值之和中target为0,如果数组第一个值比0大,那么后面的所有值也都比0大,比0大的数再加任何一个比0大的数都不可能变小,而在本题中target可真可负,如果仅用数组最小值大于target来减枝的话,在一个例子中,target=-5,数组为[-4,-1,0,0],此时会发生错误。所以在判断数组最小值大于target时,需得target大于0才行。具体的实现代码如下所示,更细节的减枝和去重都标注在代码中。
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
sort(nums.begin(),nums.end());
if (nums[0]>0&&nums[0]>target) return result;
for(int i=0;i<nums.size();i++)
{ if(i!=0&&nums[i]==nums[i-1]) continue;//去重,与三值和原理相同
for(int j=i+1;j<nums.size();j++)
{
if(nums[i]+nums[j]>target&&target>0) break;
//target为正时,两个值之和都大于target,且nums[j]一定是大于0的,所有
//后续的数也一定是大于0的,所以肯定找不到符合题意的集。
if(j>i+1&&nums[j]==nums[j-1]) continue;
long temp=(long)target-nums[i]-nums[j];
int l=j+1;
int r=nums.size()-1;
while(l<r)
{
if(nums[l]+nums[r]>temp) r--;
else if(nums[l]+nums[r]<temp) l++;
else
{
result.push_back( {nums[i],nums[j],nums[l],nums[r]});
while(l<r&&nums[r-1]==nums[r]) r--;
while(l<r&&nums[l+1]==nums[l]) l++;
r--;
l++;
}
}
}
}
return result;
}
};
242.有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词
思路:题目的要求就是统计两个字符串中的字符数,判断s有的t也要有,且有的个数相同,且所有的字符都是小写的,那么久好说了,我们只需要设置一个大小为26的数组统计s字符每一位出现的次数,再次统计t时,将字符出现的次数相抵消,如果最后数组中还存在非0的项,那么就说明不符合题意。
class Solution {
public:
bool isAnagram(string s, string t) {
int record[26]={0};
for(int i=0;i<s.size();i++)
{
record[s[i]-'a']++;
}
for(int j=0;j<t.size();j++ )
{
record[t[j]-'a']--;
}
for(int k=0;k<26;k++)
{
if(record[k]!=0) return false;
}
return true;
}
};
注释:当题目输入大小受限时,可以用数组替代哈希表。
349.两个数组的交集
给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
思路:求两个数组的交集,并要去重,去重在一个无序的数组中很麻烦,且输出不需要有序,所以我们用到了unordered_set,它对元素不会进行排序,节省了很多时间。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result;
unordered_set<int> nums1_set(nums1.begin(),nums1.end());
for(int i=0;i<nums2.size();i++)
{
if(nums1_set.find(nums2[i])!=nums1_set.end())
result.insert(nums2[i]);
}
return vector<int>(result.begin(),result.end());
}
};
454四数相加 II
给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
思路:题目的大意是四个长度都为n的的数组,能否在每个数组中找到一个元素,致使四个元素加起来的和等于0,若存在,有多少个元素组满足这个要求。也就是说,题目要求输出个数,且不需要去重(元素相同但下标不同依然是两个不同的元组)。既然只要求返回个数,那我们就可以把前两个数组的每组元素和放入map中(由于不用去重,所以map的value部分存储的是和为key值元组的个数),接下来,我们去便利另外两个数组,把他们的相反值拿到map中去查找,若找到,则统计变量则加上以该值为key的value值。
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
int count=0;
map<int,int> IntMap;
for(int i=0;i<nums1.size();i++)
{
for(int j=0;j<nums2.size();j++)
{
IntMap[nums1[i]+nums2[j]]++;//注意不用去重
}
}
for(int i=0;i<nums3.size();i++)
{
for(int j=0;j<nums4.size();j++)
{
if(IntMap.find(0-(nums3[i]+nums4[j]))!=IntMap.end())
count+=IntMap[0-(nums3[i]+nums4[j])];//注意不用去重
}
}
return count;
}
};
202.快乐数
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
思路:本题的结题关键正如题目所说,当一个数的各位的平方相加和出现循环时,就肯定不是快乐数
class Solution {
public:
bool isHappy(int n) {
unordered_set<int> Iset;
while(1)
{ int sum=0;
while(n)
{
sum+=pow(n%10,2);
n /=10;
}
if(sum==1) return true;
if(Iset.find(sum)!=Iset.end())
return false;
else Iset.insert(sum);
n=sum;
}
}
};
383 赎金信
给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。
思路
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int record[26]={0};
for(int i=0;i<magazine.size();i++)
{
record[magazine[i]-'a']++;
}
for(int i=0;i<ransomNote.size();i++)
{
record[ransomNote[i]-'a']--;
}
for(int i=0;i<26;i++)
{
if(record[i]<0)
return false;
}
return true;
}
};
总结:二数之和和二值相加只需要将前面遍历过的值保存下来,后面遍历新值时只需要去找保存的元素里有没有对应差值的。而三值相加和四值相加属于同一类问题,三值之和和四值之和要注意去重和减枝问题。在三值相加中,由于数组是经过排序的,所以在最外层的元素遇到与前值相等时应跳过避免去重,同理,当找到第二个和第三个值时,也需要进行去重;四值相加时,尤其注意找第一个值和第二个值时(第一层和第二层循环)的去重和减枝条件。