LeetCode之路

LeetCode之路


热题HOT100

双指针

53 最大(连续)子序和

做的时候觉得是一个贪心,即如果当前想加的下一个结点>=0且当前子串和<0,则放弃前面的子串,把头指针指到此点。再做一些特判(vector长度为1,全为负值)

最后发现其实是个dp问题

定义dp[i]为:(起始设为1,不然怪怪的) 在1—i 这个串中,所有以i为结尾的子串中的最大子串和,即 1—i/2—i/3—i等等等,这个串可能不是1—i这个串中的最大字串和,但是就是这么定义,因为只有这样dp[i]才能跟第i+1的节点联系上,才好使用dp。

dp[i+1]=max(dp[i],0)+第i+1节点值

结果:dp[1],dp[2],dp[3]…dp[n]中的max

//双指针+贪心(类似dp)+不断放弃<0

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int i=0; //头指针
        int j=0; //尾指针
        int maxv=nums[0];//当前最大和
        int curv=nums[0];//当前和
        if(nums.size()==1)//特判
        {
            return nums[0];
        }
        int maxx=nums[0];
        for(int p=0;p<nums.size();p++) //特判全负数
        {
            maxx=(maxx>=nums[p])?maxx:nums[p];
        }
        while(j<nums.size()-1)
        {
            if(nums[j+1]>=0&&curv<0) //放弃前面的
            {
                j++;
                curv=nums[j];
                i=j;
            }
            else //不放弃
            {
                j++;
                curv+=nums[j];
            }
            maxv=(curv>maxv)?curv:maxv;
        }
        return max(maxv,maxx);
    }
};

dp解法

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        if(nums.size()==1) return nums[0];;
        vector<int> dp(nums.size());
        dp[0]=nums[0];
        int maxv=nums[0];
        for(int i=1;i<nums.size();i++)
        {
            dp[i]=max(dp[i-1],0)+nums[i];
            maxv=(dp[i]>maxv)?dp[i]:maxv;
        }
        return maxv;
    }
};

11 成水最多的容器

双指针,数学可证
在这里插入图片描述

//两侧双指针居中移动 每次都移动较短的边 数学可证
class Solution {
public:
    int maxArea(vector<int>& height) {
        int i=0;
        int j=height.size()-1;
        int curv=0;
        int maxv=min(height[i],height[j])*(j-i);
        while(i!=j)
        {
            if(height[i]>=height[j])j--;
            else i++;
            curv=min(height[i],height[j])*(j-i);
            maxv=(curv>maxv)?curv:maxv;
        }
        return maxv;
    }
};

15 三数之和

1.排序 2.外层确定第一个元素进行遍历 3.内层另两个元素用双指针确定 4.最后去重

//排序+去重+双指针
//双指针:在过程中比较,决定移动哪个指针
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res; //输出
        sort(nums.begin(),nums.end());
        int n = nums.size();
        if(n<3)return res;
        for(int i=0;i<=n-3;i++)
        {
            int j=i+1; //双指针
            int k=n-1;
            if(nums[i]==0&&nums[j]==0&&nums[k]==0){res.push_back({nums[i],nums[j],nums[k]});break;}//特判
            if(nums[i]>0)break;  //特判
            while(j!=k)
            {
                if(nums[i]+nums[j]+nums[k]==0)
                {
                    res.push_back({nums[i],nums[j],nums[k]});
                    j++;
                }
                else if(nums[i]+nums[j]+nums[k]<0)j++;
                else k--;
            }
        }
        sort(res.begin(),res.end());//去重
        res.resize(unique(res.begin(),res.end())-res.begin());
        return res;
    }
};

利用hash
1.构建hash表,统计每个元素的出现次数
2.特判[0,0,0]
3.特判[a,a,b]
4.去重
5.两层循环定两个元素,另外一个元素用hash查
6.结果去重

75 颜色分类

//双指针(三)+扫描一遍
//p0指针:初始指向开头,过程中指向0的末尾的下一个
//p2指针:初始指向结尾,过程中指向2的开端的前一个
//i指针:用于遍历
//当i遍历到0时,与p0指向的元素swap,同时p0++,i++
//当i遍历到2时,与p2指向的元素swap,同时p2--,i不变
//上述操作i变化不同的原因是,i和p0都是从左向右遍历,所以当nums[i]与nums[p0]交换后,当前的nums[i](之前的nums[p0])一定不是0/2,因为i已经遍历过该部分,已经处理过;另一方面,也要保证i>=p0(不然会出bug),综上所述,i++。相反,p2之前指向的元素没有处理过,所以交换后不能i++。
//当i遍历到1时,i++ 
class Solution {
public:
    void sortColors(vector<int>& nums) {
        int p0=0;
        int p2=nums.size()-1;
        int i=0;
        while(i<=p2)
        {
            if(nums[i]==0) swap(nums[i++],nums[p0++]);
            else if(nums[i]==2) swap(nums[i],nums[p2--]);
            else i++;
        }
    }
};

283 移动零

//双指针(一个扫描,一个标定位置)
//px指针:初始指向开头,过程中指向非零元素的下一个
//i指针:用于遍历
//nums[i]==0时,i++
//nums[i]!=0时,nums[i]与nums[px]进行swap,i++,px++
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int px=0;
        for(int i=0;i<nums.size();i++)
            if(nums[i]!=0) swap(nums[i],nums[px++]);   
    }
};

42 接雨水

/*
双指针
用两个指针把每一轮想计算的部分圈起来
p0:本轮计算起始点
p1:本轮计算结束点
定位每一轮的p0:遍历寻找第一个nums[p0]>nums[p0+1]的p0;初始p1=p0+1
定位每一轮的p1的两种情况:(a)nums[p1]>=nums[p0] 直接圈起 (b)nums[p1]<nums[p0]的所有p1中选择max(nums[p1])对应的p1
计算:画图看(减法)
特殊情况:若p1==p0+1,则需要将p1后移一位,重新开始下一轮
*/
class Solution {
public:
    //计算每一轮的雨水量
    int perTrap(int p0,int p1,vector<int>& nums)
    {
        int res1=min(nums[p0],nums[p1])*(p1-p0-1);
        int res2=0;
        int maxSub=min(nums[p0],nums[p1]);
        for(int i=p0+1;i<p1;i++)
           res2+=nums[i];
        return res1-res2;
    }
    int trap(vector<int>& nums) {
        if(nums.size()<3) return 0;
        int p0=0;
        int p1=0;
        int res=0;
        int mark=true;
        while(p1!=nums.size()-1)
        {
            //定位该轮p0,p1初始位置
            for(int i=p0;i<nums.size()-1;i++)
            {
                if(nums[i]!=0 && nums[i+1]<nums[i])
                {
                    p0=i;
                    p1=i+1;
                    break;
                }
                if(i==nums.size()-2)
                mark=false; //本轮无雨水能积累
            }
            if(mark==false)break;//无雨水积累,直接跳出循环
            //定位新一轮p1的位置
            int curp1=p1;
            int bestp1=p1;
            while(curp1<nums.size())
            {
                if(nums[curp1]>=nums[p0])
                {
                    bestp1=curp1;
                    break;
                }
                else if(nums[curp1]>0)
                {
                    bestp1=(nums[curp1]>=nums[bestp1])?curp1:bestp1;
                    curp1++;
                }
                //为0
                else curp1++; 
            }
            p1=bestp1;
            //特判:如果p1没更新,即p1==p0+1。该情况为p0,p1相邻,此时需要将p0后移一个,重新开始下一轮
            if(p1==p0+1) p0++;
            else 
            {
                res+=perTrap(p0,p1,nums);
                p0=p1;//将p0指向p1,开始下一轮
            }
        }
        return res;
    }
};

优化做法:初始 p1=p0+2 && 雨水计算部分减法需要注意(maxSub)

/*
双指针
用两个指针把每一轮想计算的部分圈起来
p0:本轮计算起始点
p1:本轮计算结束点
定位每一轮的p0:遍历寻找第一个nums[p0]>nums[p0+1]的p0;初始p1=p0+2
定位每一轮的p1的两种情况:(a)nums[p1]>=nums[p0] 直接圈起 (b)nums[p1]<nums[p0]的所有p1中选择max(nums[p1])对应的p1
计算:画图看(减法)
*/
class Solution {
public:
    //计算每一轮的雨水量
    int perTrap(int p0,int p1,vector<int>& nums)
    {
        int res1=min(nums[p0],nums[p1])*(p1-p0-1);
        int res2=0;
        int maxSub=min(nums[p0],nums[p1]);
        for(int i=p0+1;i<p1;i++)
        {
            if(nums[i]>maxSub) res2+=maxSub; //防止减去了超过边界的值
            else res2+=nums[i];
        }
        return res1-res2;
    }
    int trap(vector<int>& nums) {
        if(nums.size()<3) return 0;
        int p0=0;
        int p1=0;
        int res=0;
        int mark=true;
        while(p1!=nums.size()-1)
        {
            //定位该轮p0,p1初始位置
            for(int i=p0;i<nums.size()-2;i++)
            {
                if(nums[i]!=0 && nums[i+1]<nums[i])
                {
                    p0=i;
                    p1=i+2;
                    break;
                }
                if(i==nums.size()-3)
                mark=false; //本轮无雨水能积累
            }
            if(mark==false)break;//无雨水积累,直接跳出循环
            if(p0>=nums.size()-2)break;//无雨水积累(针对没进入for的部分)
            //定位新一轮p1的位置
            int curp1=p1;
            int bestp1=p1;
            while(curp1<nums.size())
            {
                if(nums[curp1]>=nums[p0])
                {
                    bestp1=curp1;
                    break;
                }
                else if(nums[curp1]>0)
                {
                    bestp1=(nums[curp1]>=nums[bestp1])?curp1:bestp1;
                    curp1++;
                }
                //为0
                else curp1++; 
            }
            p1=bestp1;
            if(p1==p0+1)//一直为0的特判;一直为0,p1没更新,所以还是p0+1
                break;
            res+=perTrap(p0,p1,nums);
            p0=p1;//将p0指向p1,开始下一轮
        }
        return res;
    }
};

数学解法 时间复杂度O(n)

class Solution {
//左侧遍历+右侧遍历-全部体积-柱子体积

public:
    int trap(vector<int>& height) {
        int n=height.size();
        int max1=0,max2=0,max3=0,max4=0;
        int x1=0,x2=0,x3=0,x4=0;
        for(int i=0;i<n;i++)
        {
            max1=(height[i]>max1)?height[i]:max1;
            max2=(height[n-i-1]>max2)?height[n-i-1]:max2;
            x1+=max1;
            x2+=max2;
            max3=(height[i]>max3)?height[i]:max3;
            x4+=height[i];
        }
        x3=max3*n;
        return x1+x2-x3-x4;
    }
};

哈希表

1 两数之和

leetcode #1 两数之和 应用hash O(1)查找特性 查找target-x

一般用count即可



class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {   

        unordered_map<int,int>hashtable;
        for(int i=0; i!=nums.size(); ++i)
        {
            auto it = hashtable.find(target-nums[i]); // 应用find
            if(it!=hashtable.end())
            {
                return {it->second, i};
            }
            hashtable[nums[i]]=i;
        }
        return {};
    }    
    
};

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,int> hashtable;
        for (int i = 0; i != nums.size(); i++) {
            if (hashtable.count(target - nums[i])) //应用count
            {
                return { hashtable[target - nums[i]], i };
            }
            hashtable[nums[i]] = i;    
        }
        return {};
    }
};

20 有效的括号

先剪枝
需要用到两两对应时,想想hash

