力扣Hot100题单个人计划c++版(二)

力扣Hot100题单个人计划c++版(一)
力扣Hot100题单个人计划c++版(二)
力扣Hot100题单个人计划c++版(三)
力扣Hot100题单个人计划c++版(四)
力扣Hot100题单个人计划c++版(五)


刷题链接:力扣Hot 100
每日一题,每日一更,白板手写。

力扣Hot 100

  • 21.全排列
  • 22.旋转图像
  • 23.字母异位词分组
  • 24.最大子序和
  • 25.跳跃游戏
  • 26.合并区间
  • 27.不同路径
  • 28.最小路径和
  • 29.爬楼梯
  • 30.编辑距离
  • 31.颜色分类
  • 32.最小覆盖子串
  • 33.子集
  • 34.单词搜索
  • 35.柱状图中最大的矩形
  • 36.最大矩形
  • 37.二叉树的中序遍历
  • 38.不同的二叉搜索树
  • 39.验证二叉搜索树
  • 40.对称二叉树


21.全排列

9.21打卡
数组元素不重复,且都在-10~10之间。简单递归即可。

class Solution {
public:
    vector<vector<int>> ans;
    int vis[25]={0};
    const int k=11;
    void dfs(const vector<int>& nums,vector<int>& cur,int idx,int n){
        if(idx==n){
            ans.emplace_back(cur);
            return;
        }
        for(int i=0;i<n;i++){
            if(!vis[nums[i]+k]){
                vis[nums[i]+k]=1;
                cur.push_back(nums[i]);
                dfs(nums,cur,idx+1,n);
                cur.pop_back();
                vis[nums[i]+k]=0;
            }
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        int n=nums.size();
        vector<int> tmp;
        dfs(nums,tmp,0,n);
        return ans;
    }
};

22.旋转图像

9.22打卡

  1. 空间O(1)的写法,只用一个中间变量交换四个位置。
class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n=matrix.size();
        for(int i=0;i<n/2;++i){
            for(int j=0;j<(n+1)/2;++j){
                int tmp=matrix[i][j];
                matrix[i][j]=matrix[n-j-1][i];
                matrix[n-j-1][i]=matrix[n-i-1][n-j-1];
                matrix[n-i-1][n-j-1]=matrix[j][n-i-1];
                matrix[j][n-i-1]=tmp;
            }
        }
    }
};
  1. 顺时针旋转90度也可以表示成水平翻转后对角翻转。
class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n=matrix.size();
        for(int i=0;i<n/2;++i)
            for(int j=0;j<n;++j)
                swap(matrix[i][j],matrix[n-i-1][j]);
        for(int i=0;i<n-1;++i)
            for(int j=i+1;j<n;++j)
                swap(matrix[i][j],matrix[j][i]);
    }
};

23.字母异位词分组

9.23打卡
题目第一眼想到状态压缩,然后发现可以重复字符,状压空间太大。那只能是哈希表写,根据26个字符个数也可以自己设置hash算法。另有一个题解是排序字符,尽管很麻烦但也不失为一种思路,stl模版确实方便,可以用string做key值。

  1. 排序后哈希。
class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string,vector<string>> mp;
        for(auto str:strs){
            string tmp=str;
            sort(tmp.begin(),tmp.end());
            mp[tmp].emplace_back(str);
        }
        vector<vector<string>> ans;
        for(auto it=mp.begin();it!=mp.end();++it)ans.emplace_back(it->second);
        return ans;
    }
};
  1. 统计字符哈希
    自己写hash算key最好。本人能力有限只能用STL的string型做key值。
class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string,vector<string>> mp;
        for(auto str:strs){
            int count[26]={0};
            for(int i=0;i<str.size();++i)count[str[i]-'a']++;
            string key;
            for(int i=0;i<26;i++)
                if(count[i]){
                    key.push_back(char('0'+count[i]));
                    key.push_back(char('a'+i));
                }
            mp[key].emplace_back(str);
        }
        
        vector<vector<string>> ans;
        for(auto it=mp.begin();it!=mp.end();++it)
            ans.emplace_back(it->second);
        
        return ans;
    }
};

