【LeetCode高频100题-3】冲冲冲(持续更新23.2.1)

62. 不同路径

题意

  • 一道数学题,排列组合/小学奥赛题。
  • 动态规划不是一般来解决最值问题的吗,这道题为什么会想到dp?

解法1 排列组合

从左上角到右下角,一共要走m+n-2步,其中向右n-1步,向下m-1步,因此路径的总数,相当于从m+n-2中选择m-1个向下的步数,即排列组合。
在这里插入图片描述

  • 但是,需要注意的是,题目只保证最后结果在int型范围内,而实际上如果按下面的代码运行,即便中间运算已经用long long存储,还是会溢出,所以需要一边乘一边除(即便是一边乘一边除,中间过程也必须用long long,否则中间计算会超出int型可表示范围)。
class Solution {
public:
    int uniquePaths(int m, int n) {
        long long ans=1;
        for(int i=n;i<=m+n-2;i++)
            ans=ans*i; 	//会溢出
        for(int i=1;i<m;i++)
            ans/=i;
        return ans;
    }
};
// ac代码
class Solution {
public:
    int uniquePaths(int m, int n) {
        long long ans=1;
        for(int i=n,j=1;i<=m+n-2;i++,j++)
            ans=ans*i/j;
        return ans;
    }
};

解法2 动态规划

  • dp[i][j]表示走到 (i,j) 这个位置有几种走法。
  • dp[i][j]=dp[i-1][j]+dp[i][j-1]
  • 注意dp[0][0]和边界情况(i-1j-1)处理(也可以将dp[0,:]dp[:,0]全置1)。
class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int> > dp(m,vector<int>(n,0));
        dp[0][0]=1;
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
                if(dp[i][j]==0) 	//为了维护dp[0][0]
                {
                    int left=j==0?0:dp[i][j-1];
                    int top=i==0?0:dp[i-1][j];
                    dp[i][j]=left+top;
                }
            }
        }
        return dp[m-1][n-1];
    }
};

Attention

  • 二维数组的定义
vector<vector<int>> asd1(row, vector<int>(column, 0)); //初始化row*column二维动态数组,初始化值为0
  • 动态规划解法中,其实只需要保存dp[i-1][j]dp[i][j-1]两个数,还有空间优化的余地。
  • 排列组合基础
    在这里插入图片描述
    在这里插入图片描述

64. 最小路径和

题意

  • 只能向右或向下走,约束很强,所以很好遍历!
  • 求最小值。
  • 和第62题有相似之处。

解法1 DFS(剪枝也超时)

建立一个队列q和一个数组value[](记录每个点的最小值),将(0,0)压入队列,然后每从队列中取出一个点 (x,y) ,就将其右 (x+1,y) 和下 (x,y+1) 两个点压入队列中,同时更新其右 (x+1,y) 和下 (x,y+1) 两个点的最小值。

但是由于超时,需要剪枝。所以只有当前点使得其右 (x+1,y) 或下 (x,y+1) 的点的最小值被更新时,才将这个点压入队列中。

但是依旧超时,,,

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        // m行n列
        int m=grid.size(),n=grid[0].size();
        vector<vector<int>> value(m,vector<int>(n,-1));
        queue<pair<int,int> > q;
        pair<int,int> st(0,0);
        q.push(st);
        value[0][0]=grid[0][0];
        while(!q.empty())
        {
            pair<int,int> tmp=q.front();
            q.pop();
            int x=tmp.first,y=tmp.second;
            if(x+1<m&&y<n)
            {
                int tmp_value=value[x][y]+grid[x+1][y];
                if(value[x+1][y]!=-1)
                {
                    if(tmp_value<=value[x+1][y])
                    {
                        value[x+1][y]=tmp_value;
                        pair<int,int> nxt(x+1,y);
                        q.push(nxt);
                    }
                }
                else
                {
                    value[x+1][y]=tmp_value;
                    pair<int,int> nxt(x+1,y);
                    q.push(nxt);
                }
                    
            }
            if(y+1<n&&x<m)
            {
                int tmp_value=value[x][y]+grid[x][y+1];
                if(value[x][y+1]!=-1)
                {
                    if(tmp_value<=value[x][y+1])
                    {
                        value[x][y+1]=tmp_value;
                        pair<int,int> nxt(x,y+1);
                        q.push(nxt);
                    }
                }
                else
                {
                    value[x][y+1]=tmp_value;
                    pair<int,int> nxt(x,y+1);
                    q.push(nxt);
                }
            }
        }
        return value[m-1][n-1];
    }
};

解法2 动态规划

dp[i][j]是到达点 (i,j) 时路径的最小值,则dp[i][j]=min(dp[i-1[j]+grid[i][j],dp[i][j-1]+grid[i][j])

和第62题不同的处理是,这里左边界和上边界的点要单独处理。因为最左列的点只能从它上面的点过来,而最上行的点只能从它左边过来。

其他没什么难点。

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        // m行n列
        int m=grid.size(),n=grid[0].size();
        vector<vector<int>> dp(m,vector<int>(n,-1));
        dp[0][0]=grid[0][0];
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
                if(i==0&&j!=0)
                    dp[i][j]=dp[i][j-1]+grid[i][j];
                else if(j==0&&i!=0)
                    dp[i][j]=dp[i-1][j]+grid[i][j];
                else
                {
                    int left=j-1>=0?dp[i][j-1]:0;
                    int top=i-1>=0?dp[i-1][j]:0;
                    dp[i][j]=min(left+grid[i][j],top+grid[i][j]);
                }
            }
        }
        return dp[m-1][n-1];
    }
};