class Solution {
public:
    bool isValid(string s) {
        stack<char> mystack;
        unordered_map<char,char>hashtable = {{'(',')'},{'[',']'},{'{','}'}};
        if(s.size()%2!=0)return false;
        for(int i=0;i<s.size();i++)
        {
            if(s[i]=='('||s[i]=='['||s[i]=='{') mystack.push(s[i]);
            else if(!mystack.empty() && hashtable[mystack.top()]==s[i]) mystack.pop();
            else return false;
        }
        if(!mystack.empty())return false;
        return true;
    }
};

3 无重复的最长子串

<下面有后续的优化>

首先想到queue,其次想到hash来实现字符的状态标识,字符状态标识想到利用mark[int(‘A’)]这种形式的bool数组实现,asii码0-128,所以创造一个bool mark[150]

注:遇到下一个重复的字符后,当队列里之前的字符以及重复的字符pop掉后,要记得加入当前这个重复的字符,同时更新length,最大长度的计算最外层循环每循环一次计算一次即可。

//队列+asii码版hash+遍历
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        queue<char> que;
        int maxl=0;    //最大长度
        int length=0; //当前长度
        bool mark[150];  //构造asii码版hash
        memset(mark,true,sizeof(mark)); //true:该字符之前未出现  false:该字符之前已出现
        for(int i=0;i<s.size();i++)
        {
            int tmp = int(s[i]);   //该字符对应的asii码       
            if(mark[tmp]) //之前未出现过
            {
                que.push(s[i]);
                mark[tmp]=false;
                length++;
            }
            else    //出现过
            {
                while(1)  //去掉重复字符以及其之前的字符,为继续读入新字符做准备
                {
                    mark[int(que.front())]=true; //更新即将弹出的字符状态
                    length--;
                    if(que.front()==s[i]) //到了重复的那个字符
                    {
                        que.pop();
                        break;
                    }
                    else
                    {
                        que.pop();
                    }
                }
                que.push(s[i]);   //重复的下一个的录入!!!
                mark[tmp]=false;
                length++;
            }  
            maxl=(length>maxl)?length:maxl; //更新maxl
        }
        return maxl;
    }
};

优化算法
双指针:其实根本没有必要去模拟队列,只需两个指针,一个模拟当前队头,一个模拟当前队尾,这样可以将空间复杂度从O(n)优化到 O(1),直接在原string上进行操作
拓展
若需要输出该串,只需记录maxl时把首指针和尾指针的下标记下来。

//双指针代替队列+asii码版hash+遍历
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int maxl=0;    //最大长度
        int length=0; //当前长度
        int begin=0;  //该串的起始下标
        int end=0;    //该串的终止下标
        int q1=0;        //队头指针
        int q2=0;        //队尾指针
        bool mark[150];  //构造asii码版hash
        memset(mark,true,sizeof(mark)); //true:该字符之前未出现  false:该字符之前已出现
        while(q2<s.size()) 
        {
            int tmp = int(s[q2]);   //该字符对应的asii码       
            if(mark[tmp]) //之前未出现过
            {
                q2++;
                mark[tmp]=false;
                length++;
            }
            else    //出现过
            {
                while(1)  //去掉重复字符以及其之前的字符,为继续读入新字符做准备
                {
                    mark[int(s[q1])]=true; //更新即将弹出的字符状态
                    length--;
                    if(s[q1]==s[q2]) //到了重复的那个字符
                    {
                        q1++;
                        break;
                    }
                    else
                    {
                        q1++;
                    }
                }
                q2++;
                mark[tmp]=false;
                length++;
            }
            if(length>maxl)     //更新maxl,begin,end
            {
                maxl=length;
                begin=q1;
                end=q2;
            }  
        }
        //cout<<s.substr(begin,maxl)<<endl; //输出该字串
        return maxl;
    }
};

进一步简化hash;以及将hash的value赋为下标(类似 1.两数之和)
注意:不论队首是否需要调整,队尾所对应的hash值都需要调整

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int i=0;//队首
        int j=0;//队尾
        vector<int> mp(128,-1);//mp[i]=t; i表示字母,t表示字母出现的位置,为-1表示从未出现过
        int res=0;
        while(j<s.size())
        {
            int loc=mp[int(s[j])];
            if(loc>=0) 
            { //i后移且更新mp
               while(i<loc){mp[int(s[i])]=-1;i++;}
               i++;
            }
            mp[int(s[j])]=j;
            res=((j-i+1)>res)?j-i+1:res;
            j++;
        }
        return res;
    }
};
//hash容器版本
#include<bits/stdc++.h>
class Solution {
public:
    /**
     * 
     * @param arr int整型vector the array
     * @return int整型
     */
    int maxLength(vector<int>& arr) {
        // write code here
        int n=arr.size();
        if(n==1||n==0) return n;
        int maxx=0;
        int head,tail=0;
        unordered_map<int,int> hash;
        for(int i=0;i<n;i++)
        {
            if(!hash.count(arr[i])) hash[arr[i]]=i;
            else 
            {
                maxx=(tail-head>maxx)?tail-head:maxx;
                head=hash[arr[i]]+1;
                //clear掉head前的东西
                hash.clear();
                for(int j=head;j<=tail;j++) hash[arr[j]]=j;
            }
            tail++;
        }
        return max(maxx,tail-head);
    }
};

287 寻找重复数

方法一:哈希表
时间复杂度 O(n)
空间复杂度 O(n)
注:一般的数据结构,新开一个长度为n的结构,空间复杂度就是O(n)。若空间复杂度为O(1),一般情况下是直接对原数据结构进行操作。

//hash统计出现次数,遇见多次的直接跳出
class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        unordered_map<int,int> mp;
        int key;
        for(int i=0;i<nums.size();i++)
        {
            if(mp.count(nums[i])){key=nums[i];break;} 
            else mp[nums[i]]=1;
        }
        return key;
    }
};

方法二:快慢指针(链表题)
题解

136 只出现一次的数字

hash解法
空间复杂度O(n)
时间复杂度O(n)

/*
hash表统计出现次数
注意:O(Kn)=O(n)
*/
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        unordered_map<int,int> mp;
        int key;
        for(int i=0;i<nums.size();i++)
        {
            if(!mp.count(nums[i]))
            {
                mp[nums[i]]=1;
            }
            else mp[nums[i]]++;
        }
        for(int i=0;i<nums.size();i++)
        {
            if(mp[nums[i]]==1)
            {
                key=nums[i];
                break;
            }
        }
        return key;
    }
};

异或解法
空间复杂度O(1)
时间复杂度O(n)

/*
异或运算:
a⊕a=0
a⊕0=a
a1⊕a1⊕a2⊕a2⊕a3=a3
*/
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res=0;
        for(int i=0;i<nums.size();i++)
        {
            res=res^nums[i];
        }
        return res;
    }
};

49 字母异位词分组

/*
构建hash表:key为sort后的字符串;value为vector<string>,用于存储所有该类的字符串
*/
class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        //hash
        unordered_map<string,vector<string>> mp;
        for(auto elem:strs)
        {
            string cur = elem;
            sort(elem.begin(),elem.end());
            mp[elem].push_back(cur);
        }
        //输出
        vector<vector<string>> res;
        for(auto elem:mp)
            res.push_back(elem.second); //直接整体push_back进来
        return res;
    }
};

560 和为k的子数组

/*
连续和为k -> 前缀和简化
不需要输出子数组,只需输出次数 -> 使用hash记录前缀和的次数
解析链接:https://leetcode-cn.com/problems/subarray-sum-equals-k/solution/dai-ni-da-tong-qian-zhui-he-cong-zui-ben-fang-fa-y/
统计前缀和 O(n)
遍历前缀和数组,使用hash统计各前缀和的出现次数,遍历过程中判断hash[当前前缀和-k]
总复杂度O(n)
*/
class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        //统计前缀和
        vector<int> preSum;
        int last = 0;
        for(auto elem:nums)
        {
            preSum.push_back(last+elem);
            last+=elem;
        }
        //遍历+hash
        unordered_map<int,int> mp;
        mp[0]=1;//前缀和为0的情况=1
        int res=0;
        for(auto elem:preSum)
        {
            //首先进行判断,需要注意有多个答案的情况(直接加mp[elem-k]即可) 
            if(mp.count(elem-k)) res+=mp[elem-k];
            //之后,统计本次,注意前后顺序,防止k=0时多算本次的情况
            if(!mp.count(elem))mp[elem]=1;
            else mp[elem]++;
        }
        return res;
    }
};

1737 满足三条件之一需改变的最少字符数

/*
hash-->统计26个字母可以用数组作hash  <注意:26的遍历不算复杂度>
讨论+遍历
*/
class Solution {
public:
    int ans;
    void x(vector<int> a,vector<int> b) //使得b全在a前面
    {
        for(int i=1;i<26;i++) //i为分界点
        {
            int tmp=0;
            for(int j=i;j<26;j++) tmp+=a[j];
            for(int j=0;j<i;j++) tmp+=b[j];
            ans=min(ans,tmp);
        }   
    }
    int minCharacters(string a, string b) 
    {
        vector<int> ca(26),cb(26); //这个方法可以直接给vector设置大小,且初值为0
        int n=a.size(),m=b.size();
        for(char c:a) ca[c-'a']++; //string的迭代简化
        for(char c:b) cb[c-'a']++;
        ans=n+m;
        
        //第三种情况
        for(int i=0;i<26;i++)
            ans=min(ans,n+m-ca[i]-cb[i]);

        //第一二种情况
        x(ca,cb);
        x(cb,ca);

        return ans;
    }
};

前缀和解法题解,基本类似上面解法,更巧妙

两个大数据集找交集(阿里面试)

若电脑内存只有16G,两个数据集都有上百G,如何找交集?

利用hash嵌套的思想
首先,将数据集取模,按结果的不同把每个数据集分成若干个小块,因为两者模相同的小块才有可能有交集,所以将两个模相同的小块拿到电脑中,进行取交集。
取交集的思路:遍历第一个小块,建hash,遍历第二个,有重复的部分,拿出来即可。

739 每日温度

/*
单调递减栈:维护一个栈,保证里面的元素从栈底至栈顶单调递减

构造pair<温度,序列号> 维护pair的递减栈
若当前元素温度>栈顶元素温度,栈顶元素对应的输出=当前元素序列号-栈顶元素序列号,再将栈顶pop,继续比较新栈顶
若当前<=栈顶,则直接压栈
*/
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& T) {
        stack<pair<int,int>> st;
        vector<int> res;
        res.resize(T.size(),0);
        for(int i=0;i<T.size();i++)
        {
            while(!st.empty() && T[i]>st.top().first) //短路算法 先算前面
            {
                res[st.top().second]=i-st.top().second;
                st.pop();
            }
            if(st.empty() || T[i]<=st.top().first) st.push(make_pair(T[i],i));
        }
        return res;
    }
};

后来发现根本不用pair,因为有序号自然可以知道所对应的温度

关于单调栈的使用情况

  1. 每个元素找到它右边第一个比它大的元素的位置,求它们的距离
    从左到右遍历,维护一个从栈底到栈顶递减的栈,因为遇到新元素大于栈顶元素时,栈顶元素就遇到了对应的比它大的最近元素,要弹栈。这也是栈从底到顶单调递减的原因
  2. 每个元素找到它右边第一个比它小的元素的位置,求它们的距离
    从左到右,维护栈底到栈顶递增的栈,遇到递减元素,弹栈
  3. 每个元素找到它左边第一个比它大的元素的位置,求它们的距离
    相当于把1的数组头尾调过来,只需要从右向左遍历维护栈底到栈顶递减栈即可
  4. 每个元素找到它左边第一个比它小的元素的位置,求它们的距离
    相当于把2的数组头尾调过来,只需要从右向左遍历维护栈底到栈顶递增栈即可

