2022-01-26每日刷题打卡
飞书——每日一题
494. 目标和
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
示例 1:
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
广度搜索(或是说记忆化搜索?),从第一个元素开始,每次创出两个分支,一个是加上当前元素值,一个是减去当前元素值,当遍历完所有元素后,判断总和是否等于target。如果等于,则计数器+1。等所有搜索结束后,返回计数器。
class Solution {
public:
int count=0;
int findTargetSumWays(vector<int>& nums, int target) {
int sum=0,ans=0;
bfs(nums,target,sum,ans);
return count;
}
void bfs(vector<int>& nums, int target,int sum,int ans)
{
if(ans==nums.size())
{
if(sum==target)count++;
return;
}
bfs(nums,target,sum+nums[ans],ans+1);
bfs(nums,target,sum-nums[ans],ans+1);
}
};
429. N 叉树的层序遍历
给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。
树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。
示例 1:
输入:root = [1,null,3,2,4,null,5,6]
输出:[[1],[3,2,4],[5,6]]
要把树的每一层分别存下来,用的是广度优先搜索,这里多叉树和二叉树的区别就是多叉树的子树都存在数组里,二叉树只有两个孩子,只要把多叉树的子树节点从数组中取出就行。
/*
// Definition for a Node.
class Node {
public:
int val;
vector<Node*> children;
Node() {}
Node(int _val) {
val = _val;
}
Node(int _val, vector<Node*> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
vector<vector<int>>v;
if(!root)return v;
queue<Node*>que;
que.push(root);
while(que.size())
{
int len=que.size();
vector<int>ans;
for(int i=0;i<len;i++)
{
ans.push_back(que.front()->val);
if(que.front()->children.size())
{
for(int i=0;i<que.front()->children.size();i++)
{
que.push(que.front()->children[i]);
}
}
que.pop();
}
v.push_back(ans);
}
return v;
}
};
797. 所有可能的路径
给你一个有 n 个节点的 有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序)
graph[i] 是一个从节点 i 可以访问的所有节点的列表(即从节点 i 到节点 graph[ i] [ j]存在一条有向边)。
示例 1:
输入:graph = [[1,2],[3],[3],[]]
输出:[[0,1,3],[0,2,3]]
解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3
这个二维数组,其实就是个邻接矩阵,解释起来就是:graph[0]是{1,2},就是说点0可以走到点1和点2,再去看下标1即graph[1]是{3},就是说点1可以走到点3。
我们进行深度搜索,一开始先从点0出发,看可以到达哪些点,存入数组中,然后到达那个点,继续看那个点能到达哪些点……以此往复,当到达的点是最后一个点时,把数组存入一个二维数组中,这就算一条路线了。
class Solution {
public:
vector<vector<int>>v;
vector<int>ans;
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
ans.push_back(0);
bfs(graph,0,graph.size()-1);
return v;
}
void bfs(vector<vector<int>>& graph,int x,int n)
{
if(x==n)
{
v.push_back(ans);
return;
}
for(auto i:graph[x])
{
ans.push_back(i);
bfs(graph,i,n);
ans.pop_back();
}
}
};
802. 找到最终的安全状态
在有向图中,以某个节点为起始节点,从该点出发,每一步沿着图中的一条有向边行走。如果到达的节点是终点(即它没有连出的有向边),则停止。
对于一个起始节点,如果从该节点出发,无论每一步选择沿哪条有向边行走,最后必然在有限步内到达终点,则将该起始节点称作是 安全 的。
返回一个由图中所有安全的起始节点组成的数组作为答案。答案数组中的元素应当按 升序 排列。
该有向图有 n 个节点,按 0 到 n - 1 编号,其中 n 是 graph 的节点数。图以下述形式给出:graph[i] 是编号 j 节点的一个列表,满足 (i, j) 是图的一条有向边。
示例 1:
输入:graph = [[1,2],[2,3],[5],[0],[5],[],[]]
输出:[2,4,5,6]
解释:示意图如上。
这里题目所说的“安全点”就是说某个点没有出度(即到达不了下一个点)、或是说某个点的所有路径都可以到达前者。比如说这里的测试例,点5和点6到达不了下一个点,所以这两个是安全点,点4和2都只有一条通往点5的路,所以他俩也是安全点。但点0,点0可以到达点1、2,虽然点2也是安全点,但点1还能到达点3,点3可以到达0,即形成了一个闭环(黄金体验镇魂曲!点3永远也到达不了安全点的事实),所以这里的答案是2 4 5 6。
这题的解法是拓扑排序。拓扑排序的知识点感兴趣的自己去搜搜看,反正就是说,一个点有多少向外延申的边,这个点的出度就是多少,反之有多少边可以到达这个点,这个点的入度就是多少,比如点0,可以到达1和2,所以出度是2,而点3可以到达点0,所以入度是1。拓扑排序就是每次取一个入度为0的点为起点,然后把这个点拿出来,同时,这个点到达其他点的路径都去掉,即入度-1。比如这里,点4的入度是0,所以可以当成是拓扑排序的起点,然后因为点4可以到达点5,所以点5的入度-1。如果一个点的入度因此又变成了0,那么这个点就可以当成拓扑排序的下一个点。
那么拓扑排序和这题有什么关系呢,这题要我们找出所有的安全点,即出度为0和任意路径都能到达安全点的点。我们可以把这个图翻转过来,拿测试例来说,原本是0可以到1、2,反转后就是1和2可以到0。对应的,原本是3到0,现在是0到3,可以看出,反转后入度和出度调换了。那么安全点的条件就变成了:入度为0的点,和只有入度为0的点能到达的点。发现没有,这就是拓扑排序的条件和作用。我们反转后,找到所有入度为0的点为拓扑排序的起点,这个起点也同时就是安全点。然后看这个安全点能到达的点,如果这个点的入度能减为0,说明这个点只有安全点能到达,说明这个点也是一个安全点。走完一边拓扑排序后,把拓扑排序的点都存入数组里(按照升序排序)。
class Solution {
public:
vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
int n=graph.size();
vector<vector<int>>v(n);
vector<int>rd(n);
for(int i=0;i<n;i++)
{
for(auto j:graph[i])
{
v[j].push_back(i);
}
rd[i]=graph[i].size();
}
queue<int>que;
for(int i=0;i<n;i++)
{
if(rd[i]==0)
que.push(i);
}
while(que.size())
{
int t=que.front();
que.pop();
for(int i=0;i<v[t].size();i++)
{
rd[v[t][i]]--;
if(rd[v[t][i]]==0)
{
que.push(v[t][i]);
}
}
}
vector<int>ans;
for(int i=0;i<n;i++)
{
if(rd[i]==0)ans.push_back(i);
}
return ans;
}
};
841. 钥匙和房间
有 n 个房间,房间按从 0 到 n - 1 编号。最初,除 0 号房间外的其余所有房间都被锁住。你的目标是进入所有的房间。然而,你不能在没有获得钥匙的时候进入锁住的房间。
当你进入一个房间,你可能会在里面找到一套不同的钥匙,每把钥匙上都有对应的房间号,即表示钥匙可以打开的房间。你可以拿上所有钥匙去解锁其他房间。
给你一个数组 rooms 其中 rooms[i] 是你进入 i 号房间可以获得的钥匙集合。如果能进入 所有 房间返回 true,否则返回 false。
示例 1:
输入:rooms = [[1],[2],[3],[]]
输出:true
解释:
我们从 0 号房间开始,拿到钥匙 1。
之后我们去 1 号房间,拿到钥匙 2。
然后我们去 2 号房间,拿到钥匙 3。
最后我们去了 3 号房间。
由于我们能够进入每个房间,我们返回 true。
准备一个长度和rooms一样的数组v,初始化都为0,当进入某个房间后,在数组v中把对应的房间序号从0变成1。一开始以0房间为起点开始广度搜索,在数组v中把对应房间序号变成1,每次根据房间里的钥匙进入对应的下一个房间,但如果已经是1了,则没必要进入下一个房间了(反正进入过了,说明该房间的钥匙我们也都拿过来了,还进去干嘛)。广度搜索结束后,遍历一遍数组v,如果还有房间是0,说明这房间没有进去过,返回false,如果遍历完还没有返回false,则返回true。
class Solution {
public:
bool canVisitAllRooms(vector<vector<int>>& rooms) {
int n=rooms.size();
vector<int>v(n);
v[0]=1;
bfs(rooms,v,0);
for(int i=0;i<n;i++)
{
if(v[i]==0)return false;
}
return true;
}
void bfs(vector<vector<int>>& rooms,vector<int>& v,int u)
{
for(auto i:rooms[u])
{
if(v[i]!=0)continue;
v[i]=1;
bfs(rooms,v,i);
}
}
};
865. 具有所有最深节点的最小子树
给定一个根为 root 的二叉树,每个节点的深度是 该节点到根的最短距离 。
返回包含原始树中所有 最深节点 的 最小子树 。
如果一个节点在 整个树 的任意节点之间具有最大的深度,则该节点是 最深的 。
一个节点的 子树 是该节点加上它的所有后代的集合。
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4]
输出:[2,7,4]
解释:
我们返回值为 2 的节点,在图中用黄色标记。
在图中用蓝色标记的是树的最深的节点。
注意,节点 5、3 和 2 包含树中最深的节点,但节点 2 的子树最小,因此我们返回它。
nmd这个题目描述,搞不懂什么是最小子树,后来看了评论才知道,就是说如果这个树的左右子节点都是最大深度的话,这个节点就是最小子树(人话就是最大深度节点的公共祖先)。比如这里,最大深度的节点是7和4,这两个节点的公告祖先就是2。如果是
0
/ \
1 2
\
3
的话,最深节点是3,这个节点的公共祖先就是他自己了,返回3。
所以就是,每次判断左右子树的深度,如果深度一样,就说明当前节点就是我们要的最小子树,返回当前节点。如果是左子树的深度大,说明最小子树在当前节点的左子树,去左子树找最小子树,反之如果右子树深度大,就去右子树找最小子树。
/**
* 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* subtreeWithAllDeepest(TreeNode* root) {
if(!root)return root;
int left=getDeep(root->left);
int right=getDeep(root->right);
if(left==right)return root;
else if(left>right)return subtreeWithAllDeepest(root->left);
else return subtreeWithAllDeepest(root->right);
}
int getDeep(TreeNode *root)
{
if(!root)return 0;
return 1+max(getDeep(root->right),getDeep(root->left));
}
};
958. 二叉树的完全性检验
给定一个二叉树的 root ,确定它是否是一个 完全二叉树 。
在一个 完全二叉树 中,除了最后一个关卡外,所有关卡都是完全被填满的,并且最后一个关卡中的所有节点都是尽可能靠左的。它可以包含 1 到 2h 节点之间的最后一级 h 。
示例 1:
输入:root = [1,2,3,4,5,6]
输出:true
解释:最后一层前的每一层都是满的(即,结点值为 {1} 和 {2,3} 的两层),且最后一层中的所有结点({4,5,6})都尽可能地向左。
这题写的有点繁琐了。用广度搜索一层一层遍历二叉树,每次遍历新的一层时,判断当前层的节点数是否满足完全二叉树的标准(第一层1个节点,第二次2个,第三次4个),如果不满足,拿一个bool变量记录下来,然后开始遍历时,如果此层有节点有子节点的话,说明这不是一个完全二叉树,因为完全二叉树正常情况每层都是符合前面的标准的,如果该层节点数不满足标准,说明这一层就是最后一层了,这些节点都是叶子节点,这样是允许节点数不满足标准的,既然是叶子节点,怎么会有子节点呢,说明这层不是最后一层,但节点数不满足,所以返回false。如果此层节点数满足标准,当遍历时,如果有节点没有左节点,先判断它有没有右节点,如果有说明不符合完全二叉树,返回false,如果也没有右节点,那判断它是不是这一层的最后一个节点,如果不是,说明也不是完全二叉树,返回false。因为前面知道了,这一层不是最后一层,这一层的节点必定是有子节点的,如果没有子节点,说明这个可能是这一层的最后一个节点,如果不是最后一个节点那显然就不是完全二叉树。如果这个节点有左节点,就把左节点入队,再判断这个节点是否有右节点,如果有就入队,如果没有,就判断这个节点是不是最后一个节点,如果不是,返回false,原理同上。当广度搜索结束后,还没有返回false,说明这是一个完全二叉树,返回true。
/**
* 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 isCompleteTree(TreeNode* root) {
if (!root)return true;
queue<TreeNode*>que;
que.push(root);
bool flag = true,st=true;
int ans = 1;
while (que.size())
{
int n = que.size();
st = true;
if (n != ans)flag = false;
for (int i = 0; i < n; i++)
{
if (que.front()->left != NULL)
{
if (!flag)return false;
else if (!st)return false;
que.push(que.front()->left);
}
else
{
if (que.front()->right != NULL)return false;
st = false;
}
if (que.front()->right != NULL)
{
if (!flag)return false;
que.push(que.front()->right);
}
else st = false;
que.pop();
}
ans *= 2;
}
return true;
}
};
967. 连续差相同的数字
返回所有长度为 n 且满足其每两个连续位上的数字之间的差的绝对值为 k 的 非负整数 。
请注意,除了 数字 0 本身之外,答案中的每个数字都 不能 有前导零。例如,01 有一个前导零,所以是无效的;但 0 是有效的。
你可以按 任何顺序 返回答案。
示例 1:
输入:n = 3, k = 7
输出:[181,292,707,818,929]
解释:注意,070 不是一个有效的数字,因为它有前导零。
广度优先搜索,准备一个数组v,for循环1到9的数字(不能有0,因为这里我们要准备的是第一位的数字,而第一位数字不能是0),然后计算所有+k后小于等于9的数,和-k后大于等于0的数,把数存入数组里,开始广度搜索,每次先判断,数的位数是否等于n,如果等于,就结束程序。bfs中准备一个数组ans和一个哈希表,哈希表以数字为key,出现次数为val,从v中取出之前存下的数,然后在尾部添加与前一位数差值为k的数,然后判断一下这个数有没有存过哈希表里,如果没有就把数存入数组ans里,并把这个数存入哈希表中。如果这个数出现在哈希表里了,就不再把这个数存入数组中,这一步是为了防止出现重复的元素。比如当n=2,k=0时,结果应该是11、22、33等,但要是不去重的话,结果就可能是11、11、22、22等,这是因为我们在给数添加新的尾部时,计算前一位数的差值时,是进行了+和-的操作的,比如k=3时,如果第一位数字是5,那就会有52或58两个数。而当k是0时,+和-的操作得到的数是一样的,比如第一位数字是5,那就会得到55和55,如果都把这两个数存入数组中显然是不正确的,所以要去重。等这一步的广度搜索结束后,把数组ans的值全赋给v,然后进行下一次广度搜索,直到数字的位数等于n为止。
class Solution {
public:
vector<int>v;
vector<int> numsSameConsecDiff(int n, int k) {
for(int i=1;i<=9;i++)
{
if(i+k<=9)v.push_back(i);
else if(i-k>=0)v.push_back(i);
}
bfs(n,k,1);
return v;
}
void bfs(int n,int k,int u)
{
if(u==n)return ;
int num;
vector<int>ans;
unordered_map<int,int>mymap;
for(auto i:v)
{
if(i%10+k<=9)
{
num=i*10+i%10+k;
if(mymap[num]==0)
{
ans.push_back(num);
mymap[num]=1;
}
}
if(i%10-k>=0)
{
num=i*10+i%10-k;
if(mymap[num]==0)
{
ans.push_back(num);
mymap[num]=1;
}
}
}
v=ans;
bfs(n,k,u+1);
}
};
994. 腐烂的橘子
在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一:
值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。
返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1 。
示例 1:
输入:grid = [[2,1,1],[1,1,0],[0,1,1]]
输出:4
先遍历一遍矩阵,找出所有的腐烂橘子的下标并存入队列中,然后根据腐烂橘子的下标开始广度搜索,每次搜索以所有腐烂橘子的下标为中心,把其上下左右四个方向的橘子都腐烂掉,并把新腐烂橘子的下标存入队列中。每完成一次广度搜索,时间times+1。等所有广度搜索结束后,再遍历一遍矩阵,判断里面是否还有新鲜的橘子,如果有,返回-1,如果没有就返回times。
class Solution {
public:
typedef pair<int,int>PII;
int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
int n,m;
int orangesRotting(vector<vector<int>>& grid) {
n=grid.size(),m=grid[0].size();
int times=0;
queue<PII>que;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(grid[i][j]==2)
{
que.push({i,j});
}
while(que.size())
{
int len=que.size();
for(int i=0;i<len;i++)
{
for (int j = 0; j < 4; j++)
{
int a = que.front().first + dx[j], b = que.front().second + dy[j];
if (a >= 0 && a < n && b >= 0 && b < m && grid[a][b] == 1)
{
grid[a][b] = 2;
que.push({ a,b });
}
}
que.pop();
}
if(que.size())times++;
}
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(grid[i][j]==1)return -1;
return times;
}
};