24.最大子序和

9.24打卡
不得不说easy题的题解总是出人意料啊,还真是easy啊。

  1. 题目很熟悉,就是挑战程序设计的开篇之题,经典动态规划。
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int ans=nums[0];
        int pre=(ans>0?ans:0);
        int n=nums.size();
        for(int i=1;i<n;i++){
            pre+=nums[i];
            ans=max(ans,pre);
            if(pre<0)pre=0;
        }
        return ans;
    }
};
  1. 官方题解线段树。。。抱歉是我太菜了,打扰了。mark一下,考完研重新学一下线段树相关问题。不过这个题的思路并不难,不需要线段树全部知识,思想就是二分每一段,求出左右端点的最大连续和,逐层合并即可。尽管这个时间复杂度也是 O ( n ) O(n) O(n),甚至空间复杂度是 O ( l o g n ) O(log n) O(logn),但是这种算法实际上可以解决任意区间内的最大连续和问题。
class Solution {
public:
    struct interval{
        int isum,lsum,rsum,msum;
    };
    interval pushup(interval a,interval b){
        int isum=a.isum+b.isum;
        int lsum=max(a.lsum,a.isum+b.lsum);
        int rsum=max(b.rsum,a.rsum+b.isum);
        int msum=max(max(a.msum,b.msum),a.rsum+b.lsum);
        return (interval){isum,lsum,rsum,msum};
    }
    interval merge(vector<int>&nums,int l,int r){
        if(l==r)return (interval){nums[l],nums[l],nums[l],nums[l]};
        int m=l+((r-l)>>1);
        interval a=merge(nums,l,m);
        interval b=merge(nums,m+1,r);
        return pushup(a,b);
    }
    int maxSubArray(vector<int>& nums) {
        return merge(nums,0,nums.size()-1).msum;
    }
};

25.跳跃游戏

9.25打卡
有点类似最短路径算法Dijsktra算法。两者都是基于贪心。该题思路是每选取一个点,扩展其最远能到达的位置即可。如果该点超出当前能扩展的范围外就返回false,如果能到最后一个点,就返回true。

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int n=nums.size();
        int dj=0;
        for(int i=0;i<n;i++){
            if(i>dj)return false;
            dj=max(dj,nums[i]+i);
        }
        return true;
    }
};

26.合并区间

9.26打卡
区间问题,以为是线段树或者贪心。题解有点意外,排序后,从第一个开始不断合并即可。

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        if(intervals.size()==1)return intervals;
        sort(intervals.begin(),intervals.end());
        vector<vector<int>> merge;
        for(int i=0;i<intervals.size();i++){
            int vl=intervals[i][0],vr=intervals[i][1];
            if(i==0||merge.back()[1]<vl)
                merge.push_back({vl,vr});
            else
                merge.back()[1]=max(merge.back()[1],vr);
        }
        return merge;
    }
};

27.不同路径

9.27打卡
题目很经典了,经典dp,当然组合数学好的话可以直接推公式出来。

  1. dp,每个点的路径数等于左边格子和右边格子的路径数和。第一行和第一列就初始化为1。或者将第零行初始化,像我下面这种做法。
class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> dp(m+1,vector<int>(n+1,0));
        dp[0][1]=1;
        for(int i=1;i<=m;i++)
            for(int j=1;j<=n;j++)
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
        return dp[m][n];
    }
};
  1. 排列组合,向下移动m-1次,向右移动n-1次。因此路径的总数,就等于从 m+n-2m+n−2 次移动中选择 m-1次向下移动的方案数。mark下组合数的模版:总结组合数的几种求法(模板)。待补。

28.最小路径和

9.28打卡
简单dp,首先初始化第一列和第一行的值,这两个是没法变的,然后从 g r i d [ 1 ] [ 1 ] grid[1][1] grid[1][1]开始选择上方和右边最小的一个。

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int n=grid.size(),m=grid[0].size();
        for(int i=1;i<m;i++)grid[0][i]+=grid[0][i-1];
        for(int i=1;i<n;i++)grid[i][0]+=grid[i-1][0];
        for(int i=1;i<n;i++){
            for(int j=1;j<m;j++){
                grid[i][j]+=min(grid[i-1][j],grid[i][j-1]);
            }
        }
        return grid[n-1][m-1];
    }
};