155 最小栈

辅助栈:可以直接把两个栈结合成一个pair为元素的栈;也可以再建立一个栈。

/*
构造栈内元素pair,pair.first存值,pair.second为当其为栈顶时,栈内最小元素的值。
*/
class MinStack {
public:
    stack<pair<int,int>> st;
    /** initialize your data structure here. */
    MinStack() {
    }
    void push(int x) {
        if(st.empty()) st.push(make_pair(x,x));
        else st.push(make_pair(x,min(x,st.top().second)));
    }
    
    void pop() {
        st.pop();
    }
    
    int top() {
        return st.top().first;
    }
    
    int getMin() {
        return st.top().second;
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(x);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->getMin();
 */
class MinStack {
public:
    /** initialize your data structure here. */
    MinStack() {
    }
    stack<int> st;
    stack<int> mst;
    
    void push(int x) {
        st.push(x);
        if(mst.empty()||x<mst.top()) mst.push(x);
        else mst.push(mst.top());    
    }
    
    void pop() {
        st.pop();
        mst.pop();
    }
    
    int top() {
        return st.top();
    }
    
    int getMin() {
        return mst.top();
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(x);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->getMin();
 */

394 字符串解码

本题难点在于括号内嵌套括号,需要从内向外生成与拼接字符串,这与栈的先入后出特性对应。

/*
遍历s,维护多个栈
栈1:存数字 用stack实现,又因为是string类型,所以设一个stTmp,存当前一段的数字,遇到[时,把之前那一段存到stack里,stTmp清空
栈2:存字母字符 用string实现,遇到],从栈3栈顶元素对应下标的开始复制,按栈1栈顶元素的值决定复制次数(for+substr)
栈3:存遇到'['时栈2的大小,所以栈3栈顶元素为 最近的一个'['后的第一个字母在栈2中的下标
*/
class Solution {
public:
    string decodeString(string s) {
        stack<int> st;
        string res;
        stack<int> mark;
        string stTmp;
        for(int i=0;i<s.size();i++)
        {
            cout<<"i="<<i<<endl;

            if(s[i]>='0'&&s[i]<='9')
            {   
                stTmp+=s[i];
                cout<<"stTmp:"<<stTmp<<endl;
            } 
            else if(s[i]>='a'&&s[i]<='z') {res+=s[i];cout<<"res:"<<res<<endl;}
            else if(s[i]=='[') 
            {
                st.push(atoi(stTmp.c_str()));
                stTmp="";
                cout<<"st"<<" top:"<<st.top()<<" size:"<<st.size()<<endl;
                mark.push(res.size());
                cout<<"mark"<<" top:"<<mark.top()<<" size:"<<mark.size()<<endl;
            }
            else if(s[i]==']')
            {
                string tmp = res.substr(mark.top(),res.size()-mark.top());
                cout<<"tmp:"<<tmp<<endl;
                mark.pop();
                cout<<"st top:"<<st.top()<<endl;
                for(int j=0;j<st.top()-1;j++)
                {
                    res+=tmp;
                }
                cout<<"res:"<<res<<endl;
                st.pop();
            }
        }
        return res;
    }
};

注:可简化为只有遇到[和]时再进行栈操作 相关链接
构造一个stack<pair<int,string>>,int存这个‘[’之前的num,string存这个‘[’之前的res。

#include<bits/stdc++.h>
using namespace std;
int main()
{
    string s;
    cin>>s;
    stack<pair<int,string> > st;
    int n=s.size();
    int multi=0;
    string res="";
    for(int i=0;i<n;i++)
    {
        if(s[i]>='0'&&s[i]<='9') multi=s[i]-'0';
        else if(s[i]>='a'&&s[i]<='z') res+=s[i];
        else if(s[i]=='[')
        {
            st.push(make_pair(multi,res));
            multi=0;
            res="";
        }
        else if(s[i]==']')
        {
            int k = st.top().first;
            string s="";
            while(k--)
            {
                s+=res;
            }
            res=st.top().second+s;
            st.pop();
        }
    }
    cout<<res<<endl;
    return 0;
}

int型整数逆序(华为笔试)

要注意溢出

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int input;
    cin>>input;
    int x=input;
    stack<int> st;
    while(x)
    {
        st.push(x%10);
        x=x/10;
    }
    long output=0;
    int i=1;
    while(!st.empty())
    {
        output+=i*st.top();
        i=i*10;
        st.pop();
    }
    cout<<output<<endl;
    return 0;
}

用两个栈实现队列(阿里面试题)

这里是引用

347 前k个高频元素

nlogn的解法

/*
hash统计出现次数 o(n)
hash装入vector<pair<key,vaule>> 按value进行从大到小sort o(n) o(nlogn)
循环取前k个key
总复杂度 o(nlogn)
*/
class Solution {
public:
    bool static cmp(pair<int,int> a, pair<int,int> b) //写在类内 需要+static
    {
        return a.second>b.second; //按第二个元素从大往小排序
    }
    vector<int> topKFrequent(vector<int>& nums, int k) {
        //hash统计次数
        unordered_map<int,int> mp;
        for(auto elem:nums)
        {
            if(!mp.count(elem)) mp[elem]=1;
            else mp[elem]++;
        }
        //排序
        vector<pair<int,int>> tmp;
        for(auto elem:mp)
          //tmp.push_back(make_pair(elem.first,elem.second)); //另一种pair写法
            tmp.push_back({elem.first,elem.second});
        
        sort(tmp.begin(),tmp.end(),cmp);
        //取结果
        vector<int> res;
        for(int i=0;i<k;i++)
            res.push_back(tmp[i].first);
        return res;
    }
};

时间复杂度为O(nlogk)的解法(优先队列作堆)

/*
hash统计出现次数 o(n)
维护一个长度为k的优先队列(value最小的放在队首),遍历hash,每次将遍历的元素的value值与队列队首元素的value比较,决定插入与否。插入环节时间复杂度logk,所以总复杂度nlogk
输出优先队列里的key值
总复杂度 o(nlogk)
*/
class Solution {
public:
    struct cmp{
        bool operator ()(pair<int,int> a, pair<int,int> b)
        {
            return a.second>b.second; //第二个元素最小值优先
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        //hash统计次数
        unordered_map<int,int> mp;
        for(auto elem:nums)
        {
            if(!mp.count(elem)) mp[elem]=1;
            else mp[elem]++;
        }
        //维护长度为k的优先队列
        priority_queue<pair<int,int>,vector<pair<int,int> >,cmp > que;
        for(auto elem:mp)
        {
            if(que.size()<k) que.push(make_pair(elem.first,elem.second));
            else if(elem.second>que.top().second && que.size()==k) 
            {
                que.pop();
                que.push(make_pair(elem.first,elem.second));
            }
        }
        //取结果
        vector<int> res;
        int n = que.size();
        for(int i=0;i<n;i++)
        {
            res.push_back(que.top().first);
            que.pop();
        }
        return res;
    }
};

215 数组中的第K个最大元素

/*
优先队列作堆(小顶堆)
维护一个长度为k的优先队列,每次与堆顶比较,大于堆顶即插入
最后输出堆顶
nlogk
*/
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int,vector<int>,greater<int>> que;
        for(auto elem:nums)
        {
            if(que.size()<k) que.push(elem);
            else if(elem>que.top()) 
            {
                que.pop();
                que.push(elem);
            }
        }
        return que.top();
    }
};

扩展 O(n)求topk的做法
复杂度O(n)+O(2/n)+O(4/n)+…
类似快排
但这种算出的topk内部没有大小顺序,只能确定topk这个元素和所有topk都有哪些

第一次partition,划分之后:<快排的每一次遍历> 
与快排的不同之处:从大到小排序,舍弃另一半

i = partition(arr, 1, n);

如果i大于k,则说明arr[i]左边的元素都大于k,于是只递归arr[1, i-1]里第k大的元素即可;
如果i小于k,则说明说明第k大的元素在arr[i]的右边,于是只递归arr[i+1, n]里第k-i大的元素即可;
画外音:这一段非常重要,多读几遍。

这就是随机选择算法randomized_select,RS,其伪代码如下:

int RS(arr, low, high, k){

if(low== high) return arr[low];

i= partition(arr, low, high);

temp= i-low; //数组前半部分元素个数

if(temp>=k)

return RS(arr, low, i-1, k); //求前半部分第k大

else

return RS(arr, i+1, high, k-i); //求后半部分第k-i大

}

/代码如下:由快排代码更改得来
class Solution {
public:
    int qsort(vector<int> &nums,int left,int right,int k)
    {
        int x=nums[left];
        int i=left;
        int j=right;
        while(i<j)
        {
            while(i<j&&nums[j]>=x) j--;
            if(i<j) nums[i++]=nums[j];
            while(i<j&&nums[i]<=x) i++;
            if(i<j) nums[j--]=nums[i];
        }
        nums[i]=x;
        int tmp = i-left; //因为k代表的是left-right这个部分的第几个 所以要求出tmp进行比较
        if(tmp==k) return x;
        else if(tmp>k) return qsort(nums,left,i-1,k);
        else return qsort(nums,i+1,right,k-tmp-1); //k-tmp-1:可以这么想,当tmp=0时,k若不减一就没变化了
    }
    int findKth(vector<int> a, int n, int K) {
        // write code here
        int k = n-K; //找第k小的数 k在0到n-1之间
        return qsort(a,0,n-1,k);
    }
};

贪心

621 任务调度器

方法1 模拟+贪心

注意:
bestchoice和choice遍历时的初值设置,可以先在循环外设best的初值(设一个非常特殊的值,遍历不到的),然后在循环内部进行特判,来实现初值的赋值。

/*
greedy:在每次选择时,选择当前状况下可选列表中剩余最多的task O(26n)

构造hash表,key值为任务名,value为下面的vector
vector<pair<int,int>> 第一个存该任务的剩余个数,第二个存该任务可以使用的最早时间

*/
class Solution {
public:
    int leastInterval(vector<char>& tasks, int n) {
        unordered_map<char,pair<int,int> > mp;
        //build hash
        for(auto elem:tasks)
        {
            if(!mp.count(elem)) mp[elem]=make_pair(1,0);
            else mp[elem].first ++;
        }
        // greedy
        int num=tasks.size();
        char choice; //本轮初步选择
        char choiceBest;//最佳最佳选择
        bool mark = true; //本轮是否可以执行task
        int i = 0; //time
        for(;num!=0;i++)
        {
            choiceBest='0';
            for(auto elem:mp)
            {
                if(mp[elem.first].first > 0 && mp[elem.first].second <= i)
                {
                    choice = elem.first;
                    mark = true;
                    if(choiceBest == '0' || mp[choiceBest].first < mp[choice].first)
                       {
                           choiceBest = choice;
                       }  
                }               
            } 
            if(mark == true)
            {
                //cout<<choiceBest<<" ";
                mp[choiceBest].first--; //cout<<mp[choiceBest].first<<" ";
                mp[choiceBest].second = i+n+1;  
                num--;    
            }
            //else cout<<"null ";
            mark = false;
        }
        return i;
    }
};

方法2 桶思想

class Solution {
public:
    int leastInterval(vector<char>& tasks, int n) {
        int len = tasks.size();
        vector<int> hash(26,0);
        int maxx=0;
        for(auto elem:tasks) 
        {
            hash[elem-'A']++;
            maxx=(hash[elem-'A']>maxx)?hash[elem-'A']:maxx;
        }
        int k=0;
        for(auto elem:hash)
        {
            if(elem==maxx) k++;
        }
        return max(len,(maxx-1)*(n+1)+k);
    }
};

要注意 并列的if 如果不想有相交的情况,要用 if/else if

55 跳跃游戏

/* 
维护能跳至的最远距离k <只要能跳到最远点,最远点之前的点全能跳到>
只要起跳点i<=k,则能跳到
*/
class Solution {
public:
    bool canJump(vector<int>& nums) {
        int k=0;
        for(int i=0;i<nums.size();i++)
        {
            if(i>k) return false;
            k=max(k,i+nums[i]);
        }
        return true;
    }
};

406 根据身高重建队列

注:sort用于vector时

sort(vec.begin(),vec.end(),cmp);
/*
hint: What can you say about the position of the shortest person?
If the position of the shortest person is i, how many people would be in front of the shortest person?

从身高最小的人开始填,其对应的pair的第二个元素==所填位置之前的空格数目
若身高有相同,先填第二个元素大的(试试看就知道)

greedy:从身高小的入手
*/
class Solution {
public:
    bool static cmp(vector<int> a,vector<int> b)
    {
        if(a[0]==b[0]) return a[1]>b[1];
        else return a[0]<b[0];
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        vector<vector<int>> que;
        int n=people.size();
        que.resize(n);
        bool mark[n];
        memset(mark,0,sizeof(mark));

        sort(people.begin(),people.end(),cmp); //.begin .end
        for(int i=0;i<n;i++)
        {
            int x=people[i][1];
            for(int j=0;j<n;j++)
            {
                if(x==0 && mark[j]==false) 
                {
                    que[j]={people[i][0],people[i][1]};
                    mark[j]=true;
                    break;
                }
                if(mark[j]==false) x--;
            }
        }
        return que;
    }
};

排序

56 合并区间

/*
按第一个元素从小到大(第一个元素相同时,第二个元素从大到小(或从小到大))排序 nlogn
遍历 前一个元素的second与后一个的first和second比较 n
*/
class Solution {
public:
    bool static cmp(vector<int> &a,vector<int> &b)
    {
        if(a[0]==b[0]) return a[1]>b[1];
        else return a[0]<b[0];
    }
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        int n = intervals.size();
        if(n==1) return intervals;

        vector<vector<int>> res;
        sort(intervals.begin(),intervals.end(),cmp);
        res.push_back(intervals[0]);
        int x=0;
        for(int i=1;i<intervals.size();i++)
        {
            int cur1 = res[x][0];
            int cur2 = res[x][1];
            if(cur2>=intervals[i][0] && cur2<intervals[i][1])
            {
                res.pop_back();
                res.push_back({cur1,intervals[i][1]});
            }
            else if(cur2<intervals[i][0])
            {
                res.push_back(intervals[i]);
                x++;
            }
        }
        return res;
    }
};

关于tree结构体的一些操作

定义

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
 1. TreeNode* a //a是一个指向TreeNode结构体的指针
 2. a->val,a->left,a->right / (*a).val,(*a).left,(*a).right //1的操作
 3. TreeNode* a = new TreeNode();//TreeNode()调用构造函数,新建一个节点,new的返回类型为指针,所以前面是TreeNode* a
 4. TreeNode b //b是结构体
 5. TreeNode b(); a=&b; //这两句就相当于3

建树

前序建树

TreeNode* buildTree() //递归建树(前序建立)
{
    int a;
    cin>>a;
    if(a==-1) return nullptr;
    else
    {
        TreeNode* root = new TreeNode(a);
        root->left = buildTree();
        root->right = buildTree();
        return root;
    }
}
若为列表
vector<int> vec;
int p = 0;
TreeNode* buildTree() //递归建树(前序建立)
{
	if(p == vec.size()) return nullptr;
    int a = vec[p++];
    if(a==-1) return nullptr;
    else
    {
        TreeNode* root = new TreeNode(a);
        root->left = buildTree();
        root->right = buildTree();
        return root;
    }
}

注意!这里不能void(TreeNode * root)这样建
这样相当于 TreeNode * root_local = root; 然后在function内root_local指向了新的空间, 而外部的root还是原先的位置,没法同步。

617 合并二叉树

/*
递归dfs
1.先写边界条件(特殊情况)
2.再写一般情况
*/
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        
        if(!t1 && !t2) return nullptr;
        else if(!t1) return t2;
        else if(!t2) return t1;
        else //t1 t2 都不为空
        {
            t1->val+=t2->val;
            t1->left=mergeTrees(t1->left,t2->left);
            t1->right=mergeTrees(t1->right,t2->right);
            return t1;
        }
    }
};

226 翻转二叉树

/*
递归dfs
*/
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if(!root) return nullptr;
        swap(root->left,root->right);
        root->left=invertTree(root->left);
        root->right=invertTree(root->right);
        return root;
    }
};

