Leetcode 刷题记录

写给自己的:

算法是一个很关键的技能,关于思路和编译能力的提升都有很大的帮助,一个题不要只是想怎么解决出来,而是想怎么最快的解决,虽然这样很慢,但是现在的目标不是什么速成,慢下来可能也是件好事吧。去真正的搞懂每一题。

 

三数之和    

这题使用双向法,使用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;
    }
};

注意事项:

  1. 如果使用的是蛮力法,在解决重复问题上很麻烦
  2. 所以解决重复的最好方法就是使用C位法(左右索引),使用两个while循环来排除重复值
  3. 题目存在多组的答案,所以当找到一组时,都必须要向中间靠

 

最接近的三数之和

解题思路:

跟三数之和的寻找的思路一样,也是头尾查询法,同时寻找出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;
    }
};

注意事项:

  1. 在进行头尾缩进的时候,记得是用tmp和target来比较,而不是差值进行比较

 

寻找两个有序数组的中位数

题意:

如题目一样,就是在两组数组合并成一个数组,找出他们之间的中位数。(这里科普一下中位数的意义:将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。)

解题思路:

  1. 蛮力法:根据两个数组的总数,找到中位数的位置,然后根据位置求出中位数。
  2. 递归法:使用两个数来重新分割两个数组为两个长度相等的数组,让左边的任意一个数均小于右边的任意一个数。

定义数的意义:

蛮力法:

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;
    }

注意事项:

蛮力法:

  1. 需要分清楚有多少种情况:
    1. nums1为空或者nums2为空
    2. 总数为偶数还是总数为奇数
  2. target位置定义,target是从通过减法来寻找中位数的位置的,而代码中的i,j是通过加法来完成的,所以要搞好之间的关系,举例子就可以找到规律

递归法:

  1. 交换两个vector类型的方法
  2. i,j每次循环时都需要改变,所以不能在循环体外求得它们得值
  3. 使i增大减小时,我们是通过改变iMin和iMax,然后再用i=(iMin+iMax)/2来完成得
  4. 要处理特殊得情况:
    1. nums1为空或者nums2为空得情况:这时就直接找不是为空得中位数即可(注意i-1,j-1就是它们得中位数索引
    2. 当i,j是它们数组的最后索引时,这时我们就找右边的最小那个数,求出中位数即可(一样i,j就是它们的索引

 

最长回文子串

解题思路:

  1. 蛮力法:使用双循环,每个子串都反转来比较和原子串是否相等,如果相等即可返回记录长度,找出最长的长度;
  2. 蛮力法2.0:暂时还没有看懂,稍后更新;
  3. 动态规划法:以字符串中每个字符和它的下一个字符为中心展开,寻找最长回文子串;

定义数的意义:

蛮力法:

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),超出时间限制;

动态规划法:

  1. start,end的更新数据方式,start是以中心一半向前移,end是以中心一半向后移;
  2. c++中substr方法的使用,第二个参数是子串的长度
  3. 动态函数while循环的向两边展开的条件
  4. 注意为空的情况
  5. len-1为了避免数组索引越界
  6. 左右边界包括了回文串中后两个数,所以要减去不是回文串的字符

字符串转换整数 (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;
        }
    }
};

注意事项:

  1. 要排除开头的全部空格(使用index来解决)
  2. 不要忽略开头为+的情况
  3. 如果中间非数字(包括存在空格),要跳出循环,而不是直接返回数据
  4. 在验证int范围时(INT_MAX (2^31 − 1) 或 INT_MIN (−2^31) ),要分三种情况来返回值:
    1. "2147483648"->2147483647
    2. "-2147483647"->-2147483647
    3. "-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;
        }
    }
};

注意事项:

  1. 栈数为0和1时的判定
  2. 当字符是'}',')',']'时,我们都必须先检查栈是否为空

 

删除排序数组中的重复项

解题思路:

  1. vector自带的删除函数erase法:遇到相同的,使用erase删除掉
  2. 双指针法:找到相同的,把他们全部向前移动,最后返回数量

代码:

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法:

  1. 当你删除一个数时,要记得把i--,这样可以避免少检查数
  2. 这个方法频繁使用到原函数erase,所以效率值很低

双指针法:

  1. 要注意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了

字符串相乘

解题思路:

  1. 最简单粗暴的思路:把num1,num2变成long型再进行相乘,但是无论是哪个类型,都会超出最大长度,所以这个方法不行
  2. 数组位数法: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的情况
    }
};

注意事项:

  1. 要注意结果为0的情况
  2. 在进行进位计算的时候,要进行的是+=操作
  3. 要理解乘法原理计算的代码

反转字符串中的单词 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;
    }
};

注意事项:

  1. index存储的是每个子串的第一个索引,而不是空格的索引值
  2. 每个子串的长度计算
  3. 在最后子串的反转的分割条件用空格并不适用,所以要单独求解

除自身以外数组的乘积

解题思路:

  1. 双指针法:使用左右指针计算乘积,当左右指针重合时停止,但是还是超过了时间限制
  2. 使用两个数组,一个记录顺序的数乘积,另一个记录逆序的数乘积,并反向顺序记录,最后通过规律求解

定义数的意义:

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;
    }
};

注意事项:

  1. ret存储的方式是逆序存储
  2. 在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. 要排除为空和为1的情况
  2. count的求法,取m,n的较小值加1后除于2 
  3. 当前层数和共层数的关系没有等于

螺旋矩阵 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;
    }
};

注意事项:

  1. 我曾使用两个for循环来完成操作实现,但是很明显超出时间限制,所以就需要考虑到map函数,而且map函数存储类型是重要的选择
  2. 当map函数的数据需要遍历时,需要使用iterator类型(迭代)