29.爬楼梯

9.29打卡
经典斐波那契题了。

  1. 变量迭代。
class Solution {
public:
    int climbStairs(int n) {
        int a=0,b=0,c=1;
        while(n--){
            a=b;
            b=c;
            c=a+b;
        }
        return c;
    }
};
  1. 矩阵快速幂,每次将矩阵的2n次方转化成n次方乘n次方,相当于每次少算一半的计算量。 O ( l o g n ) O(log n) O(logn)时间复杂度。
class Solution {
public:
    vector<vector<long long>> multiply(vector<vector<long long>>& a,vector<vector<long long>>& b){
        vector<vector<long long>> c(2, vector<long long>(2));
        for(int i=0;i<2;i++)
            for(int j=0;j<2;j++)
                c[i][j]=a[i][0]*b[0][j]+a[i][1]*b[1][j];
        return c;
    }
    vector<vector<long long>> mulpow(vector<vector<long long>> a,int n){
        vector<vector<long long>> ret={{1,0},{0,1}};
        while(n){
            if(n&1)ret=multiply(ret,a);
            n>>=1;
            a=multiply(a,a);
        }
        return ret;
    }
    int climbStairs(int n) {
        vector<vector<long long>> a={{1,1},{1,0}};
        vector<vector<long long>> ans=mulpow(a,n);
        return ans[0][0];
    }
};
  1. 通项公式法。数学解法,解一个齐次递推方程即可。此处不提。

30.编辑距离

9.30打卡
字符串的经典dp了,老题。结果改了改三个操作还是不会。参考官方题解。

class Solution {
public:
    int minDistance(string word1, string word2) {
        int n=word1.size();
        int m=word2.size();
        if(!n*m)return n+m;
        int dp[n+1][m+1];
        for(int i=0;i<=n;i++)dp[i][0]=i;
        for(int i=0;i<=m;i++)dp[0][i]=i;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                int a=dp[i-1][j]+1;
                int b=dp[i][j-1]+1;
                int c=dp[i-1][j-1]+int(word1[i-1]!=word2[j-1]);
                dp[i][j]=min(a,min(b,c));
            }
        }
        return dp[n][m];
    }
};

31.颜色分类

10.1打卡
要求:仅使用常数空间的一趟扫描算法,且为原地排序(否则直接计算0,1,2的个数即可)。显然双指针算法,一个指向前面换遍历到的0,一个指向后面换遍历到的2。题解的另一种算法大同小异,即一个指向末尾0的位置,一个指向末尾1的位置。

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int n=nums.size();
        int p0=0,p2=n-1;
        for(int i=0;i<n;++i){
            while(nums[i]==2&&i<p2)swap(nums[i],nums[p2--]);
            if(nums[i]==0)swap(nums[i],nums[p0++]);
        }
    }
};

32.最小覆盖子串

10.2打卡
不需要顺序一致,明显是滑动窗口,统计窗口内的字符和目标字符个数能不能对上即可。

class Solution {
public:
    unordered_map<char,int> ms,mt;
    bool check(){
        for(const auto &p:mt)
            if(ms[p.first]<p.second)return false;
        return true;
    }

    string minWindow(string s, string t) {
        for(auto &c:t)++mt[c];
        int l,r;
        l=r=0;
        int n=s.size();
        int minlen=n+1,pos=0;
        while(r<n){
            if(mt.count(s[r])){
                ++ms[s[r]];
            }
            while(check()&&l<=r){
                if(r-l+1<minlen){
                    minlen=r-l+1;
                    pos=l;
                }
                if(mt.count(s[l]))--ms[s[l]];
                ++l;
            }
            ++r;
        }
        return minlen==(n+1)?string():s.substr(pos,minlen);
    }
};

33.子集