104 二叉树的最大深度

//递归
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(!root) return 0;
        else return 1 + max(maxDepth(root->left),maxDepth(root->right));
    }
};

101 对称二叉树

/*
递归 
*/
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    bool x(TreeNode* r1,TreeNode* r2)
    {
        if (!r1 && !r2) return true;
        else if (!r1 || !r2) return false;
        else if(r1->val == r2->val) return x(r1->left,r2->right) && x(r1->right,r2->left);
        else return false;
    }
    bool isSymmetric(TreeNode* root) {
        if(!root) return true;
        return x(root->left,root->right);
    }
};

543 二叉树的直径

递归方式
设函数来求:经过根节点的直径长度
遍历树的各个节点,带入到前面的函数中

递归环节太多

/*
递归
每个节点的直径=左子树深度+右子树深度
*/
/**
 * 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:
    int maxv=0;
    int maxDepth(TreeNode* root) 
    {
        if(!root) return 0;
        else return 1 + max(maxDepth(root->left),maxDepth(root->right));
    }
    int x(TreeNode* root)//以root为根节点且路径穿过root根节点时,路径的max(左子树深度+右子树深度)
    {     
        if(!root) return 0;
        else return maxDepth(root->left)+maxDepth(root->right);
    }
    int diameterOfBinaryTree(TreeNode* root) 
    {
        if(!root) return 0;
        maxv=(x(root)>maxv)?x(root):maxv;
        int cur1=diameterOfBinaryTree(root->left);
        int cur2=diameterOfBinaryTree(root->right);
        return max(maxv,max(cur1,cur2));
    }
};

减少递归环节。

递归函数,粗略理解即反复调用自己。 但是在函数内部,仅调用尽可能少次数的自身,使得递归可以进行下去即可,不需要调用多次,会导致超时。
递归的过程想维护最优解,可以设全局变量。 调用递归的时候,不一定要在return中进行,访问自身函数即是递归调用。

/*
递归
每个节点的直径=左子树深度+右子树深度 
递归过程中维护maxv
*/
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
 /*
递归返回当前节点的最大深度,
过程中维护一个max
max=(当前节点的)左子树最大深度+右子树最大深度
 */
class Solution {
public:
    int maxv=0;
    int maxDepth(TreeNode* root) //返回值为当前节点的最大深度
    {
     //求的时候维护maxv(maxv是我们所求的,函数返回值不一定是全局最优)
        if(!root) return 0;
        int left=maxDepth(root->left);
        int right=maxDepth(root->right);
        int cur=left+right;
        maxv=(cur>maxv)?cur:maxv;
        return 1 + max(left,right); //注:此处不要再用maxDepth(root->left)了,又来一边递归,费时。
    }
    int diameterOfBinaryTree(TreeNode* root) 
    {
        maxDepth(root); //注:有返回值的函数也可以直接写
        return maxv;
    }
};

102 二叉树的层序遍历

/*
bfs
为了把每层放入到一个vector中,需要在正常的while里再加一层循环,把当前队列里的元素打入到同一个vector中
*/
/**
 * 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: 
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if(!root) return res;
            
        queue<TreeNode*> que;
        que.push(root);
        while(!que.empty())
        {  
            int n = que.size(); 
            vector<int>level;
            while(n--)
            {
                auto cur = que.front();
                que.pop();
                level.push_back(cur->val);
                if(cur->left) que.push(cur->left);
                if(cur->right) que.push(cur->right);
            }
            res.push_back(level);
        }
        return res;        
    }
};

103 二叉树锯齿层序遍历(deque)

https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/solution/103-ju-chi-ceng-xu-bian-li-deque-by-flyt-v5fb/

94 二叉树的中序遍历

前序 后序都可以用类似的递归实现,调整val(中)的push_back的位置即可
前序: 中(val) - 左 - 右
中序: 左 - 中(val) - 右
后序: 左 - 右 - 中(val)

下面的是递归实现方式。
非递归实现见leetcode之路(2)。

/*
递归 新建void函数处理
*/
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void inorder(TreeNode* root,vector<int>& res)
    {
        if(!root) return;
        inorder(root->left,res);
        res.push_back(root->val);
        inorder(root->right,res);
    }
    vector<int> inorderTraversal(TreeNode* root) 
    {
        vector<int> res;
        inorder(root,res);
        return res;
    }
};
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> res;
    vector<int> inorderTraversal(TreeNode* root) {
       if(!root) return {};
       inorderTraversal(root->left);
       res.push_back(root->val);
       inorderTraversal(root->right);
       return res;
    }
};

114 二叉树展开为链表(单向)

非常好的解析

递归的一个非常重要的点就是:不去管函数的内部细节是如何处理的,我们只看其函数作用以及输入与输出。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void flatten(TreeNode* root) {
        if(!root) return;
        flatten(root->left);
        flatten(root->right);
        TreeNode* tmp=root->right;
        root->right=root->left;
        root->left=nullptr;
        while(root->right) root=root->right;
        root->right=tmp;
    }
};

双向链表的扩展

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void flatten(TreeNode* root) {
        if(!root) return;
        flatten(root->left);
        flatten(root->right);

        TreeNode* l=root->left;
        TreeNode* r=root->right;
        root->right=l;
        root->left=nullptr;
        l->left=root;

        while(root->right) root=root->right;
        root->right=r;
        r->left=root;
    }
};

将树(中序遍历)转化成双向链表(字节面试)

这个写法是万能写法!!!!!

由于inorder(x)后我们不知道x部分的头节点是谁,(前序遍历一定还是x),同时inorder没返回值,所以这种以外围全局变量保存的方式,一定要注意inorder的调用顺序。

/*
通过保存pre,在递归中序遍历树的基础上,每次执行当前部分,都将left指向pre。同时将pre的right指向当前root,构造双向链表
*/
#include<bits/stdc++.h>
using namespace std;
struct Treenode
{
    int val;
    Treenode *left;
    Treenode *right;
    Treenode(int x):val(x),left(nullptr),right(nullptr){}
};
Treenode* pre = nullptr;
void inorder(Treenode* root)
{
    if(!root) return;
    inorder(root->left); //左边已经调整好,最后一个是pre;

    //处理中间部分 把左侧链表和右侧链表连起来 ,left指向前驱,right指向后继
    if(!pre) root->left = nullptr; //没有左边的,该点做头;
    else
    {
        root->left=pre;    
        pre->right=root;
    }
    pre=root;
    inorder(root->right);
}
int main()
{
    inorder(root);
    pre->right=nullptr; //双向链表的最后一个指向空
    //再通过 root找到最左端的头
    while(root->left!=nullptr)
    {
    	root=root->left;	
    }
}

先序