ATTENTION


70. 爬楼梯

题意

  • 动态规划典型例题

解法1 动态规划

dp[i]是到达第 i 级台阶的方法数量。由于第i级台阶可以通过第 i-1 和 i-2 级台阶到达,所以dp[i]=dp[i-1]+dp[i-2]

边界条件:由于第 0 级台阶和第 1 级台阶都只有 1 种到达方法,所以初始化dp[0]=dp[1]=1

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

解法2 矩阵快速幂(待补充)


72.编辑距离(waiting)


75. 颜色分类(待整理)

题意

  • 啊?这不是直接冒泡就能结束了吗,,,
  • 荷兰国旗问题?

解法1


76.最小覆盖子串

题意

  • 在字符串S中寻找包含字符串T所有字符的最小子串。
  • 字符串T可能有重复字符。
  • 覆盖子串唯一

解法1 滑动窗口(有待复习)

需要维护字符频数数组

暴力枚举
首先遍历字符串T,得到字符频数数组。然后枚举所有长度大于等于T.size()的子串,再遍历每个子串,与字符串T进行比较。时间复杂度O(|S|3+|T|)。
冗余:因为求的是最小字串,所以当一个子串s[i,j]已经包含字符串T的所有字符时,以s[i]为起始的更长子串就不用再考虑了。

滑动窗口

  • 首先将右边界右移,直到子串覆盖字符串T的所有字符。
  • 然后将左边界右移,得到当前最小覆盖子串。
  • 然后将左右边界都右移,回到第一步,寻找下一个符合的覆盖子串。
class Solution {
public:
    unordered_map<char,int> cur_cnt,t_cnt;
    int cur_tot=0,t_tot=0;
    bool is_cover() 	//是否覆盖的判断还是挺坑的
    {
        if(cur_tot<t_tot) return false; 
        for(auto it:t_cnt)
        {
            if(cur_cnt[it.first]<it.second) 	//覆盖子串的字符数可能比字符串T多
                return false;
        }
        return true;
    }
    string minWindow(string s, string t) {
        int s_n=s.size(),t_n=t.size();
        // string ans=s;
        string ans="";

        // t的词频数组
        for(auto &c:t)
            t_cnt[c]+=1;
        t_tot=t_n;

        int l=0,r=0;
        cur_cnt[s[l]]+=1;
        cur_tot+=1;
        while(true)
        {
            while(!is_cover()&&r<s_n)
            {
                r++;
                cur_cnt[s[r]]+=1;
                cur_tot+=1;
            }
            // 溢出
            if(r==s_n)
                break;
            else  //cover
            {
                while(is_cover())
                {
                    cur_cnt[s[l]]-=1;
                    cur_tot-=1;
                    l++;
                }
                // 一个覆盖子串,不一定是最小的
                // ans=ans.size()>(r-l+1)?s.substr(l-1,r-l+2):ans;   //更新
                if(ans==""||ans.size()>(r-l+1))
                    ans=s.substr(l-1,r-l+2);
                r++;
                cur_cnt[s[r]]+=1;
                cur_tot+=1;
            }
        }
        return ans;
    }
};

ATTENTION

  • auto(待整理)

78.子集


94. 二叉树的中序遍历

题意

解法

中序遍历:左-根-右。

当在最底层时,每个结点都可视为一棵树的,所以,应当在遍历到的时候进行 push 操作。

对于根,首先要判断是否为 NULL。然后再遍历其左子树,push root,遍历右子树。

/**
 * 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>& ans)
    {
        if(root==NULL) return; 	//这个很重要,也就是说现将root传进来,再判断它存不存在。 
        inOrder(root->left,ans);
        ans.push_back(root->val);
        inOrder(root->right,ans);
    }
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans;
        inOrder(root,ans);
        return ans;
    }
};
//这个版本有冗余判断
/**
 * 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>& ans)
    {
        if(root==NULL) return ;
        if(root->left!=NULL) 	//冗余
            inOrder(root->left,ans);
        ans.push_back(root->val);
        if(root->right!=NULL) 	//冗余
            inOrder(root->right,ans);
    }
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans;
        inOrder(root,ans);
        return ans;
    }
};

98. 验证二叉搜索树

题意

  • 验证所给的二叉树是不是二叉搜索树,要求:左 < 根 < 右。
  • 要注意:整颗左子树 < 根 < 整颗右子树
  • 节点值的范围:[INT_MIN, INT_MAX]

解法

基于match(root,l,r)考虑每一个节点 root :

  • 若 root == NULL,则返回 true;
  • 若 root != NULL,则判断它的值在不在限定范围 (l ,r) 内,不在返回 false;
  • 若 root != NULL,且它的值在范围 (l ,r) 内,则只有当 root->left 和 root->right 都满足时,才返回 true。

注意:节点元素范围为 [INT_MIN, INT_MAX],所以 root 的值的范围应该比 int 大(因为是开区间),否则,若节点为 INT_MIN 或 INT_MAX ,会误判。(比如只有一个节点,节点的值为 INT_MAX,如果将范围设为(INT_MIN, INT_MAX)的话,会误判。)

/**
 * 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 match(TreeNode* root,long long l,long long r)
    {
        if(root==NULL) return true;
        if(root->val>=r || root->val<=l) return false;
        return match(root->left,l,root->val)&&match(root->right,root->val,r);
    }
    bool isValidBST(TreeNode* root) {
        return match(root,LLONG_MIN,LLONG_MAX);
    }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值