10.3打卡
由于元素不相同,其实就是01背包,对于一个元素只有选和不选,遍历所有情况即可。可以采用状压法, ( 0 , 2 n − 1 ) (0,2^{n}-1) (0,2n1)对应了每一种情况,也可以选择回溯法,对每一个数字选和不选即可。
1.状压法

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        int n=nums.size();
        vector<vector<int>> ans;
        for(int i=0;i<(1<<n);++i){
            vector<int> t;
            for(int j=0;j<n;j++){
                if(i&(1<<j))t.emplace_back(nums[j]);
            }
            ans.emplace_back(t);
        }
        return ans;
    }
};

2.回溯法

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> t;
    void dfs(int cur,const int n,vector<int>& nums){
        if(cur==n){
            ans.emplace_back(t);
            return;
        }
        t.emplace_back(nums[cur]);
        dfs(cur+1,n,nums);
        t.pop_back();
        dfs(cur+1,n,nums);
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        int n=nums.size();
        dfs(0,n,nums);
        return ans;
    }
};

3.还有一个独特的思路,初始化一个空的解集,对于每个数字,遍历解集,每次加入该数字,也能生成解集。其实也可以理解为,所有解集的每个位置的0,都变成一次1,就能得到全部解集。

class Solution {
public:
    vector<vector<int>> ans;
    vector<vector<int>> subsets(vector<int>& nums) {
        int n=nums.size();
        vector<int> t;
        ans.emplace_back(t);
        for(int i=0;i<n;++i){
            int m=ans.size();
            for(int j=0;j<m;++j){
                t=ans[j];
                t.push_back(nums[i]);
                ans.emplace_back(t);
            }
        }
        return ans;
    }
};

34.单词搜索

10.5打卡。简单的搜索。

class Solution {
public:
    vector<pair<int, int>> directions{{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
    bool dfs(vector<vector<char>>& board,int cur,int curx,int cury,const int n,string word){
        if(cur==n){
            return true;
        }
        bool res=false;
        for(auto& dir:directions){
            int nextx=curx+dir.first,nexty=cury+dir.second;
            if(nextx<0||nextx>=board.size()||nexty<0|nexty>=board[0].size())continue;
            if(board[nextx][nexty]==word[cur]){
                char c=board[nextx][nexty];
                board[nextx][nexty]=0;
                if(dfs(board,cur+1,nextx,nexty,n,word)){
                    res=true;
                    break;
                }
                board[nextx][nexty]=c;
            }
        }
        return res;
    }
    bool exist(vector<vector<char>>& board, string word) {
        int n=board.size();
        int m=board[0].size();
        int len=word.size();
        for(int i=0;i<n;++i){
            for(int j=0;j<m;++j){
                if(board[i][j]==word[0]){
                    char c=board[i][j];
                    board[i][j]=0;
                    if(dfs(board,1,i,j,len,word))return true;;
                    board[i][j]=c;
                }
            }
        }
        return false;
    }
};

35.柱状图中最大的矩形

10.5打卡
题解是单调栈,更容易理解一些,但本人训练acm时期恰好做过原题,hdoj1506-Largest Rectangle in a Historgram。本题算是非常经典的dp题,该题上一道hdoj1505是本题扩展到二维的题目。本题实际上是对于每一个高度,快速找到左右边界。这样我们可以用dp数组来记录该位置上一个边界地址,每次直接比较原高度和相邻边界,即可找到最终位置。该代码相较官方题解更难理解,但更加快捷。

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int n=heights.size();
        vector<int> dpl(n);
        vector<int> dpr(n);
        dpl[0]=0;dpr[n-1]=n-1;
        for(int i=1;i<n;++i){
            int t=i;
            while(t>0&&heights[i]<=heights[t-1])
                t=dpl[t-1];
            dpl[i]=t;
        }
        for(int i=n-2;i>=0;--i){
            int t=i;
            while(t<(n-1)&&heights[i]<heights[t+1])
                t=dpr[t+1];
            dpr[i]=t;
        }
        int ans=0;
        for(int i=0;i<n;i++){
            int tmp=heights[i]*(dpr[i]-dpl[i]+1);
            ans=max(ans,tmp);
        }
        return ans;
    }
};

36.最大矩形