#include<bits/stdc++.h>
using namespace std;
struct Treenode
{
    int val;
    Treenode *left;
    Treenode *right;
    Treenode(int x):val(x),left(nullptr),right(nullptr){}
};
Treenode* pre = nullptr;
void inorder(Treenode* root)
{
    if(!root) return;
    if(!pre) root->left = nullptr; //没有左边的,该点做头;
    else
    {
        root->left=pre;    
        pre->right=root;
    }
    pre=root;
    inorder(root->left); //左边已经调整好,最后一个是pre;
    inorder(root->right);
}
int main()
{
    inorder(root);
    pre->right=nullptr; //双向链表的最后一个指向空
    //root为头
}

后序

#include<bits/stdc++.h>
using namespace std;
struct Treenode
{
    int val;
    Treenode *left;
    Treenode *right;
    Treenode(int x):val(x),left(nullptr),right(nullptr){}
};
Treenode* pre = nullptr;
void inorder(Treenode* root)
{
    if(!root) return;
    inorder(root->left); //左边已经调整好,最后一个是pre;
    inorder(root->right);
    if(!pre) root->left = nullptr; //没有左边的,该点做头;
    else
    {
        root->left=pre;    
        pre->right=root;
    }
    pre=root;
}
int main()
{
    inorder(root);
    pre->right=nullptr; //双向链表的最后一个指向空
    while(root->left!=nullptr)
    {
    	root=root->left;	
    }
}

105 从前序与中序遍历序列构造二叉树

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
 /*
 递归
 前序遍历:[根节点,[左子树],[右子树]]
 中序遍历:[[左子树],根节点,[右子树]]
 所以可以根据从前序遍历数组中找到根节点,再去中序遍历数组中定位(可以现存个中序的hash,方便查找定位),把左子树和右子树确认出来,之后子树进行递归操作。

 建树过程:函数返回值为TreeNode*,函数内部root->right和root->left递归赋值,相当于每层函数只建立一个根节点,左子树的根节点和右子树的根节点由下一级递归返回

 关于递归参数:因为肯定要传递子树所对应的vector的信息,然而每层都赋值一个新vector会开辟很多空间,且操作麻烦,所以只传递子树在原vector上的左右界限。
 */
class Solution {
public:
    unordered_map<int,int> mp;
    TreeNode* build(vector<int>& preorder, vector<int>& inorder,int preorder_left,int preorder_right,int inorder_left,int inorder_right) //最后一个参数是该层建立的树的root
    {  //边界条件:无子树,返回nullptr
        if(preorder_left>preorder_right) return nullptr;
        int x=mp[preorder[preorder_left]];//根节点在中序遍历数组中的位置
        TreeNode* root = new TreeNode(preorder[preorder_left]); //建立根节点
        //左子树递归:在preorder中,preorder_left+1成为新左子树的起点,依次由示意图可推断其他参数
        root->left = build(preorder,inorder,preorder_left+1,preorder_left+x-inorder_left,inorder_left,x-1); 
        //右子树递归
        root->right = build(preorder,inorder,preorder_left+1+x-inorder_left,preorder_right,x+1,inorder_right);
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) 
    {
        for(int i=0;i<inorder.size();i++) mp[inorder[i]]=i;
        int n=preorder.size();
        return build(preorder,inorder,0,n-1,0,n-1);
    }
};

98 验证二叉搜索树

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
 /*
递归
注:节点要大于左子树的所有点(左子树的左子树+左子树的右子树,所以不能单纯比较一次大小,需要维护一个区间
 */
class Solution {
public:
    bool x(TreeNode* root,long long l,long long r) //有longlong的数据
    {
        if(!root) return true;
        if(root->val<=l || root->val>=r) return false;
        return x(root->left,l,root->val) && x(root->right,root->val,r);
    }
    bool isValidBST(TreeNode* root) 
    {
        return x(root,LONG_MIN, LONG_MAX); //LONG_MIN/MAX为long long类型的极值
    }
};

236 二叉树的最近公共祖先

清晰题解
这类型的还可以看看 865

/**
 * 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) {
        //临界条件:思考-当root为什么的时候,会有直接的返回值
        if(!root) return NULL;
        if(root==p||root==q) return root;

        //递归part: 思考-这个时候就相当于求出了左子树和右子树的情况
        //1.若子树中只有p或q的一个 返回的是这个点的位置 
        //2.若子树中同时有p和q,返回其pq的最近root
        //3.若子树无p和q 返回NULL
        TreeNode* left = lowestCommonAncestor(root->left,p,q);
        TreeNode* right = lowestCommonAncestor(root->right,p,q);
        if(!left) return right;
        if(!right) return left;
        if(left && right) return root;
        return NULL;
    }
};

链表

Listnode*a,*b;
a=b;
即:a指针 指向 b指针所指的东西(a,b要指向同一个 且 a是变量)
a为指向a结构体的指针;a->next为a结构体引出来的next指针
—a–>[|||结构体a|||]–next–>

206 反转链表

递归法比较难理解 看看即可
题解视频

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
 /*
递归
归的过程翻转

 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) 
    {
        if(!head||!head->next) return head;
        ListNode* res =reverseList(head->next);
        head->next->next=head;
        head->next=nullptr;
        return res; 
        //递归返回的过程中 res一直指向旧链表最后一个元素,也就是新链表的表头
        //递归的归的过程中,head在逐层向前,从后向前完成翻转。
    }
};

方法2 迭代遍历(更好理解+更普遍)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
 /*
迭代遍历
1->2->3->null 变为 null<-1<-2<-3,重点在于,逐个反转的过程中,要保存前一个和后一个
设pre cur next 三个指针 
null  1->  2  ->  3->null
|     |    |
pre  cur  next      每次将cur->next= pre;之后pre cur next 都向后移
注:指针的赋值就是指向同一个元素
 */

class Solution {
public:
    ListNode* reverseList(ListNode* head) 
    {
        ListNode* pre,*cur,*next;
        pre=nullptr;
        cur=head;
        next=head;
        while(cur)
        {
            next=next->next;
            cur->next=pre;
            pre=cur;
            cur=next;
        }
        return pre;
    }
};

非常熟练的写法,如下所示:

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        if(!pHead||!pHead->next) return pHead;
        ListNode* pre = nullptr;
        ListNode* cur = pHead;
        ListNode* nxt = cur->next;
        while(nxt)
        {
            cur->next=pre;
            pre=cur;
            cur=nxt;
            nxt=nxt->next;
        }
        cur->next=pre;
        return cur;
    }
};

2 两数相加

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
 /*
 建立新链表 a->next=new Listnode(x);a=a->next;
 新链表头可先设一个头指针指向val为0的节点,之后保存头指针,返回的时候直接返回头指针的next
 循环判断时判断当前指针即可
 同时要考虑到只有进位的情况
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        if(!l1) return l2;
        if(!l2) return l1;
        ListNode* cur1 = l1;
        ListNode* cur2 = l2;
        ListNode* res = new ListNode(0);
        ListNode* cur3 = res;
        int c=0; //进位
        while(cur1||cur2||c)
        {
            int tmp1=(cur1)?cur1->val:0; 
            int tmp2=(cur2)?cur2->val:0;
            int tmp3=tmp1+tmp2+c;
            if(tmp3>=10)
            {
                tmp3=tmp3%10;
                c=1;
            }
            else c=0; 
            cur3->next = new ListNode(tmp3);
            
            cur1=(cur1)?cur1->next:nullptr;
            cur2=(cur2)?cur2->next:nullptr;
            cur3=cur3->next;
        }

        return res->next;
    }
};

21 合并两个有序链表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
 /*
 直接在原链表上操作,l1指针标定现在链表1遍历到的位置,l2标定链表2,l3连接已经选择的节点
 l3每选择一个节点,将其标定原链表的指针指向next(l1/l2遍历下一点),同时l3也要指向下一个点
 注意:return的是l3的头节点,遍历过程中l3在改变,应该在最开始备份一个head节点
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if(!l1) return l2;
        if(!l2) return l1;
        //初始化
        ListNode* l3;
        if(l1->val <= l2->val) 
        {
            l3=l1;
            l1=l1->next;
        }
        else
        {
            l3=l2;
            l2=l2->next;
        }
        ListNode* res=l3;
        while(l1||l2)
        {
            if(!l1) {l3->next=l2;break;}
            else if(!l2) {l3->next=l1;break;}
            else if(l1->val <= l2->val) 
            {
                l3->next=l1;
                l1=l1->next;
            }
            else
            {
                l3->next=l2;
                l2=l2->next;
            }
            l3=l3->next;
        }
        return res;
    }
};

148 排序链表

详情见链接中的方法一

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
    public:
    ListNode* sortList(ListNode* head) 
    {
        if(!head||!(head->next)) return head;
        return sortList(head, nullptr);
    }
    ListNode* sortList(ListNode* head, ListNode* tail) 
    {
        //分割的单个最小单元    
        if (head->next == tail) 
        {
            head->next = nullptr; //断开
            return head;
        }
        //快慢指针确定当前分割中点
        ListNode* slow = head;
        ListNode* fast = head;
        while (fast != tail) 
        {
            slow = slow->next;
            fast = fast->next;
            if (fast != tail) {
                fast = fast->next;
            }
        }
        ListNode* mid = slow;
        ListNode* list1 = sortList(head, mid);
        ListNode* list2 = sortList(mid, tail);
        ListNode* sorted = merge(list1, list2);
        return sorted;
    }
    ListNode* merge(ListNode* l1, ListNode* l2) 
    {
        if(!l1) return l2;
        if(!l2) return l1;
        //初始化
        ListNode* l3;
        if(l1->val <= l2->val) 
        {
            l3=l1;
            l1=l1->next;
        }
        else
        {
            l3=l2;
            l2=l2->next;
        }
        ListNode* res=l3;
        while(l1||l2)
        {
            if(!l1) {l3->next=l2;break;}
            else if(!l2) {l3->next=l1;break;}
            else if(l1->val <= l2->val) 
            {
                l3->next=l1;
                l1=l1->next;
            }
            else
            {
                l3->next=l2;
                l2=l2->next;
            }
            l3=l3->next;
        }
        return res;
    }
};

19 删除链表的倒数第 N 个结点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
 /*
 Hint: Maintain two pointers and update one with a delay of n steps.
 */

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(!head->next) return nullptr;
        ListNode* first = head;
        ListNode* second = head;
        while(n--) second=second->next;
        if(!second) return first->next;
        while(second->next)
        {
            first=first->next;
            second=second->next;
        }
        first->next=first->next->next;
        return head;
    }
};

141 环形链表

/*
单层hash<unordered_set>
*/
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        unordered_set<ListNode*> hash;
        while(head!=nullptr)
        {
            if(hash.count(head)) return true;
            hash.insert(head);
            head=head->next;
        }
        return false;        
    }
};

快慢指针法

「Floyd 判圈算法」(又称龟兔赛跑算法)
假想「乌龟」和「兔子」在链表上移动,「兔子」跑得快,「乌龟」跑得慢。当「乌龟」和「兔子」从链表上的同一个节点开始移动时,如果该链表中没有环,那么「兔子」将一直处于「乌龟」的前方;如果该链表中有环,那么「兔子」会先于「乌龟」进入环,并且一直在环内移动。等到「乌龟」进入环时,由于「兔子」的速度快,它一定会在某个时刻与乌龟相遇,即套了「乌龟」若干圈。

我们可以根据上述思路来解决本题。具体地,我们定义两个指针,一快一满。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head==NULL||head->next==NULL) return false;
        ListNode* slow=head;
        ListNode* fast=head->next;
        while(slow!=fast)
        {
            if(fast==NULL) return false;
            slow=slow->next;
            fast=fast->next;
            if(fast!=NULL) fast=fast->next;
        }
        return true;
    }
};

#include<bits/stdc++.h>
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(!head) return false;
        ListNode* fast = head;
        ListNode* slow = head;
        do
        {
            if(!fast) return false;
            fast=fast->next;
            slow=slow->next;
            if(fast) fast=fast->next;
            else return false;
        }
        while(fast!=slow);
        return true;
    }
};

