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-1
和j-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
- pair的用法
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 :文章来源:https://www.toymoban.com/news/detail-418622.html
- 若 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)的话,会误判。)文章来源地址https://www.toymoban.com/news/detail-418622.html
/**
* 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);
}
};
到了这里,关于【LeetCode高频100题-3】冲冲冲(持续更新23.2.1)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!