写给自己的:
算法是一个很关键的技能,关于思路和编译能力的提升都有很大的帮助,一个题不要只是想怎么解决出来,而是想怎么最快的解决,虽然这样很慢,但是现在的目标不是什么速成,慢下来可能也是件好事吧。去真正的搞懂每一题。
三数之和
这题使用双向法,使用k,i,j的索引来标记三个数,使用while循环,判断哪三个数符合要求。
代码:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(),nums.end());
if(nums.empty() || nums.back()<0 ||nums.front()>0) {
return {};
}
for(int k=0;k<nums.size();k++) {
if(nums[k]>0) {break;}
if(k>0&&nums[k]==nums[k-1]) {
continue;
}
int i=k+1,j=nums.size()-1,target=0-nums[k];
while(i<j) {
if(target==nums[i]+nums[j]) {
res.push_back({nums[k], nums[i], nums[j]});
while(i<j&&nums[i]==nums[i+1]) {i++;}
while(i<j&&nums[j]==nums[j-1]) {j--;}
i++;j--;
} else if(target>nums[i]+nums[j]) {
i++;
} else {
j--;
}
}
}
return res;
}
};
注意事项:
- 如果使用的是蛮力法,在解决重复问题上很麻烦
- 所以解决重复的最好方法就是使用C位法(左右索引),使用两个while循环来排除重复值
- 题目存在多组的答案,所以当找到一组时,都必须要向中间靠
最接近的三数之和
解题思路:
跟三数之和的寻找的思路一样,也是头尾查询法,同时寻找出target和tep的最小差距值,最后输出tep
定义数的意义:
minDel:最小差距值
代码:
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
int res,minDel=INT_MAX;
sort(nums.begin(),nums.end());
if(nums.size()==0||nums.size()<3) {
return {};
}
for(int i=0;i<nums.size();i++) {
int j=i+1,k=nums.size()-1;
while(j<k) {
int tmp=nums[i]+nums[j]+nums[k];
if(tmp==target) {
return target;
} else {
if(minDel>abs(target-tmp)) {
minDel=abs(target-tmp);
res=tmp;
}
if(tmp>target) {
k--;
} else if(tmp<target) {
j++;
}
}
}
}
return res;
}
};
注意事项:
- 在进行头尾缩进的时候,记得是用tmp和target来比较,而不是差值进行比较
寻找两个有序数组的中位数
题意:
如题目一样,就是在两组数组合并成一个数组,找出他们之间的中位数。(这里科普一下中位数的意义:将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。)
解题思路:
- 蛮力法:根据两个数组的总数,找到中位数的位置,然后根据位置求出中位数。
- 递归法:使用两个数来重新分割两个数组为两个长度相等的数组,让左边的任意一个数均小于右边的任意一个数。
定义数的意义:
蛮力法:
target:中位数的位置
index1,index2:标记两个需要求得中位数的相关数(如果有必要的话)
递归法:
m,n:表示nums1和nums2的大小(nums1必须是比较大的那个数组。)
i,j:表示分割两个数组后,在右边数组中原两个数组的第一个数的索引
iMin,iMax:表示nums1(较大数组)的有效范围,需要通过这个范围来确定i的值。(当分割的数组不符合“左边的任意一个数均小于右边的任意一个数”时,需要调试有效范围从而达到对i的增大减小)
temp:当总数为偶数时,表示为另一个数,从而求得中位数
代码:
蛮力法:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
double res=0.0;
int sz=nums1.size()+nums2.size();
int target=sz/2;
if(nums1.size()==0) {
if(sz%2==0) {
res=(nums2[target-1]+nums2[target])/2.0;
return res;
} else {
res=nums2[target];
return res;
}
} else if(nums2.size()==0) {
if(sz%2==0) {
res=(nums1[target-1]+nums1[target])/2.0;
return res;
} else {
res=nums1[target];
return res;
}
}
if(sz%2==0) {
int index1=0,index2=0,i,j;
target++;
for(i=0,j=0;i<nums1.size()&&j<nums2.size();) {
if(nums1[i]>nums2[j]) {
target--;
if(target==1) {
index1=nums2[j];
} else if(target==0) {
index2=nums2[j];
break;
}
j++;
} else {
target--;
if(target==1) {
index1=nums1[i];
} else if(target==0) {
index2=nums1[i];
break;
}
i++;
}
}
while(index1==0||index2==0) {
if(i==nums1.size()) {
target--;
if(target==1) {
index1=nums2[j];
} else if(target==0) {
index2=nums2[j];
break;
}
j++;
} else {
target--;
if(target==1) {
index1=nums1[i];
} else if(target==0) {
index2=nums1[i];
break;
}
i++;
}
}
res=(index1+index2)/2.0;
} else {
int i,j;
target++;
for(i=0,j=0;i<nums1.size()&&j<nums2.size();) {
if(nums1[i]>nums2[j]) {
target--;
if(target==0) {
res=nums2[j];
break;
}
j++;
} else {
target--;
if(target==0) {
res=nums1[i];
break;
}
i++;
}
}
while(res==0.0) {
if(i==nums1.size()) {
target--;
if(target==0) {
res=nums2[j];
break;
}
j++;
} else {
target--;
if(target==0) {
res=nums1[i];
break;
}
i++;
}
}
}
return res;
}
递归法:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
double res=0.0;
int m=nums1.size();
int n=nums2.size();
if(m>n) {
nums1.swap(nums2); //漏点1.
swap(m,n);
}
int iMin=0,iMax=m;
int mid=(m+n+1)/2; //***求中值时记得要总数加一!!
while(iMin<=iMax) {
int i=(iMin+iMax)/2,j=mid-i; //***问题3.
if(i>iMin&&nums1[i-1]>nums2[j]) {
iMax=i-1; //问题1.
//i--;
} else if(i<iMax&&nums2[j-1]>nums1[i]) {
iMin=i+1; //问题2.
//i++;
} else {
if(i==0) {
res=nums2[j-1];
} else if(j==0) {
res=nums1[i-1];
} else {
res=max(nums1[i-1],nums2[j-1]);
}
if((m+n)%2!=0) {
return res;
} else {
double temp;
if(i==m) {
temp=nums2[j];
} else if(j==n) {
temp=nums1[i];
} else {
temp=min(nums1[i],nums2[j]);
}
return (res+temp)/2.0;
}
}
}
return res;
}
注意事项:
蛮力法:
- 需要分清楚有多少种情况:
- nums1为空或者nums2为空
- 总数为偶数还是总数为奇数
- target位置定义,target是从通过减法来寻找中位数的位置的,而代码中的i,j是通过加法来完成的,所以要搞好之间的关系,举例子就可以找到规律
递归法:
- 交换两个vector类型的方法
- i,j每次循环时都需要改变,所以不能在循环体外求得它们得值
- 使i增大减小时,我们是通过改变iMin和iMax,然后再用i=(iMin+iMax)/2来完成得
- 要处理特殊得情况:
- nums1为空或者nums2为空得情况:这时就直接找不是为空得中位数即可(注意i-1,j-1就是它们得中位数索引)
- 当i,j是它们数组的最后索引时,这时我们就找右边的最小那个数,求出中位数即可(一样i,j就是它们的索引)
最长回文子串
解题思路:
蛮力法:使用双循环,每个子串都反转来比较和原子串是否相等,如果相等即可返回记录长度,找出最长的长度;- 蛮力法2.0:暂时还没有看懂,稍后更新;
- 动态规划法:以字符串中每个字符和它的下一个字符为中心展开,寻找最长回文子串;
定义数的意义:
蛮力法:
max:最长回文子串的长度
index:最长子串的头字符的索引
temp,tmp:反转后的子串,保存原子串
动态规划法:
start,end:最长回文子串的头尾字符索引
len1,len2,len:循环中以自己为中心的最长回文子串,以自己后一个字符为中心的最长回文子串,两个中较大的最长回文子串
L,R:动态函数中以中心向两边展开的左右索引
代码:
蛮力法:
class Solution {
public:
string longestPalindrome(string s) {
int sz=s.size();
if(sz==0||sz==1) {
return s;
}
int index,max=0;
for(int i=0;i<sz;i++) {
for(int j=i;j<sz;j++) {
string temp=s.substr(i,j-i+1);
string tmp=temp;
reverse(temp.begin(),temp.end());
if(temp==tmp) {
if(max<j-i+1) {
max=j-i+1;
index=i;
}
}
}
}
return s.substr(index,max);
}
};
动态规划法:
class Solution {
public:
string longestPalindrome(string s) {
if(s.size()==0) {
return "";
}
int m=s.size();
int start=0,end=0;
for(int i=0;i<m;i++) {
int len1=expandAoundCenter(s,i,i);
int len2=expandAoundCenter(s,i,i+1);
int len=max(len1,len2);
if(len>end-start) {
start=i-(len-1)/2;
end=i+len/2;
}
}
return s.substr(start,end-start+1);
}
int expandAoundCenter(string s,int left,int right) {
int L=left,R=right;
while(L>=0 && R<s.size() && s.at(L)==s.at(R) ) {
L--;
R++;
}
return R-L-1;
}
};
注意事项:
蛮力法:
这个代码是没法通过的,因为时间复杂度为O(n^3),超出时间限制;
动态规划法:
- start,end的更新数据方式,start是以中心一半向前移,end是以中心一半向后移;
- c++中substr方法的使用,第二个参数是子串的长度
- 动态函数while循环的向两边展开的条件
- 注意为空的情况
- len-1为了避免数组索引越界
- 左右边界包括了回文串中后两个数,所以要减去不是回文串的字符
字符串转换整数 (atoi)
解题思路:
这道题好像是只有蛮力法来解决,但是问题是存在着很多问题没有提及的情况,需要我们一步步来调试,这题最难的就是一步通过的问题。
定义数的意义:
res(long):存放最终结果,必须要定义为long才能检验int溢出问题的数据类型
index:记录开头第一个不是空格的索引
flag:记录是否为负数
代码:
class Solution {
public:
int myAtoi(string str) {
long res=0;//使用long避免溢出
int index=0;//记录开头第一个不是空格的索引
bool flag=false;//记录是否为负数
if(str.size()==0) {
return res;
}
for (int i = 0; i < str.size(); i++) { //排除空格
if (str[i] == ' ') {
index++;
}
else {
break;
}
}
if ((str[index]-'0'<0 || str[index]-'0'>9) && str[index] != '-' &&str[index]!='+') { //如果开头为字母,直接返回0
return 0;
}
if (str[index] == '-') { //判断是否为负数
flag = true;
index++;
}else if (str[index] == '+') { //判断是否存在"+"号
index++;
}
for (int i = index; i<str.size(); i++) {
if (str[i] == ' ') { //中间存在空格直接break;
break;
}
int tmp = str[i] - '0';
if (tmp >= 0 && tmp <= 9) { //如果是数字的转化
res = res * 10 + tmp;
}
if ((tmp<0 || tmp>9 ||str[i] == ' ') && i != index) {
break;
}
if (res>=INT_MAX) { //溢出问题的解决
if (flag&&res==INT_MAX) {
return INT_MIN+1;
}
if(flag&&res>INT_MAX) {
return INT_MIN;
}
if(!flag) {
return INT_MAX;
}
}
}
if(flag) { //判断是否为负数
return -res;
} else {
return res;
}
}
};
注意事项:
- 要排除开头的全部空格(使用index来解决)
- 不要忽略开头为+的情况
- 如果中间非数字(包括存在空格),要跳出循环,而不是直接返回数据
- 在验证int范围时(INT_MAX (2^31 − 1) 或 INT_MIN (−2^31) ),要分三种情况来返回值:
- "2147483648"->2147483647
- "-2147483647"->-2147483647
- "-2147483648"->-2147483648
最长公共前缀
解题思路:
这题我想到的方法就很蛮力法,双循环找到相同的部分,添加给string中返回即可,在网上看了的思路原理都是使用LCP方法,但是因为我一开始就没有看懂这个思路,有待更新。
定义数的意义:
蛮力法:
res:就是返回结果
LCP法:
代码:
蛮力法:
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
string res="";
if(strs.size()==0) {
return res;
}
for(int i=0;i<strs[0].size();i++) {
for(int j=1;j<strs.size();j++) {
if(strs[j][i]!=strs[j-1][i]) {
return res;
}
}
res+=strs[0][i];
}
return res;
}
};
LCP法:
注意事项:
蛮力法:
就是注意下为空的情况即可;
LCP法:
有效的括号
解题思路:
使用栈进行入栈出栈操作即可
定义数的意义:
num1,num2,num3:分别代表剩余{,(,[的个数
tmp:栈顶的字符
代码:
class Solution {
public:
bool isValid(string s) {
stack<char> ss;
if(s.size()==0) {
return true;
}
if(s.size()==1) {
return false;
}
int num1=0,num2=0,num3=0;
for(int i=0;i<s.size();i++) {
if(s[i]=='{') {
ss.push(s[i]);
num1++;
} else if(s[i]=='(') {
ss.push(s[i]);
num2++;
} else if(s[i]=='[') {
ss.push(s[i]);
num3++;
} else {
if(s[i]=='}') {
if(ss.empty()) {
return false;
}
char tmp=ss.top();
ss.pop();
if(tmp!='{') {
return false;
}
num1--;
} else if(s[i]==')') {
if(ss.empty()) {
return false;
}
char tmp=ss.top();
ss.pop();
if(tmp!='(') {
return false;
}
num2--;
} else {
if(ss.empty()) {
return false;
}
char tmp=ss.top();
ss.pop();
if(tmp!='[') {
return false;
}
num3--;
}
}
}
if(num1==0&&num2==0&&num3==0) {
return true;
} else {
return false;
}
}
};
注意事项:
- 栈数为0和1时的判定
- 当字符是'}',')',']'时,我们都必须先检查栈是否为空
删除排序数组中的重复项
解题思路:
- vector自带的删除函数erase法:遇到相同的,使用erase删除掉
- 双指针法:找到相同的,把他们全部向前移动,最后返回数量
代码:
erase法:
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
for(int i=1;i<nums.size();i++) {
if(nums[i]==nums[i-1]) {
nums.erase(nums.begin()+i-1);
i--;
}
}
return nums.size();
}
};
双指针法:
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.empty()) {
return 0;
}
int i=0;
for(int j=1;j<nums.size();j++) {
if(nums[i]!=nums[j]) {
i++;
nums[i]=nums[j];
}
}
return i+1;
}
};
注意事项:
erase法:
- 当你删除一个数时,要记得把i--,这样可以避免少检查数
- 这个方法频繁使用到原函数erase,所以效率值很低
双指针法:
- 要注意nums为空的情况
盛最多水的容器
题意:
在数组中,找到两个数作为一个长方形,长为它们索引的差值,高为两个数中较小的那个数,求得所以数组中的最大长方形
解题思路:
蛮力法:
使用双重循环把全部的长方形都求出,最后得出最大的那个
双指针法:
使用双索引值,总是较小值向较大值移动即可求出
定义数的意义:
双指针法:
i,j:头尾索引值
代码:
蛮力法:
class Solution {
public:
int maxArea(vector<int>& height) {
int res,maxtmp=0;
for(int i=0;i<height.size()-1;i++) {
for(int j=i+1;j<height.size();j++) {
int hei=min(height[i],height[j]),wid=j-i;
maxtmp=max(maxtmp,hei*wid);
}
}
return maxtmp;
}
};
双指针法:
class Solution {
public:
int maxArea(vector<int>& height) {
int maxtmp=0;
int i=0,j=height.size()-1;
while(i<j) {
int tmp=min(height[i],height[j]);
maxtmp=max(maxtmp,tmp*(j-i));
if(height[i]<height[j]) {
i++;
} else {
j--;
}
}
return maxtmp;
}
};
注意事项:
蛮力法:
很明显,这个方法的效率很低,所以当数组的数量较多时,就会超出时间限制无法AC
双指针法:
这个其实就是蛮力法的一个优化,去除多余的求解,重点在于头尾索引的移动方式:较小值向较大值移动,能想到这点就可以AC了
字符串相乘
解题思路:
- 最简单粗暴的思路:把num1,num2变成long型再进行相乘,但是无论是哪个类型,都会超出最大长度,所以这个方法不行
- 数组位数法:m位数与n位数相乘得到的一定是一个m+n位数或者m+n-1位数,用乘法的计算原理,数组存储位数来完成AC
定义数的意义:
m,n:num1和num2的位数
bits:用来存储结果位数的各个数
p:每个计算中结果存储在哪个位置的索引值
sz:除去0之后的最高位索引值
代码:
class Solution {
public:
string multiply(string num1, string num2) {
int m=num1.size(),n=num2.size();
vector<int> bits(m+n,0);
//下面进行的乘法原理计算,很重要!!!
for(int i=m-1;i>=0;i--) {
int p=m-1-i;
for(int j=n-1;j>=0;j--) {
int res=(num1[i]-'0')*(num2[j]-'0')+bits[p];
if(res>=10) {
bits[p+1]+=res/10;//记得这里是加等于!!
}
bits[p]=res%10;
p++;
}
}
string result;
int sz=m+n-1;
while(sz>=0&&bits[sz]==0) {
sz--;
}
for(int i=0;i<=sz;i++) {
result=char(bits[i]+'0')+result;
}
return result==""?"0":result;//分析结果为0的情况
}
};
注意事项:
- 要注意结果为0的情况
- 在进行进位计算的时候,要进行的是+=操作
- 要理解乘法原理计算的代码
反转字符串中的单词 III
解题思路:
以空格为分割条件,用substr来取子串,把子串全部反转后,用新的string存储起来,最后返回即可
定义数的意义:
index:每个子串的第一个字符串的索引
temp:子串的存储
代码:
class Solution {
public:
string reverseWords(string s) {
int index = 0;
string res = "";
for (int i = 0; i < s.size(); i++)
{
if (s[i] == ' ') {
string temp = s.substr(index, i - index);
index = i+1;
reverse(temp.begin(), temp.end());
res = res + temp + " ";
}
if (i == s.size() - 1) {
string temp = s.substr(index, s.size() - index);
reverse(temp.begin(), temp.end());
res += temp;
}
}
return res;
}
};
注意事项:
- index存储的是每个子串的第一个索引,而不是空格的索引值
- 每个子串的长度计算
- 在最后子串的反转的分割条件用空格并不适用,所以要单独求解
除自身以外数组的乘积
解题思路:
- 双指针法:使用左右指针计算乘积,当左右指针重合时停止,但是还是超过了时间限制
- 使用两个数组,一个记录顺序的数乘积,另一个记录逆序的数乘积,并反向顺序记录,最后通过规律求解
定义数的意义:
ins:记录顺序乘积数组
ret:先是记录逆序乘积的数组,然后是结果数组
num1,num2:通过计算得出ins,ret每个索引值的乘积数
代码:
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int len=nums.size();
if(len==0||len==1) {
return nums;
}
vector<int> ins(len,nums[0]); //先是存储全部的第一个数
vector<int> ret(len,nums[len-1]);//先是存储全部的最后一个数
int num1=nums[0],num2=nums[len-1];
for(int i=1;i<len;i++) {
num1*=nums[i];
num2*=nums[len-i-1];
ins[i]=num1;
ret[len-i-1]=num2; //逆序存储!!
}
ret[0]=ret[1]; //第一个数的求解!!!
for(int i=1;i<len-1;i++) {
ret[i]=ins[i-1]*ret[i+1];
}
ret[len-1]=ins[len-2]; //最后一个数的求解!!!
return ret;
}
};
注意事项:
- ret存储的方式是逆序存储
- 在ret存储结果的阶段,边界数(第一个和最后一个数)的求解需要单独求得
螺旋矩阵 和 螺旋矩阵 II
题意:
螺旋矩阵:
在二维数组中,从外到内,一层一层的遍历到最后一个数
螺旋矩阵 II:
在一维数组中,需要像”蛇“一样的遍历数组
解题思路:
这两题都没有什么巧妙的方法,就是通过归纳找到每层的索引值然后赋值,这两题的难点都在于怎么找到这个规律
定义数的意义:
螺旋矩阵:
m,n:二维数组中的行数和列数
count:一共搜索的共次数
i:当前的层数
螺旋矩阵 II:
c,i:当前的数和层数
代码:
螺旋矩阵:
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> res;
if(matrix.empty()||matrix[0].empty()) {
return res;
}
int m=matrix.size(),n=matrix[0].size();
int count=(min(m,n)+1)/2; //一共搜索的层数
int i=0;
//每层外部搜索,要搞清楚其中的逻辑
while(i<count) {
for(int j=i;j<n-i;j++) {
res.push_back(matrix[i][j]);
}
for(int j=i+1;j<m-i;j++) {
res.push_back(matrix[j][(n-1)-i]);
}
for(int j=(n-1)-(i+1);j>=i&&(m-1-i!=i);j--) { //记得n-1和i+1要加上括号!!!!!!!!!为什么呢???
res.push_back(matrix[m-1-i][j]);
}
for(int j=(m-1)-(i+1);j>=i+1&&(n-1-i)!=i;j--) {
res.push_back(matrix[j][i]);
}
i++;
}
return res;
}
};
螺旋矩阵 II:
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> ivec;
vector<int> vec(n,0);
for(int i=0;i<n;i++) {
ivec.push_back(vec);
}
int c=1,i=0;
while(c<=n*n) {
for(int j=i;j<n-i;j++) {
ivec[i][j]=c++;
}
for(int j=i+1;j<n-i;j++) {
ivec[j][n-i-1]=c++;
}
for(int j=n-i-2;j>=i;j--) {
ivec[n-i-1][j]=c++;
}
for(int j=n-i-2;j>i;j--) {
ivec[j][i]=c++;
}
i++;
}
return ivec;
}
};
注意事项:
当然是找到规律啦,这个就不解释了,本来就是纯逻辑的事
螺旋矩阵:
- 要排除为空和为1的情况
- count的求法,取m,n的较小值加1后除于2
- 当前层数和共层数的关系没有等于
螺旋矩阵 II:
这个没有什么注意的细节,要注意的还是规律的归纳罢了
字母异位词分组
解题思路:
使用map函数,分别类型为string,vector<string>,把全部的string排序,这样有相同字母的单词就会是一样的,我们就将一样的单词放进vector<string>中,最后再全部添加到svec中返回。
定义数的意义:
svec:返回结果类型
sv:map函数存储相同的值
temp:每个单词按顺序排序后存储的值
代码:
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
vector<vector<string>> svec;
if(strs.empty()) {
return svec;
}
sort(strs.begin(),strs.end());
map<string,vector<string>> sv;
for(int i=0;i<strs.size();i++) {
string temp=strs[i];
sort(temp.begin(),temp.end());
sv[temp].push_back(strs[i]);
}
for(map<string,vector<string>>::iterator iter=sv.begin();iter!=sv.end();iter++) {
svec.push_back(iter->second);
}
return svec;
}
};
注意事项:
- 我曾使用两个for循环来完成操作实现,但是很明显超出时间限制,所以就需要考虑到map函数,而且map函数存储类型是重要的选择
- 当map函数的数据需要遍历时,需要使用iterator类型(迭代)
无重复字符的最长子串
解题思路:
- 滑动窗口法1:左闭右开,向右添加字符,直到有重复字符,然后找到最大的那个子串。时间复杂度为O(n)
- 滑动窗口法2(优化版):使用map函数,分别记录字符和位置,跳过重复的循环
- 数组法:直接使用数组来存储位置,记录左边界的位置,有无重复字符或者遇到较大子串时,就更新最大子串
定义数的意义:
滑动窗口法:
sc:set函数,存储目前存在的字符
pos:set函数的迭代数,用来寻找重复数
i,j:左右边界
滑动窗口法(优化版):
mci:map函数,存储字符和字符最后所在的位置
i,j:分别代表左右边界
数组法:
m:存储ascii表中的字符在数组中的位置
left:左边界
代码:
滑动窗口法:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int n=s.size(),res=0;
set<char> sc;
set<char>::iterator pos;
int i=0,j=0;
while(i<n&&j<n) {
pos=sc.find(s[j]);
if(pos==sc.end()) {
sc.insert(s[j++]);
res=max(res,j-i);
} else {
sc.erase(s[i++]);
}
}
return res;
}
};
时间为104ms
滑动窗口法(优化版):
class Solution {
public:
int lengthOfLongestSubstring(string s) {
map<char,int> mci;
map<char,int>::iterator pos;
int res=0;
for(int i=0,j=0;j<s.size();j++) {
pos=mci.find(s[j]);
if(pos!=mci.end()) {
i=max(mci[s[j]],i);
}
res=max(res,j-i+1);
mci[s[j]]=j+1;
}
return res;
}
};
时间为32ms
数组法:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int m[256]={0};
int left=0,maxlen=0;
for(int i=0;i<s.size();i++) {
if(m[s[i]]==0 || m[s[i]]<left) {
maxlen=max(maxlen,i-left+1);
} else {
left=m[s[i]];
}
m[s[i]]=i+1;
}
return maxlen;
}
};
时间为4ms
注意事项:
滑动窗口法:
- 一定要给res初始值
- i,j记录的是左右边界位置,而不是索引值,所以长度就是j-i即可
- 记得iterator的用法,vector,list,map,set如果要迭代都需要使用到iterator
滑动窗口法(优化版):
- 这个的关键在于i位置的决定:
- 遇到重复字符时,i变成遇到前一个同样字符的下一个的索引值
- 然后在求无重复子串时,当前最大的子串
数组法:
- 要给m数组初始化,方式为m[256]={0}
- i,left都是索引值,所以要长度是i-left+1
递增的三元子序列
解题思路:
题目的要求就不能使用蛮力法来解决问题,只能使用一个循环,使用一个while循环,先找到最小值,再找中间值,最后如果存在最大值即可返回true,最小中间值会随着循环的进行可能会更新数据
定义数的意义:
Min:最小值
medium:中间值
代码:
class Solution {
public:
bool increasingTriplet(vector<int>& nums) {
int n=nums.size();
if(n<3) {
return false;
}
int Min=INT_MAX,medium=INT_MAX;
int i=0;
while(i<n) {
if(nums[i]<Min) {
Min=nums[i];
} else if(nums[i]>Min&&nums[i]<medium) {
medium=nums[i];
} else if(nums[i]>medium) {
return true;
}
i++;
}
return false;
}
};
注意事项:
- 题目要求的是子序列,所以不能打乱原数组的位置
- 如果存在比最小值小的值,就更新最小值
- 如果存在比最小值大,但是比中间值小的值就更新中间值
- 如果存在比中间值大的数,即可返回true
- Min,medium初始值都是最大的INT值
- 直接排除数量小于3的数组,可以提高效率
按列翻转得到最大值等行数
解题思路:
题目只存在0,1,把1变成0后的原理就是把1和0的位置进行交换,所以找到位置相同和位置相反的数量最大的即是答案
定义数的意义:
mss:存储与原数组配对的相同和相反的string数组
msi:存储数组和反数组的数量
代码:
class Solution {
public:
int maxEqualRowsAfterFlips(vector<vector<int>>& matrix) {
map<string,string> mss;
map<string,int> msi;
for(int i=0;i<matrix.size();i++) {
string temp=vecToString(matrix[i],0);
mss[temp]=vecToString(matrix[i],1);
msi[temp]++;
}
int res=1;
for(auto& n:msi) {
res=max(res,n.second+msi[mss[n.first]]);
}
return res;
}
string vecToString(vector<int> ivec,int flag) {
string res="";
for(auto v:ivec) {
res+='0'+(flag?!v:v);
}
return res;
}
};
按列翻转得到最大值等行数
解题思路:
- 蛮力法:先找到两个字符串中相等的部分,然后再消去相同部分中重复的部分,最后再判断是否为公约字符串
- 数学法:找出字符串长度的最大公约数,然后判断子串是否为公约字符串即可
定义数的意义:
蛮力法:
temp:两个字符串中相等的部分
temp2:以temp的公约数为基数,创建的首个子串
temp3:以temp的公约数为基数,每公约数个子串
tmp,tmp2:temp和temp2的长度
flag:判断该子串是否为公约串
数学法:
n3:str1,str2长度的最大公约数
isMatch函数:是否为公约串
代码:
蛮力法:
class Solution {
public:
string gcdOfStrings(string str1, string str2) {
string temp,res;
int n=str1.size(),m=str2.size();
if(n<m) {
str1.swap(str2);
swap(n,m);
}
for(int i=0;i<m;i++) {
if(str1[i]==str2[i]) {
temp+=str1[i];
} else {
return "";
}
}
int tmp=temp.size();
for(int i=1;i<=tmp;i++) {
if(tmp%i==0) {
string temp2=temp.substr(0,i);
int j;
for(j=i;j<tmp;) {
string temp3=temp.substr(j,i);
if(temp2!=temp3) {
break;
} else {
j+=i;
}
}
if(j>=tmp) {
bool flag=true;
int tmp2=temp2.size();
for(int i=0;i<n;) {
string temp3=str1.substr(i,tmp2);
if(temp3!=temp2) {
flag=false;
break;
}
i+=tmp2;
}
if(flag) {
res=temp2;
}
}
}
}
return res;
}
};
数学法:
class Solution {
public:
string gcdOfStrings(string str1, string str2) {
int n1=str1.size(),n2=str2.size();
int n3=gcd(n1,n2);
string res=str1.substr(0,n3);
if(isMatch(res,str1)&&isMatch(res,str2)) {
return res;
}
return "";
}
bool isMatch(string str1,string str2) {
for(int i=0;i<str1.size();i++) {
if(str1[i]!=str2[i%str2.size()]) {
return false;
}
}
return true;
}
};
思路分析:
蛮力法:
找出子串中重复部分是最难的,且每个公约数子串都得找,十分耗时,思路很直接,实现复杂
数学法:
直接找出最大公约数,以最大公约数找到子串,直接判断是否为公约串,判断的方法十分巧妙
翻转链表
解题思路:
- 常规方法:使用两个链表来分别存储前一个节点和后面的全部节点
- 递归法:递归的参数是head的下一个节点,但是没很懂其中的逻辑关系
代码:
常规法:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *prev=NULL,*curr=head;
while(curr!=NULL) {
ListNode *temp=curr->next;
curr->next=prev;
prev=curr;
curr=temp;
}
return prev;
}
};
分数到小数
解题思路:
没什么巧妙的方法,分情况讨论,主要注意的是每种情况的细节和边界情况
定义数的意义:
index:标志了一共几个循环数
代码:
class Solution {
public:
string fractionToDecimal(int numerator, int denominator) {
string res="";
if(denominator==0||numerator==0) {
return "0";
}
long num=numerator,den=denominator;
if(denominator<0^numerator<0) { //只有当其中一个符合才能执行代码
res+="-";
}
num=abs(num);
den=abs(den);
long temp1=num/den;
res+=to_string(temp1);
if(num%den==0) {
return res;
}
res+=".";
unordered_map<long,int> mli;
long sub=num%den;
int index=0;
while(sub&&mli.find(sub)==mli.end()) {
mli[sub]=index++;
sub*=10;
res+=to_string(sub/den);
sub%=den;
}
if(mli.find(sub)!=mli.end()) {
res+="()";
int cur=res.size()-2;
while(index-->mli[sub]) {
swap(res[cur],res[cur-1]);
cur--;
}
}
return res;
}
};
解题分析:
- 除数和被除数为0时,都是输出“0”
- 在处理分数时,要标志上index再给index添加1
- 理清循环数的思路
Pow(x, n)
解题思路:
就是把pow方法的原理搞清楚,使用递归思路解决
代码:
class Solution {
public:
double myPow(double x, int n) {
bool flag=true;
if(n<0) {
return 1/Half(x,n);
}
return Half(x,n);
}
double Half(double x,int n) {
if(n==0) {
return 1.0;
}
double res=Half(x,n/2);
if(n%2==0) {
return res*res;
} else {
return res*res*x;
}
}
};
解题分析:
- 理清一个思路即可,即2^10=4^5=16^2*4
- 然后按照递归思路完成解题
两数相除
解题思路:
两数相除但是又用不到除法,就只能使用列竖式算除法的原理,计算机的乘法和除法是通过左(乘)右(除)移被除数来完成的,记录被除数移动的位数,当除数大于被除数时,就加上二进制(第count+1位上的1)转换为十进制,注意边界的情况
代码:
class Solution {
public:
int divide(int dividend, int divisor) {
bool flag=false;
if((dividend<0) ^ (divisor<0)) {
flag=true;
}
if(dividend == INT_MIN && divisor == -1)
return INT_MAX;
if(dividend == INT_MIN && divisor == 1)
return INT_MIN;
long long div=dividend,divs=divisor,res=0,count=0;
div=abs(div);
divs=abs(divs);
while(div>=divs) {
count++;
divs<<=1;
}
while(count) {
count--;
divs>>=1;
if(div>=divs) {
res+=1<<count;
div-=divs;
}
}
if(flag) {
res=0-res;
}
if(res>INT_MAX) {
return INT_MAX;
}
return res;
}
};
解题分析:
- 这题中最大的困难就是不能搞定INT_MIN变成INT_MAX的转化,最简单的解决方式就是单独找出这两种情况来求解
- 为了避免溢出错误,我们把全部数都转化成long long类型
电话号码的字母组合
解题思路:
这题使用的递归法,递归的规律是backtrack(cur+letter,next.substr(1));,cur是当前一个string,例如"a","ab",letter是当前要添加的char类型,最后next.substr(1)取的就是除了当前char以后的string。这个思路有点难想到,但要是想通了就很简单了
定义数的意义:
letters:指的是当前的sting数字对应的字母群
letter:当前每个字母
next:除了当前string的string
代码:
class Solution {
public:
map<string,string> mss;
void addMss() {
mss["2"]="abc";
mss["3"]="def";
mss["4"]="ghi";
mss["5"]="jkl";
mss["6"]="mno";
mss["7"]="pqrs";
mss["8"]="tuv";
mss["9"]="wxyz";
}
vector<string> svec;
void backtrack(string cur,string next) {
addMss();
if(next.size()==0) {
svec.push_back(cur);
}
string letters=mss[next.substr(0,1)];
for(int i=0;i<letters.size();i++) {
string letter=letters.substr(i,1);
backtrack(cur+letter,next.substr(1));
}
}
vector<string> letterCombinations(string digits) {
if(digits.size()!=0) {
backtrack("",digits);
}
return svec;
}
};
搜索旋转排序数组
解题思路:
因为是从有序数组中进行旋转得来的数组,所以我们把数组分成两部分,一部分是升序递增,另一部分是降序递减,我们找到target应该所在的部分来进行搜索,返回索引值
代码:
class Solution {
public:
int search(vector<int>& nums, int target) {
if(nums.empty()) {
return -1;
}
if(nums.size()==1) {
if(target==nums[0]) {
return 0;
} else {
return -1;
}
}
int left=0,right=nums.size()-1;
while(left<=right) {
if(target==nums[left]) {
return left;
} else if(target==nums[right]) {
return right;
} else if(target>nums[left]) {
while(target>nums[left]) {
if(left+1<nums.size()) {
if(nums[left]<=nums[left+1]) {
left++;
}
else {
return -1;
}
} else {
return -1;
}
}
if(target==nums[left]) {
return left;
} else {
return -1;
}
} else if(target<nums[right]) {
while(target<nums[right]) {
if(right-1>=0) {
if(nums[right]>=nums[right-1]) {
right--;
} else {
return -1;
}
} else {
return -1;
}
}
if(target==nums[right]) {
return right;
} else {
return -1;
}
} else {
left++;
right--;
}
}
return -1;
}
};
解题分析:
- 特别注意两种特别情况:
- 数组为空时,直接返回-1
- 数组的数量为1时,直接比较返回
- 注意left和right的递增或者递减时,不能超过数组的有效范围,否则就溢出
- 当数组刚好是倒序排序的时候,就有可能会出现target出现在nums[left]和nums[right]之间,这时就要两边同时向中间靠近
二叉树中的最大路径和
解题思路:
根据递归的思想,我们先要找到以根节点为开始的最大权值,然后比较以根节点的左右节点为开始的最大权值,以此来完成递归操作
定义数的意义:
leftMax:当前节点的左节点开始的最大权值
rightMax:当前节点的右节点开始的最大权值
tempMax:当前节点全部走完路径的最大权值
代码:
class Solution {
public:
int res=INT_MIN;
int maxPathSum(TreeNode* root) {
maxValue(root);
return res;
}
int maxValue(TreeNode *root) {
if(root==NULL) {
return 0;
}
int leftMax=max(maxValue(root->left),0);
int rightMax=max(maxValue(root->right),0);
int tempMax=root->val+leftMax+rightMax;
res=max(res,tempMax);
return root->val+max(leftMax,rightMax);
}
};
解题分析:
- leftMax和rightMax的求解时,要知道是否要加上它们,如果走完的最大权值都是个负数,就不必理会了,所以max和0来比较
- tempMax的结果要和最终结果来比较
- 递归方法返回的并不是最终结果,而是当前权值加上左右中的最大权值
二叉搜索树的最近公共祖先
解题思路:
- 我一开始的思路是使用容器存放他们经过的路径,从而对比路径值找到他们的公共祖先,但是发现对比的过程很复杂,无法实现
- 当我们找到规律时,就十分简单易懂了,即当左右值均比根值小时,就递归根的左节点,反则右节点
代码:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
int rVal=root->val,pVal=p->val,qVal=q->val;
if(pVal>rVal && qVal>rVal) {
return lowestCommonAncestor(root->right,p,q);
} else if(pVal<rVal && qVal<rVal) {
return lowestCommonAncestor(root->left,p,q);
} else {
return root;
}
}
};
解题分析:
- 这是道明白规律了就十分简单的题目,前提是你必须先找到递归方法的规律
- 使用递归时,要返回递归方法,不然会报出没有返回值的执行错误
括号生成
解题思路:
- 回溯法:找到回溯函数的规律即可,返回条件为cur长度达到要求则返回,递归规则是当(小于n时,就加上(并left+1递归,如果)小于(则符合添加条件,所以加上)并right+1递归
- 闭关法:使用3个for循环来完成,第一个for是来循环括号数,第二个是来循环left的括号数,第三个是来循环right的括号数,最后添加上"("+left+")"+right
定义数的意义:
回溯法:
ans:用来存储返回的结果
cur:当前的stiring,完成后添加到ans中返回最终结果
left,right:(和)的数量
max:一共的括号数
闭关法:
以一个默认的括号为基础
left:默认括号中的括号数量
right:默认括号外的括号数量
代码:
回溯法:
class Solution {
public:
void backtrack(vector<string> &ans,string cur,int left,int right,int max) {
if(cur.length()==max*2) {
ans.push_back(cur);
return;
}
if(left<max) {
backtrack(ans,cur+"(",left+1,right,max);
}
if(right<left) {
backtrack(ans,cur+")",left,right+1,max);
}
}
vector<string> generateParenthesis(int n) {
vector<string> ans;
backtrack(ans,"",0,0,n);
return ans;
}
};
闭关法:
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> res;
if(n==0) {
res.push_back("");
} else {
for(int i=0;i<n;i++) {
for(string left:generateParenthesis(i)) {
for(string right:generateParenthesis(n-1-i)) {
res.push_back("("+left+")"+right);
}
}
}
}
return res;
}
};
解题分析:
回溯法:
- 回溯函数中,一定要在vector<string>上添加&,不然不能完成编译
闭关法:
- 在递归函数中,原本的right就是下一个的left,所以使用循环来完成left的括号数,剩下的数量给right,递归后就完成了回溯
- 有一个默认的括号,所以第一个回溯中i<n而没有等于
全排列(重点!!!)
解题思路:
递归规律:for循环原数组,然后从first开始交换到最后一个,递归以first+1的函数规律
定义数的意义:
first:以first不变交换全部的数组,然后使用递归交换first+1,以此类推来完成数组的全排列
代码:
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res;
vector<int> temp;
for(int num:nums) {
temp.push_back(num);
}
int n=temp.size();
backtrack(0,res,temp,n);
return res;
}
void backtrack(int first,vector<vector<int>>& ivec,vector<int>& temp,int n) {
if(first==n) {
ivec.push_back(temp);
}
for(int i=first;i<n;i++) {
swap(temp[first],temp[i]);
backtrack(first+1,ivec,temp,n);
swap(temp[first],temp[i]);
}
}
};
解题分析:
- 这个思想一开始很难想到,需要时间来适应这种思想
- 在交换完成一次的递归后,需要把之前交换的数重新复位,以便不影响之后的递归不出现重复现象
子集
解题思路:
就像和全排列类似的思路,基线条件为tmp的数量是否小于nums,是则添加到结果容器res中,然后使用for循环全数组数,变化参数为i+1,在完成后要把tmp的最后一个数删除掉
定义数的意义:
first:以first为中心来完成全部的子集
代码:
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> res;
vector<int> tmp;
backtrack(res,tmp,nums,0);
return res;
}
void backtrack(vector<vector<int>> &ivec,vector<int> vec,vector<int> &nums,int first) {
if(vec.size()<=nums.size()) {
ivec.push_back(vec);
}
for(int i=first;i<nums.size();i++) {
vec.push_back(nums[i]);
backtrack(ivec,vec,nums,i+1);
vec.pop_back();
}
}
};
解题分析:
- 找对基线条件为重点突破点
- 递归函数的参数变化值是第二个突破点
格雷编码
解題思路:
格雷编码代表連續兩個數之間二進制只存在一個數字的差別,我們單從定義中很難找到它們之間的規律,其實就是使用鏡面思想,在前面已經的步驟中,鏡面複製全部數據,然後再在它們的前面加上0和1即可
定義數的意義:
i,j,k:分別表示要進行多少步,進行鏡面複製的索引值,進行加0和1步驟的索引值
cnt:以這個值為界限,來判斷前面加0還是1
代碼:
class Solution {
public:
vector<int> grayCode(int n) {
if(n==0) {
return {0};
}
vector<string> res={"0","1"}; //這個也是n=1時的答案
for(int i=1;i<n;i++) { //i從1開始,所以儅n=1時,直接返回res
for(int j=res.size()-1;j>=0;j--) {//進行鏡面複製操作
res.push_back(res[j]);
}
int cnt=pow(2,i); //界限
for(int k=0;k<res.size();k++) {
if(k<cnt) res[k]="0"+res[k];
else res[k]="1"+res[k];
}
}
vector<int> ivec;
for(int i=0;i<res.size();i++) {
ivec.push_back(change(res[i]));
}
return ivec;
}
int change(string &str) { //將二進制string轉化成十進制int值
int res=0;
for(int i=str.size()-1;i>=0;i--) {
res+=(str[i]-'0')*pow(2,str.size()-1-i);
}
return res;
}
};
注意事項:
- 在轉化成十進制時,string的索引值是和我們轉化的順序相反的,所以要從后開始且注意次方值