142 环形链表2

/*
方法一同141题方法一
*/
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        unordered_set<ListNode*> hash;
        if(head==NULL||head->next==NULL) return NULL;
        while(head!=NULL)
        {
            if(!hash.count(head)) 
            {hash.insert(head);head=head->next;}
            else return head;
        }
        return NULL;
        
    }
};

方法二 快慢指针
数学推导

/*
若初始fast=head->next;
数学推导为
2(a+b)=a-1+n(b+c)+b
得
a=(n-1)(b+c)+c-1
所以slow再走一步
*/
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head==NULL||head->next==NULL) return NULL;
        ListNode* slow = head;
        ListNode* fast = head->next;
        while(slow!=fast)
        {
            if(fast==NULL) return NULL;
            slow=slow->next;
            fast=fast->next;
            if(fast!=NULL) fast=fast->next;
        }
        ListNode* p=head;
        slow=slow->next;
        while(p!=slow)
        {
            p=p->next;
            slow=slow->next;
        }
        return p;     
    }
};

287 快慢指针

/*
空间复杂度O(1)解法:快慢指针
若想使得slow fast初始值一样,就用do while处理循环部分
*/
class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int slow=0;
        int fast=0;
        do
        {
            slow=nums[slow];
            fast=nums[nums[fast]];
        }while(slow!=fast);
        int p=0;
        while(p!=slow)
        {
            slow=nums[slow];
            p=nums[p];
        }
        return p;
    }
};

25 K个一组翻转链表

class Solution {
public:

    pair<ListNode*,ListNode*> reverseL(ListNode* begin, ListNode* end)
    {   //翻转时需要 pre cur nxt 三个指针:前提要保证end处不为null
        ListNode* pre = end->next; //把下一个的最前面的节点连接上
        ListNode* cur = begin;
        ListNode* nxt = begin->next;

        while(cur!=end) //这样可以防止nxt越界报错
        {
            cur->next=pre;
            pre = cur;
            cur = nxt;
            nxt = nxt->next;
        }
        cur->next=pre; //补上最后一个

        return {end,begin}; //返回当前的头尾指针
    }
    ListNode* reverseKGroup(ListNode* head, int k) {
        ListNode* start = new ListNode(0);
        start->next = head; //这两步很关键 在翻转链表题中,在首指针前加一个点,后续有用
        ListNode* cur = start; //当前指针指向这个新建的节点
        while(cur)
        {
            int ktmp=k;
            ListNode* last_end = cur; //保存一下上次的尾节点,以供后续连接使用
            ListNode* begin=cur->next;
            ListNode* end; //该轮需要翻转的首尾指针
            while(ktmp--)
            {
                cur = cur->next;
                if(!cur) break;
            }
            if(!cur) return start->next; //该轮未满k个 不能写if ktmp>0, 因为=0情况有可能最后一次循环cur==null
            else
            {
                end = cur;
                pair<ListNode*,ListNode*> res = reverseL(begin,end);
                //连接
                last_end->next=res.first;
                //更新cur
                cur=res.second;
            }
        }
        return start->next;
    }
};

不要求空间复杂度可以用stack

160 相交链表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
 /*
链表1:A-相交点-C
链表2:B-相交点-C
A+C+B=B+C+A
所以遍历1之后马上开始2,与此同时遍历2后马上开始1,两者相交处为焦点
 */
 // 无交点的情况也满足:第二轮都指向null
class Solution {
public:
    ListNode *getIntersectionNode( ListNode* pHead1, ListNode* pHead2) {
        ListNode* p = pHead1;
        ListNode* q = pHead2;
        while(p!=q)
        {
            p=(p)?p->next:pHead2;
            q=(q)?q->next:pHead1;
        }
        return p;
    }
};

BFS

200 岛屿数量

时间复杂度O(MN)
空间复杂度O(min(M,N)) 因为队列的长度一直在变 有pop 有push 画图可得长度最值

/*
bfs
遍历矩阵,值为1,岛屿数++,从这个点开始进行bfs遍历
bfs遍历到值为1的点后,把该点值变为0,相当于标记
*/
class Solution {
public:
    int dir[4][2]={{-1,0},{0,1},{1,0},{0,-1}};//二维数组的初始化
    int numIslands(vector<vector<char>>& grid) 
    {
        int res=0;
        int n=grid.size();
        if(n==0) return 0;
        int m=grid[0].size();
        queue<pair<int,int>> que;
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                if(grid[i][j]=='1')
                {
                    res++;
                    grid[i][j]='0';
                    que.push(make_pair(i,j));
                    while(!que.empty())
                    {
                        auto cur = que.front();
                        que.pop();
                        for(int a=0;a<4;a++)
                        {
                            int newi=cur.first+dir[a][0];
                            int newj=cur.second+dir[a][1];
                            if(newi<0||newj<0||newi>=n||newj>=m||grid[newi][newj]=='0')continue;
                            grid[newi][newj]='0';
                            que.push(make_pair(newi,newj));
                        }
                    }
                }
            }
        }
        return res;           
    }
};

DFS

200 岛屿数量

时间复杂度O(MN)
空间复杂度O(MN)

/*
dfs
遍历矩阵,值为1,岛屿数++,从这个点开始进行dfs遍历
dfs边界条件为矩阵边界和值为0,遍历到值为1的点后,把该点值变为0,相当于标记
*/
class Solution {
public:
    int dir[4][2]={{-1,0},{0,1},{1,0},{0,-1}};//二维数组的初始化
    void dfs(int i, int j, vector<vector<char>>& grid)
    {

        int n=grid.size();
        int m=grid[0].size(); 
        if(i<0||j<0||i>=n||j>=m||grid[i][j]=='0') return;
        grid[i][j]='0';
        for(int a=0;a<4;a++)
        {
            dfs(i+dir[a][0],j+dir[a][1],grid);
        }

    }

    int numIslands(vector<vector<char>>& grid) 
    {
        int res=0;
        if(grid.size()==0) return 0;
        for(int i=0;i<grid.size();i++)
        {
            for(int j=0;j<grid[0].size();j++)
            {
                if(grid[i][j]=='1')
                {
                    res++;
                    dfs(i,j,grid);
                }
            }
        }
        return res;           
    }
};

DP

动态规划做题步骤

明确 dp(i)应该表示什么(二维情况:dp(i)(j));
根据 dp(i)dp(i-1) 的关系得出状态转移方程(二维情况:dp[i][j]和dp[i+x][j+y]等)
确定初始条件,如 dp(0)

70 爬楼梯

/*
DP
dp[i]为i阶楼梯的方法数
dp[1]=1
dp[2]=2
dp[i]=dp[i-1]+dp[i-2](i>3)
*/
class Solution {
public:
    int climbStairs(int n) {
        int dp[n+5];
        dp[1]=1;
        dp[2]=2;
        for(int i=3;i<=n;i++)
        {
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};

扩展
不能连续跳两个台阶

假设:
f(x) 表示爬到第 x 级台阶的方案数, 
g(x, 1) 表示爬到第 x 级台阶并且最后一步只跨越一个台阶的方案数, 
g(x, 2) 表示爬到第 x 级台阶并且最后一步跨越了两个台阶的方案数。

由 :
f(x) = g(x, 1)+g(x,2)g(x, 1) = f(x-1)g(x, 2) = g(x-2, 1) // 最后一步跨越了两步,那么上一步只能跨越一步

得:
f(x) = g(x, 1) + g(x, 2) 
      = f(x-1) + g(x-2, 1) 
      = f(x-1) + f((x-2)-1)
      = f(x-1) + f(x-3)

5 最长回文子串

/*
dp[i][j]:从s[i]到s[j]的字符串是否为回文子串,是为1,不是为0

边界条件:
dp[i][i]=1
dp[i][i+1]=(S[i]==s[i+1])

if(s[i]==s[j]) dp[i][j]=dp[i+1][j-1]

*/
class Solution {
public:
    string longestPalindrome(string s) 
    {
        int n = s.size();
        int dp[n+5][n+5];
        memset(dp,0,sizeof(dp));
        string ans;
        for (int l = 0; l < n; ++l) 
        {
            for (int i = 0; i + l < n; ++i) 
            {
                int j = i + l;
                if(l == 0) dp[i][j] = 1;
                else if(l == 1) dp[i][j] = (s[i] == s[j]);
                else if(s[i] == s[j]) dp[i][j] =  dp[i + 1][j - 1];
                
                if (dp[i][j] && l + 1 > ans.size()) ans = s.substr(i, l + 1);
            }
        }
        return ans;
    }
};

121 买卖股票的最佳时机

/*
一次遍历
遍历过程中,记录当前min,并计算prices[i]-min,更新res,并实时更新min
*/
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(!prices.size()) return 0;
        int min=prices[0];
        int res=0;

        for(int i=0;i<prices.size();i++)
        {
            if(prices[i]<min) min=prices[i];
            else if(prices[i]>min)
            {
                int cur=prices[i]-min;
                res=(cur>res)?cur:res;
            }
        }
        return res;
    }
};

DP思路

/*
dp
dp[i]表示第i天时的最大收益
dp[i]=max(dp[i-1],prices[i]-min) 
*/
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(!prices.size()) return 0;
        int min=prices[0];
        int dp[prices.size()+5];
        dp[0]=0;

        for(int i=1;i<prices.size();i++)
        {
            if(prices[i]<min) min=prices[i];
            dp[i]=max(dp[i-1],prices[i]-min);
        }
        return dp[prices.size()-1];
    }
};

300 最长递增子序列

/*
dp
dp[i]:以nums[i]为结尾的递增子序列长度
dp[i]=max(dp[i],dp[j]+1)  (j从0到i-1遍历 && nums[i]>num[j])
找到最大的dp[x],即为答案
O(n*n)
*/
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> dp (nums.size());
        dp[0]=1;
        int maxdp=1;
        for(int i=1;i<nums.size();i++)
        {
            dp[i]=1;
            for(int j=0;j<i;j++)
            {
                if(nums[i]>nums[j])
                    dp[i]=max(dp[i],dp[j]+1);
            }
            maxdp=(dp[i]>maxdp)?dp[i]:maxdp;
        }
        return maxdp;
    }
};

322 零钱兑换(需要后续进一步理解)

关于背包
01背包问题