无重复字符的最长子串

解题思路:

  1. 滑动窗口法1:左闭右开,向右添加字符,直到有重复字符,然后找到最大的那个子串。时间复杂度为O(n)
  2. 滑动窗口法2(优化版):使用map函数,分别记录字符和位置,跳过重复的循环
  3. 数组法:直接使用数组来存储位置,记录左边界的位置,有无重复字符或者遇到较大子串时,就更新最大子串

定义数的意义:

滑动窗口法:

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

注意事项:

滑动窗口法:

  1. 一定要给res初始值
  2. i,j记录的是左右边界位置,而不是索引值,所以长度就是j-i即可
  3. 记得iterator的用法,vector,list,map,set如果要迭代都需要使用到iterator

滑动窗口法(优化版):

  1. 这个的关键在于i位置的决定:
    1. 遇到重复字符时,i变成遇到前一个同样字符的下一个的索引值
    2. 然后在求无重复子串时,当前最大的子串

数组法:

  1. 要给m数组初始化,方式为m[256]={0}
  2. 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;
    }
};

注意事项:

  1. 题目要求的是子序列,所以不能打乱原数组的位置
  2. 如果存在比最小值小的值,就更新最小值
  3. 如果存在比最小值大,但是比中间值小的值就更新中间值
  4. 如果存在比中间值大的数,即可返回true
  5. Min,medium初始值都是最大的INT值
  6. 直接排除数量小于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;
    }
};

按列翻转得到最大值等行数

解题思路:

  1. 蛮力法:先找到两个字符串中相等的部分,然后再消去相同部分中重复的部分,最后再判断是否为公约字符串
  2. 数学法:找出字符串长度的最大公约数,然后判断子串是否为公约字符串即可

定义数的意义:

蛮力法:

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;
    }
};

思路分析:

蛮力法:

找出子串中重复部分是最难的,且每个公约数子串都得找,十分耗时,思路很直接,实现复杂

数学法:

直接找出最大公约数,以最大公约数找到子串,直接判断是否为公约串,判断的方法十分巧妙

 

翻转链表

解题思路:

  1. 常规方法:使用两个链表来分别存储前一个节点和后面的全部节点
  2. 递归法:递归的参数是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;
    }
};

解题分析:

  1. 除数和被除数为0时,都是输出“0”
  2. 在处理分数时,要标志上index再给index添加1
  3. 理清循环数的思路

 

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;
        }
    }
};

解题分析:

  1. 理清一个思路即可,即2^10=4^5=16^2*4
  2. 然后按照递归思路完成解题

 

两数相除

解题思路:

两数相除但是又用不到除法,就只能使用列竖式算除法的原理,计算机的乘法和除法是通过左(乘)右(除)移被除数来完成的,记录被除数移动的位数,当除数大于被除数时,就加上二进制(第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;
    }
};

解题分析:

  1. 这题中最大的困难就是不能搞定INT_MIN变成INT_MAX的转化,最简单的解决方式就是单独找出这两种情况来求解
  2. 为了避免溢出错误,我们把全部数都转化成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. 数组为空时,直接返回-1
    2. 数组的数量为1时,直接比较返回
  2. 注意left和right的递增或者递减时,不能超过数组的有效范围,否则就溢出
  3. 当数组刚好是倒序排序的时候,就有可能会出现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);
    }
};

解题分析:

  1. leftMax和rightMax的求解时,要知道是否要加上它们,如果走完的最大权值都是个负数,就不必理会了,所以max和0来比较
  2. tempMax的结果要和最终结果来比较
  3. 递归方法返回的并不是最终结果,而是当前权值加上左右中的最大权值

 

二叉搜索树的最近公共祖先

解题思路:

  1. 我一开始的思路是使用容器存放他们经过的路径,从而对比路径值找到他们的公共祖先,但是发现对比的过程很复杂,无法实现
  2. 当我们找到规律时,就十分简单易懂了,即当左右值均比根值小时,就递归根的左节点,反则右节点

代码:

/**
 * 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;
        }
    }
};

解题分析:

  1. 这是道明白规律了就十分简单的题目,前提是你必须先找到递归方法的规律
  2. 使用递归时,要返回递归方法,不然会报出没有返回值的执行错误

 

括号生成

解题思路:

  1. 回溯法:找到回溯函数的规律即可,返回条件为cur长度达到要求则返回,递归规则是当(小于n时,就加上(并left+1递归,如果)小于(则符合添加条件,所以加上)并right+1递归
  2. 闭关法:使用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;
    }
};

解题分析:

回溯法:

  1. 回溯函数中,一定要在vector<string>上添加&,不然不能完成编译

闭关法:

  1. 在递归函数中,原本的right就是下一个的left,所以使用循环来完成left的括号数,剩下的数量给right,递归后就完成了回溯
  2. 有一个默认的括号,所以第一个回溯中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]);
        }
    }
};

解题分析:

  1. 这个思想一开始很难想到,需要时间来适应这种思想
  2. 在交换完成一次的递归后,需要把之前交换的数重新复位,以便不影响之后的递归不出现重复现象

 

子集

解题思路:

就像和全排列类似的思路,基线条件为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();
        }
    }
};

解题分析:

  1. 找对基线条件为重点突破点
  2. 递归函数的参数变化值是第二个突破点

 

格雷编码

解題思路:

格雷编码代表連續兩個數之間二進制只存在一個數字的差別,我們單從定義中很難找到它們之間的規律,其實就是使用鏡面思想,在前面已經的步驟中,鏡面複製全部數據,然後再在它們的前面加上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;
    }
};

注意事項:

  1. 在轉化成十進制時,string的索引值是和我們轉化的順序相反的,所以要從后開始且注意次方值
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值