2022-01-26每日刷题打卡

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:

narytreeexample.png (781×502) (leetcode.com)

输入: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:

all_1.jpg (242×242) (leetcode.com)

输入: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:

picture1.png (1068×305) (s3-lc-upload.s3.amazonaws.com)

输入: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:

sketch1.png (722×614) (s3-lc-upload.s3.amazonaws.com)

输入: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:

complete-binary-tree-1.png (180×145) (leetcode-cn.com)

输入: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:

oranges.png (1306×275) (leetcode-cn.com)

输入: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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值