/*
for i = 1, 2, ..., n: # 枚举前 i 个物品
    for v = 0, 1, ..., V: # 枚举体积
        f[i][v] = f[i-1][v]; # 不选第 i 个物品
        if v >= c[i]:  # 第 i 个物品的体积必须小于 v 才能选
            f[i][v] = max(f[i][v], f[i-1][v-c[i]] + w[i])
return max(f[n][0...V]) # 返回前 n 个物品的最大值
*/
/*
类比背包问题: 背包总体积 :硬币总金额
              物品单个体积:硬币的单个金额
              每个物品的价值:1(币的数量)
              背包问题是求最多的价值是多少,该题是求最少的价值是多少
*/
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int Max = amount + 1;
        vector<int> dp(amount + 1, Max);
        dp[0] = 0;
        for (int j = 0; j <coins.size(); ++j) 
        {
            for (int i = 1; i <= amount; ++i) 
            {
                if (coins[j] <= i) dp[i] = min(dp[i], dp[i - coins[j]] + 1);
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
};

面试题 08.11. 硬币(需要后续进一步理解)

完全背包

class Solution {
private:
    static constexpr int mod = 1000000007;
    static constexpr int coins[4] = {25, 10, 5, 1};

public:
    int waysToChange(int n) {
        vector<int> f(n + 1);
        f[0] = 1;
        for (int c = 0; c < 4; ++c) {
            int coin = coins[c];
            for (int i = coin; i <= n; ++i) {
                f[i] = (f[i] + f[i - coin]) % mod;
            }
        }
        return f[n];
    }
};

887 鸡蛋掉落(阿里面试)

李永乐老师b站视频
详细解析

讨论下为什么双蛋情况第一颗蛋要N,N+N-1,N+N-1+N-2,…这样扔最佳
1.若间隔始终为N,即N,2N,3N。这种情况下,假设说最大代价出现在最大楼层-1的位置,第二颗蛋的扔的次数是不变的,都是N-1,所以每多扔一次第一颗蛋,扔的总数就+1,这样的话楼层越高,次数越多
2.所以,如果我们第一个蛋按N,N+N-1,N+N-1+N-2,…的形式扔,没多扔一次第一个蛋,第二个蛋扔的次数对应的会减一,这就使得无楼层为多高,总的扔蛋数都是固定的。
3.N+N-1+N-2+N-3+…+1=N(N+1)/2=X; X为楼层高;最后到1是因为,这样可以使N尽可能地小,而我们最后扔的次数正是N

二分查找

6010

https://leetcode-cn.com/problems/minimum-time-to-complete-trips/solution/6010-chao-shi-shi-xiang-xiang-er-fen-by-3q7is/

二分模版
while l < r:
m = (l + r) >> 1
if check(m):
l = m + 1
else:
r = m

对于二分查找,一般可用以下模板,注意区别。另外可用类似 m = l + (r - l) / 2的方式解决溢出的问题。

4 寻找两个正序数组的中位数

题解

/*
归并找中位数算法O(m+n)

要想log(m+n):二分  先记个思想 具体实现之后有时间再进行
*/

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) 
    {
        int m = nums1.size();
        int n = nums2.size();
        //中位数 = (left + right)/2
        int left = (m + n + 1) / 2;
        int right = (m + n + 2) / 2;
        return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0;
    }
    //在两个有序数组中找到第k个元素(例如找第一个元素,k=1,即nums[0])
    //i: nums1的起始位置 j: nums2的起始位置(i,j都是从0开始)
    int findKth(vector<int>& nums1, int i, vector<int>& nums2, int j, int k)
    {
        //若nums1为空(或是说其中数字全被淘汰了)
        //在nums2中找第k个元素,此时nums2起始位置是j,所以是j+k-1
        if(i >= nums1.size())    return nums2[j + k - 1];
        //nums2同理
        if(j >= nums2.size())    return nums1[i + k - 1];

        //递归出口
        if(k == 1)  return min(nums1[i], nums2[j]);

        //这两个数组的第K/2小的数字,若不足k/2个数字则赋值整型最大值,以便淘汰另一数组的前k/2个数字
        int midVal1 = (i + k/2 - 1 < nums1.size()) ? nums1[i + k/2 - 1] : INT_MAX;
        int midVal2 = (j + k/2 - 1 < nums2.size()) ? nums2[j + k/2 - 1] : INT_MAX;
        //二分法核心部分
        if(midVal1 < midVal2)
            return findKth(nums1, i + k/2, nums2, j, k - k/2);
        else
            return findKth(nums1, i, nums2, j + k/2, k - k/2);
    }
};


33 搜索旋转排序数组

题解

/*
二分
通过比较nums[left]和nums[mid]来确认旋转点与mid的关系
进而分类讨论
1.左侧单调递增:若target在左侧范围内,则right=mid,否则left=mid
2.右侧单调递增:若target在右侧范围内,则left=mid,否则right=mid
注意:二分时要设为mid-1和mid+1!
*/
//因为只有单调递增的部分能确定target是在这个范围内 所以要分类讨论
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n=nums.size();
        if(!n) return -1;
        int left=0;
        int right=n-1;
        int mid =(left+right)/2;
        while(left<=right)
        {
            if(target == nums[mid]) return mid;
            if(nums[left]<=nums[mid])
            {
                if(nums[left]<=target&&target<nums[mid])
                    right = mid-1;
                else left = mid+1;
            }
            else
            {
                if(nums[mid]<target&&target<=nums[right])
                    left = mid+1;
                else right = mid-1;
            }
            mid = (left+right)/2;
        }
        return -1;
    }
};

字符串

28 KMP 实现 strStr()

KMP: 一种改进的字符串匹配算法,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。它的时间复杂度是O(m+n) <正常暴力匹配的复杂度为O(mn)>
KMP算法详谈
求next时注意,next[i](i初值为0)的值是模式串从a[0]到a[i-1](不包括a[i])所对应的公共前后缀最长长度。同时注意,求模式串a[i-1]的公共前后缀最长长度时,前后缀的长度要小于a[i-1]的长度。例如字串为abcd时,求next[2],首先求的是ab的公共前后缀最长长度,对于ab来说,前缀只有a,后缀只有b,所以next[2]==0
i为遍历过程中主串S上的指针位置,j为遍历过程中模式串P上的指针位置。
当主串S与模式串P失配时,j=next[j],P向右移动j - next[j]。也就是当模式串P后缀pj-kpj-k+1…pj-1与主串S的si-ksi-k+1…si-1匹配成功,但是pj和si失配时,因为next[j]=k,相当于在不包含pj的模式串中有最大长度为k的相同前后缀,也就是p0p1…pk-1 = pj-kpj-k+1…pj-1,所以令j = next[j],让模式串右移j - next[j]位,使模式串p0p1…pk-1 与主串si-ksi-k+1…si-1对齐,让pk和si继续匹配。
在代码中,模式串的右移其实不需要表现出来,只要有当前i和j的位置即可比较。

明白思想即可,代码之后有时间再细抠

class Solution {
public:
    void getNext(int* next, const string& s) {
        int j = -1;
        next[0] = j;
        for(int i = 1; i < s.size(); i++) { // 注意i从1开始
            while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
                j = next[j]; // 向前回溯
            }
            if (s[i] == s[j + 1]) { // 找到相同的前后缀
                j++;
            }
            next[i] = j; // 将j(前缀的长度)赋给next[i]
        }
    }
    int strStr(string haystack, string needle) {
        if (needle.size() == 0) {
            return 0;
        }
        int next[needle.size()];
        getNext(next, needle);
        int j = -1; // // 因为next数组里记录的起始位置为-1
        for (int i = 0; i < haystack.size(); i++) { // 注意i就从0开始
            while(j >= 0 && haystack[i] != needle[j + 1]) { // 不匹配
                j = next[j]; // j 寻找之前匹配的位置
            }
            if (haystack[i] == needle[j + 1]) { // 匹配,j和i同时向后移动
                j++; // i的增加在for循环里
            }
            if (j == (needle.size() - 1) ) { // 文本串s里出现了模式串t
                return (i - needle.size() + 1);
            }
        }
        return -1;
    }
};

简化记忆版

#include <bits/stdc++.h>
using namespace std;
#define maxn int(1e6 + 10)
int nxt[maxn];
vector<int> ans;
void getNext(const string& s) {
    int len = s.size();
    int i = 0;
    int j = -1;
    nxt[0] = -1;
    while (i < len) {
        if (j == -1 || s[i] == s[j]) {
            ++i;
            ++j;
            nxt[i] = j;
        } else {
            j = nxt[j];
        }
    }
}
// s2在s1中第一次出现的位置,否则返回-1
int findIdx(const string& s1, const string& s2) {
    int l1 = s1.size(), l2 = s2.size();
    int i = 0, j = 0;
    while (i < l1 && j < l2) {
        if (j == -1 || s1[i] == s2[j]) {
            ++i;
            ++j;
        } else {
            j = nxt[j];
        }
    }
    if (j == l2) {
        return i - j;
    } else {
        return -1;
    }
}
// 在可重叠的情况下,s2在s1中出现的次数
int findCnt1(const string& s1, const string& s2) {
    int l1 = s1.size(), l2 = s2.size();
    int i = 0, j = 0;
    int cnt = 0;
    while (i < l1 && j < l2) {
        if (j == -1 || s1[i] == s2[j]) {
            ++i;
            ++j;
        } else {
            j = nxt[j];
        }
        if (j == l2) {
            ++cnt;
            ans.push_back(i - j);
            j = nxt[j];
        }
    }
    return cnt;
}
// 在不重叠的情况下,s2在s1中出现的次数
int findCnt2(const string& s1, const string& s2) {
    int l1 = s1.size(), l2 = s2.size();
    int i = 0, j = 0;
    int cnt = 0;
    while (i < l1 && j < l2) {
        if (j == -1 || s1[i] == s2[j]) {
            ++i;
            ++j;
        } else {
            j = nxt[j];
        }
        if (j == l2) {
            ++cnt;
            j = 0;
        }
    }
    return cnt;
}
int main() {
    ios::sync_with_stdio(0);
    string s1;
    string s2;
    cin >> s1 >> s2;
    getNext(s2);
    assert(nxt[0] == -1);
    int len = s2.size();
    findCnt1(s1, s2);
    for (auto e : ans) {
        cout << e + 1 << endl;
    }
    for (int i = 1; i <= len; ++i) {
        cout << nxt[i];
        if (i != len) {
            cout << " ";
        }
    }

    // cout << findIdx(s1, s2) << endl;
    // cout << findCnt1(s1, s2) << endl;
    // cout << findCnt2(s1, s2) << endl;
    return 0;
}

1143 最长公共子序列

详细解析
要注意 text中的i和dp中的i查一位
text[0]是text的第一个元素
dp[0][x]指当text1为空串时的dp值

/*
dp
dp[i][j]:text1前i个字符和text2前j个字符的最长公共子序列的长度

dp[0][j]=dp[i][0]=0: 当text1为空串或text2为空串时

if(text[i]==text[j]) dp[i][j]=dp[i-1][j-1]+1
else dp[i][j]=max(dp[i-1][j],dp[i][j-1])

从左往右,从上往下遍历

dp数组 [text1.size()+1][text2.size()+1] 初始化外层一圈0,0
*/
class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        vector<vector<int>> dp(text1.size()+1,vector<int>(text2.size()+1,0));//二重vector的初始化
        for(int i=0;i<text1.size();i++)
            for(int j=0;j<text2.size();j++)
            {
                if(text1[i]==text2[j]) dp[i+1][j+1]=dp[i][j]+1;
                else dp[i+1][j+1]=max(dp[i+1][j],dp[i][j+1]);
            }

        return dp[text1.size()][text2.size()];

    }
};

如何输出该子序列

/*
dp
dp[i][j]:text1前i个字符和text2前j个字符的最长公共子序列的长度

dp[0][j]=dp[i][0]=0: 当text1为空串或text2为空串时

if(text[i]==text[j]) dp[i][j]=dp[i-1][j-1]+1
else dp[i][j]=max(dp[i-1][j],dp[i][j-1])

从左往右,从上往下遍历

dp数组 [text1.size()+1][text2.size()+1] 初始化外层一圈0,0
*/
class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        vector<vector<int>> dp(text1.size()+1,vector<int>(text2.size()+1,0));//二重vector的初始化
        for(int i=0;i<text1.size();i++)
            for(int j=0;j<text2.size();j++)
            {
                if(text1[i]==text2[j]) dp[i+1][j+1]=dp[i][j]+1;
                else dp[i+1][j+1]=max(dp[i+1][j],dp[i][j+1]);
            }
        
        //输出子序列 : 只有当斜着增加的时候,记录该点的字符
        int l1=text1.size();
        int l2=text2.size();
        string res;
        while(dp[l1][l2]>0)
        {
            if(dp[l1][l2]==dp[l1-1][l2]) l1--;
            else if(dp[l1][l2]==dp[l1][l2-1]) l2--;
            else //l1 l2 ==l1-1 l2-1 
            {
                res=text1[l1-1]+res;
                l1--;
                l2--;
            }
        }
        cout<<res<<endl;

        return dp[text1.size()][text2.size()];

    }
};