10.6打卡
实际上为hdoj1505,即上一题的二维形式。将每一层加到下一层,对于每一行来说就形成了上一题的题目,选出所有行中最大的即可。这次代码用到了上一题中官方题解的单调栈。实际上换成35题代码也可。

class Solution {
public:
        int largestRectangleArea(vector<int>& heights) {
        int n = heights.size();
        vector<int> left(n), right(n, n);
        
        stack<int> mono_stack;
        for (int i = 0; i < n; ++i) {
            while (!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) {
                right[mono_stack.top()] = i;
                mono_stack.pop();
            }
            left[i] = (mono_stack.empty() ? -1 : mono_stack.top());
            mono_stack.push(i);
        }
        
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            //cout<<"left: "<
            ans = max(ans, (right[i] - left[i] - 1) * heights[i]);
        }
        return ans;
    }
    int maximalRectangle(vector<vector<char>>& matrix) {
        int n=matrix.size();
        if(n==0)return 0;
        int m=matrix[0].size();
        vector<vector<int>> mp(n,vector<int>(m,0));
        for(int i=0;i<n;++i){
            for(int j=0;j<m;++j){
                if(matrix[i][j]=='1')mp[i][j]=(i==0?0:mp[i-1][j])+1;
            }
        }
        int ans=0;
        for(int i=0;i<n;++i)
            ans=max(ans,largestRectangleArea(mp[i]));
        return ans;
    }
};

37.二叉树的中序遍历

10.7打卡
可以递归的话是简单题,中序遍历,函数递归可以很好解决遍历思路。

class Solution {
public:
    void inparse(TreeNode* pos,vector<int>& t){
        if(pos==nullptr)return;
        inparse(pos->left,t);
        t.emplace_back(pos->val);
        inparse(pos->right,t);
    }
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans;
        inparse(root,ans);
        return ans;
    }
};

再写个非递归版本的吧。题解的morris写法理解就行了,个人感觉写这个真就没必要了。

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans;
        stack<TreeNode*> st;
        TreeNode* p=root;
        while(p!=nullptr||!st.empty()){
            while(p!=nullptr){
                st.push(p);
                p=p->left;
            }
            p=st.top();st.pop();
            ans.emplace_back(p->val);
            p=p->right;
        }
        return ans;
    }
};

38.不同的二叉搜索树

10.9打卡
组合数学的卡特兰数,只能说数学题没见过根本想不出。

class Solution {
public:
    int numTrees(int n) {
        vector<int> g(n+1,0);
        g[0]=1;
        g[1]=1;
        for(int i=2;i<=n;++i){
            for(int j=1;j<=i;++j){
                g[i]+=g[j-1]*g[i-j];
            }
        }
        return g[n];
    }
};

39.验证二叉搜索树

10.9打卡
递归检查是否在区间内即可。

class Solution {
public:
    bool check(TreeNode* pos,long long l,long long r){
        if(pos==nullptr)return true;
        if(pos->val <= l||pos->val >= r)return false;
        return check(pos->left,l,pos->val)&&check(pos->right,pos->val,r);
    }
    bool isValidBST(TreeNode* root) {
        return check(root,LONG_MIN,LONG_MAX);
    }
};

另一种解法是求出其中序遍历的数组,检查其是否升序即可。这是数据结构的知识,不多做说明,利用前面中序遍历的迭代写法可以写出。

40.对称二叉树

10.10打卡
递归写法很简单,每次判断对称两边的值是否相等,然后继续判断左子树的左右节点和右子树的右左节点是否一致。迭代写法即用队列存储每层,判断第一个和最后一个是否一致即可。此处只给出递归版本。

class Solution {
public:
    bool check(TreeNode* l,TreeNode* r){
        if(!l&&!r)return true;
        if(!l||!r)return false;
        if(l->val==r->val)
            return check(l->left,r->right)&&check(l->right,r->left);
        return false;
    }
    bool isSymmetric(TreeNode* root) {
        if(root==nullptr)return true;
        return check(root->left,root->right);
    }
};

你可能感兴趣的:(个人笔记,leetcode,算法,数据结构)