最长公共子串

牛客链接

如何输出该子串:直接在str1上取sub即可

/*
与最长公共子序列的不同之处在于
if(str1[i]!=str2[j]) dp[i+1][j+1]=0;
*/
class Solution {
public:
    /**
     * longest common substring
     * @param str1 string字符串 the string
     * @param str2 string字符串 the string
     * @return string字符串
     */
    string LCS(string str1, string str2) {
        // write code here
        int l1=str1.size();
        int l2=str2.size();
        vector<vector<int>> dp(l1+1,vector<int>(l2+1,0));
        pair<int,int> res = make_pair(0,0);
        for(int i=0;i<l1;i++)
            for(int j=0;j<l2;j++)
            {
                if(str1[i]==str2[j]) dp[i+1][j+1]=dp[i][j]+1;
                else dp[i+1][j+1]=0;
                if(dp[i+1][j+1]>dp[res.first][res.second]) res={i+1,j+1};
            }
        int n=dp[res.first][res.second];
        string x;
        x=str1.substr(res.first-n,n);
        return x;
    }
};

14 最长公共前缀

纵向查找

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        if(strs.size()==0) return"";
        int len=0;
        bool mark = true;
        int s=300;
        for(auto elem:strs) s=(elem.size()<s)?elem.size():s;
        for(int i=0;i<s;i++)
        {
            char c = strs[0][i]; 
            mark = true;
            for(auto elem:strs)
            {
                if(elem[i]!=c) 
                {mark=false;break;}   
            }
            if(mark) len++;
            else break;
        }
        string ans = strs[0].substr(0,len);
        return ans;
    }
};

简化:先str排序,再比较第一个str和最后一个str

大数加法 NC1

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * 计算两个数之和
     * @param s string字符串 表示第一个整数
     * @param t string字符串 表示第二个整数
     * @return string字符串
     */
    string solve(string s, string t) {
        // write code here
        string ans="";
        int sS = s.size();
        int tS = t.size();
        int carry = 0;
        for( int i=sS-1,j=tS-1; i>=0||j>=0; i--,j-- ){
            int a = i>=0 ? s[i]-'0' : 0;
            int b = j>=0 ? t[j]-'0' : 0;
            int sum = a+b;
            ans = to_string((sum+carry) % 10 ) + ans;
            if( sum+carry>=10 ){
                carry = 1;
            } else {
                carry = 0;
            }
        }
        if( carry )
            ans = '1' + ans;
        return ans;
    
    }
};

字符串中的数排序 美团笔试题

这道题的重点是,当我们抠出来数字的string时,不用转化成数字,再sort。而是直接重写cmp,对string进行sort,这样可以避免string过长的情况

#include<bits/stdc++.h>
using namespace std;
string x(string s)
{
    int n=s.size();
    if(n==1) return s;
    int i;
    for(i=0;i<n;i++)
    {
        if(s[i]!='0') break;
    }
    if(i==n) return "0";
    return s.substr(i,n-i);
}
bool cmp(string s1,string s2)
{
    int n1=s1.size();
    int n2=s2.size();
    if(n1==n2) return s1<s2;
    return n1<n2;

}
int main()
{
    string s;
    getline(cin,s);
    int n=s.size();
    string buff;
    vector<string> res;
    for(int i=0;i<n;i++)
    {
        if(buff.size()>0&&(s[i]>'9'||s[i]<'0'))
        {
            //cout<<x(buff)<<endl;
            res.push_back(x(buff));
            buff="";
        }
    	else if(s[i]<='9'&&s[i]>='0')
        {
            buff+=s[i];
        }
    }
    //最后一个buff特判
    if(buff.size()>0) res.push_back(x(buff));//cout<<x(buff)<<endl;
    sort(res.begin(),res.end(),cmp);
    for(auto elem:res)
    {
        cout<<elem<<endl;
    }
    return 0;
}

map

特征提取 字节2019春招题

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int N;
    cin>>N;
    while(N--)
    {
        int M;
        cin>>M;
        int res=0;
        map<pair<int,int>,pair<int,int>> mp; 
        //key:存特征     
        //value:first存这个状态的时刻,second存这个状态到这个时刻持续的时间
        for(int i=0;i<M;i++)
        {
            int l;
            cin>>l;
            for(int j=0;j<l;j++)
            {
                int a,b;
                cin>>a>>b;
                pair<int,int> cur = make_pair(a,b);
               // cout<<"cur="<<cur.first<<" "<<cur.second<<endl;
                if(!mp.count(cur)) mp[cur]=make_pair(i,1);
                else if(mp[cur].first==i-1) mp[cur]=make_pair(i,++mp[cur].second);
                else mp[cur]=make_pair(i,1);
               // cout<<"mp[cur]="<< mp[cur].first <<" "<<mp[cur].second<<endl;
                res=(mp[cur].second>res)?mp[cur].second:res;
               // cout<<"res="<<res<<endl;
            }
        }
        cout<<res<<endl;
    }
    return 0;
}

数学推导

1411 给 N x 3 网格图涂色的方案数(阿里笔试)

解析

图论

815 公交线路(阿里笔试)

还有错误,不过思路正确,以后有时间继续调

//把每一个环路当作一个点,环路和环路间的距离用邻接表表示,dfs求距离
class Solution {
public:
    int n;
    vector<vector<int> > table = vector<vector<int> >(505,vector<int>(505,1000)); //vector全局变量要这么写
    vector<bool> visit=vector<bool>(505,false);
    int nstart;
    int nend;
    int ans=550;
    void dfs(int i,int res) //第一个参数为当前到的点,第二个参数为到达该点用的换乘次数
    {
        if(table[i][nend]==0)
        {
            ans = min(ans,res);
        } 
        for(int j=0;j<505;j++)
        {
            if(table[i][j]==0&&visit[j]==false) {visit[j]=true;dfs(j,res);visit[j]=false;}
            else if(table[i][j]==1&&visit[j]==false) {visit[j]=true;dfs(j,res+1);visit[j]=false;}

        }
    }
    int numBusesToDestination(vector<vector<int>>& routes, int source, int target) {
        unordered_map<int,vector<int>> hash;
        n=routes.size();
        //存一个hash:key值为每一个节点,value为这个节点所在的所有环路组成的vector
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<routes[i].size();j++)
            {
                if(!hash.count(routes[i][j])) hash[routes[i][j]]={i};
                else hash[routes[i][j]].push_back(i);
            }
            //把一个routes里节点的距离标成0
            if(routes[i].size()==1) continue;
            for(int j=0;j<routes[i].size();j++)
            {
                int pre=routes[i][j];
                for(int k=j+1;k<routes[i].size();k++)
                {
                    int cur = routes[i][k];
                    table[pre][cur]=0;
                    table[cur][pre]=0;
                }
            }
        }
        //遍历hash的每一个节点(key),把对应的value里vector的每一项间的距离设为1(之前标成0的除外)
        for(auto elem:hash)
        {
            vector<int> tmp = elem.second;
            for(int m=0;m<tmp.size();m++)
            {
                for(int n=m+1;n<tmp.size();n++)
                {
                    if(!table[m][n]) {table[m][n]=1;table[n][m]=1;}
                }
            }
        }

        vector<int> start=hash[source];
        vector<int> end=hash[target];
        //设置起始点和终止点,由于起始点/终止点可能属于多个环路,另建两个新环路,一个到start中所有环路的距离为0,一个到end中所有环路的距离为0,求这两个环路间的距离
        nstart=n;
        nend=n+1;
        for(auto elem:start)
        {
            table[nstart][elem]=0;
            table[elem][nstart]=0;
        }
        for(auto elem:end)
        {
            table[nend][elem]=0;
            table[elem][nend]=0;
        }

        //用dfs求table中nstart到nend的距离
        int res=0;
        visit[nstart]=true;
        dfs(nstart,res);
        return ans;
    }
};

329 矩阵中的最长递增路径 再看一下

class Solution {
public:
    static constexpr int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    int rows, columns;

    int longestIncreasingPath(vector< vector<int> > &matrix) {
        if (matrix.size() == 0 || matrix[0].size() == 0) {
            return 0;
        }
        rows = matrix.size();
        columns = matrix[0].size();
        auto memo = vector< vector<int> > (rows, vector <int> (columns));
        int ans = 0;
        for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < columns; ++j) {
                ans = max(ans, dfs(matrix, i, j, memo));
            }
        }
        return ans;
    }

    int dfs(vector< vector<int> > &matrix, int row, int column, vector< vector<int> > &memo) {
        if (memo[row][column] != 0) {
            return memo[row][column];
        }
        ++memo[row][column];
        for (int i = 0; i < 4; ++i) {
            int newRow = row + dirs[i][0], newColumn = column + dirs[i][1];
            if (newRow >= 0 && newRow < rows && newColumn >= 0 && newColumn < columns && matrix[newRow][newColumn] < matrix[row][column]) {
                memo[row][column] = max(memo[row][column], dfs(matrix, newRow, newColumn, memo) + 1);
            
        }
        return memo[row][column];
    }
};

记忆化搜索 美团笔试

之后研究一下

#include<bits/stdc++.h>
#define N 100010
using namespace std;
struct e{
  int v,w;
  e(int v,int w):v(v),w(w){}
};
vector<int> vec[N];
int dp[N];
bool vis[N];
int h[N];

int dfs(int x)
{
    if(dp[x]!=-1)
    {
        return dp[x];
    }
    dp[x]=1;
    for(auto adj:vec[x])
    {
        int v=adj;
        if(vis[v]||h[v]>h[x]) continue;
        vis[v]=1;
        dp[x]=max(dp[x],dfs(v)+1);
        vis[v]=0;
    }
    return dp[x];
}
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>h[i];
    }
    for(int i=0;i<m;i++)
    {
        int u,v;
        cin>>u>>v;
        vec[u].push_back(v);
        vec[v].push_back(u);
    }
    for(int i=1;i<=n;i++)
    {
        dp[i]=-1;
        vis[i]=0;
    }
    int ans=1;
    for(int i=1;i<=n;i++)
    {
        vis[i]=1;
        ans=max(ans,dfs(i));
        vis[i]=0;
    }
    cout<<ans<<endl;
    return 0;
}





设计

146 LRU缓存机制

使用了STL当中的list和unordered_map

#include<bits/stdc++.h>
using namespace std;
class LRUCache {
private:
  list<pair<int, int>> cache;   < @note pair[key]=value
  unordered_map<int, list<pair<int, int>>::iterator> key2node;
  int cap;                      < @note 最大容量

public:
  LRUCache(int capacity) : cap(capacity) {}

  int get(int key) {
    if (!key2node.count(key)) {
      return -1;
    }
    pair<int, int> node = *key2node[key];
    cache.erase(key2node[key]); < @note 将节点移到链表头部并更新map
    cache.push_front(node);
    key2node[key] = cache.begin();
    return node.second;
  }

  void put(int key, int val) {
    auto newNode = make_pair(key, val);

    if (key2node.count(key)) {  < @note 若该节点已存在,则删除旧的节点
      cache.erase(key2node[key]);
    } else {
      if (cap == cache.size()) {
        key2node.erase(cache.back().first);
        cache.pop_back();       < @note 删除链表最后一个数据
      }
    }

    cache.push_front(newNode);  < @node 插入新的节点到头部
    key2node[key] = cache.begin();
  }
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值