LeetCode第169场周赛(Weekly Contest 169)解题报告

这周的周赛还行,20分钟内做完了前三题,但是最后一题,想着是暴力,但是没有预处理,导致超时了。赛后参考了他人的思路,明白自己的思路没错,只是没经过优化。

周赛排名:129 / 1567,还行,挤进了前 10 %,头一天晚上双周赛掉的分应该涨回来了,哈哈哈。

第一题:题意模拟,构造相反数。

第二题:DFS遍历存储两颗树的所有值,再进行排序。

第三题:DFS或者BFS都可以。

第四题:预处理 + DFS枚举所有可能的排列组合。

详细题解如下。


1. 和为零的N个唯一整数(Find N Unique Integers Sum Up to Zero)

           AC代码(C++)

2. 两棵二叉搜索树中的所有元素(All Elements in Two Binary Search Trees)

           AC代码(C++)

3.跳跃游戏 III(Jump Game III)

           AC代码(方法一   DFS   C++)

           AC代码(方法二   BFS  C++)

4.口算难题(Verbal Arithmetic Puzzle)

           AC代码( C++)


LeetCode第169场周赛地址:

https://leetcode-cn.com/contest/weekly-contest-169/


1. 和为零的N个唯一整数(Find N Unique Integers Sum Up to Zero)

题目链接

https://leetcode-cn.com/problems/find-n-unique-integers-sum-up-to-zero/

题意

给你一个整数 n,请你返回 任意 一个由 n 个 各不相同 的整数组成的数组,并且这 n 个数相加和为 0 。

示例 1:

输入:n = 5
输出:[-7,-1,1,3,4]
解释:这些数组也是正确的 [-5,-1,1,2,3],[-3,-1,2,-2,4]。

示例 2:

输入:n = 3
输出:[-1,0,1]

示例 3:

输入:n = 1
输出:[0]

提示:

  • 1 <= n <= 1000

解题思路

根据题意,这是一道构造题,需要我们构造一个数组,数组里的所有数都各不相同,要求数组里的所有数之和 = 0。

想要和 为0,最简单的一个方法就是,我们生成任意两个数为相反数即可。

当 n 为偶数,那我们就生成 n/2 对不同的相反数。

当 n 为奇数,我们除了生成 n/2 对不同的相反数外,再加上一个 0 即可。

AC代码(C++)

class Solution {
public:
    vector sumZero(int n) {
        vector ans;

        for(int i = - n/2;i < 0;++i)
            ans.push_back(i);

        if(n % 2 == 1)
            ans.push_back(0);
        
        for(int i = 1;i <= n/2 ;++i)
            ans.push_back(i);

        return ans;
    }
};

 


2. 两棵二叉搜索树中的所有元素(All Elements in Two Binary Search Trees)

题目链接

https://leetcode-cn.com/problems/all-elements-in-two-binary-search-trees/

题意

给你 root1 和 root2 这两棵二叉搜索树。

请你返回一个列表,其中包含 两棵树 中的所有整数并按 升序 排序。.

示例 1:

输入:root1 = [2,1,4], root2 = [1,0,3]
输出:[0,1,1,2,3,4]

示例 2:

输入:root1 = [0,-10,10], root2 = [5,1,7,0,2]
输出:[-10,0,0,1,2,5,7,10]

示例 3:

输入:root1 = [], root2 = [5,1,7,0,2]
输出:[0,1,2,5,7]

示例 4:

输入:root1 = [0,-10,10], root2 = []
输出:[-10,0,10]

示例 5:

输入:root1 = [1,null,8], root2 = [8,1]
输出:[1,1,8,8]

提示:

  • 每棵树最多有 5000 个节点。
  • 每个节点的值在 [-10^5, 10^5] 之间。

解题思路

二叉搜索树:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

所以对于某一个二叉搜索树而言,我们应该从左边最下层 往上 到 根节点,再到 右边直到最下。这样子就是升序遍历。

但是由于这道题数据范围不大,相当于最多有 2*5000 个数,那我们可以利用一个数组,DFS遍历树,存放所有出现的数,最后对数组进行排序即可。

AC代码(C++)

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector ans;
    
    void dfs(TreeNode* root)
    {
        if(root == NULL) return;
        
        ans.push_back(root->val);
        dfs(root->left);
        dfs(root->right);
    }
    
    vector getAllElements(TreeNode* root1, TreeNode* root2) {
        
        dfs(root1);
        dfs(root2);
        
        sort(ans.begin(), ans.end());
        return ans;
    }
};

 


3.跳跃游戏 III(Jump Game III)

题目链接

https://leetcode-cn.com/problems/jump-game-iii/

题意

这里有一个非负整数数组 arr,你最开始位于该数组的起始下标 start 处。当你位于下标 i 处时,你可以跳到 i + arr[i] 或者 i - arr[i]。

请你判断自己是否能够跳到对应元素值为 0 的 任意 下标处。

注意,不管是什么情况下,你都无法跳到数组之外。

示例 1:

输入:arr = [4,2,3,0,3,1,2], start = 5
输出:true
解释:
到达值为 0 的下标 3 有以下可能方案: 
下标 5 -> 下标 4 -> 下标 1 -> 下标 3 
下标 5 -> 下标 6 -> 下标 4 -> 下标 1 -> 下标 3 

示例 2:

输入:arr = [4,2,3,0,3,1,2], start = 0
输出:true 
解释:
到达值为 0 的下标 3 有以下可能方案: 
下标 0 -> 下标 4 -> 下标 1 -> 下标 3

示例 3:

输入:arr = [3,0,2,1,2], start = 2
输出:false
解释:无法到达值为 0 的下标 1 处。 

提示:

  • 1 <= arr.length <= 5 * 10^4
  • 0 <= arr[i] < arr.length
  • 0 <= start < arr.length

解题分析

方法一:

看到题目的第一个想法就是利用 DFS + 回溯,遍历所有可能走的路,判断有没有一条路可以到达符合题目的要求。

对于走到某一个下标的时候,DFS 先判断这个下标对应的值符不符合要求,即对应的值是不是 0。

如果不是,那就从这个 位置,可以往两个地方走,i + arr[i] 或者 i - arr[i] ,首先先判断,能不能走到(即没被走过,且没超出边界)

然后再继续判断(如果退回来就回溯)

方法二:

可以使用DFS,那么想着能不能用BFS,BFS用队列,我们可以把值为0对应的下标看成是终点,只要用BFS判断,能入队的所有下标有没有符合的即可。

也就是一维数组的BFS。

AC代码(方法一   DFS   C++)

class Solution {
public:
    
    int vis[50010];
    bool flag;
    vector a;
    int n;
    
    void dfs(int x)
    {
        if(flag == true)
            return;
        if(a[x] == 0)
        {
            flag = true;
            return;
        }
        
        if(x + a[x] < n && vis[x + a[x]]==0)
        {
            vis[x + a[x]] = 1;
            dfs(x + a[x]);
            vis[x + a[x]] = 0;
        }
        
        if(x - a[x]>=0 && vis[x - a[x]]==0)
        {
            vis[x - a[x]] = 1;
            dfs(x - a[x]);
            vis[x - a[x]] = 0;
        }
        
        
    }
    
    bool canReach(vector& arr, int start) {
        n = arr.size();
        a = arr;
        flag = false;
        
        memset(vis, 0, sizeof(vis));
        vis[start] = 1;
        dfs(start);
        
        return flag;
    }
};

AC代码(方法二   BFS  C++)

const int MAXN = 5e4 + 10;

class Solution {
public:
    int vis[MAXN];

    bool canReach(vector& arr, int start) {
        int n = arr.size();
        memset(vis, 0, sizeof(vis));

        queue q;
        q.push(start);
        vis[start] = 1;

        while(!q.empty())
        {
            int cur = q.front();
            q.pop();
            if(arr[cur] == 0)
            {
                return true;
            }

            if(cur-arr[cur] >=0 && !vis[cur-arr[cur]])
            {
                q.push(cur-arr[cur]);
                vis[cur-arr[cur]] = 1;
            }
            if(cur+arr[cur] < n && !vis[cur+arr[cur]])
            {
                q.push(cur+arr[cur]);
                vis[cur+arr[cur]] = 1;
            }
        }
        return false;
    }
};

4.口算难题(Verbal Arithmetic Puzzle)

题目链接

https://leetcode-cn.com/problems/verbal-arithmetic-puzzle/

题意

给你一个方程,左边用 words 表示,右边用 result 表示。

你需要根据以下规则检查方程是否可解:

  • 每个字符都会被解码成一位数字(0 - 9)。
  • 每对不同的字符必须映射到不同的数字。
  • 每个 words[i] 和 result 都会被解码成一个没有前导零的数字。
  • 左侧数字之和(words)等于右侧数字(result)。 

如果方程可解,返回 True,否则返回 False。

示例 1:

输入:words = ["SEND","MORE"], result = "MONEY"
输出:true
解释:映射 'S'-> 9, 'E'->5, 'N'->6, 'D'->7, 'M'->1, 'O'->0, 'R'->8, 'Y'->'2'
所以 "SEND" + "MORE" = "MONEY" ,  9567 + 1085 = 10652

示例 2:

输入:words = ["SIX","SEVEN","SEVEN"], result = "TWENTY"
输出:true
解释:映射 'S'-> 6, 'I'->5, 'X'->0, 'E'->8, 'V'->7, 'N'->2, 'T'->1, 'W'->'3', 'Y'->4
所以 "SIX" + "SEVEN" + "SEVEN" = "TWENTY" ,  650 + 68782 + 68782 = 138214

示例 3:

输入:words = ["THIS","IS","TOO"], result = "FUNNY"
输出:true

示例 4:

输入:words = ["LEET","CODE"], result = "POINT"
输出:false

提示:

  • 2 <= words.length <= 5
  • 1 <= words[i].length, results.length <= 7
  • words[i], result 只含有大写英文字母
  • 表达式中使用的不同字符数最大为 10

 

解题分析

根据题目可以,就是将给的字符串等式,利用字母与 0-9 十个数字一一映射后,使得字符串等式变成了 数学等式。要求找出存不存在一种映射方式使得,数学等式的左右两边相等。

那么一开始的想法就是,枚举所有可能的映射情况(使用DFS),相当于是全排列,最多情况的时候,就是十个字母和十个数字的任意组合,大概为 10! = 3, 628, 800。那么时间复杂度上应该过得去。

枚举了可能的映射情况后,再一一对应去计算此时变成数学等式后,左右两边相不相等。

但是实现之后,还是 超时了,哭泣。。。。

 

赛后看了大佬的解题思路之后,也是利用 DFS 去枚举所有映射情况,但是在计算数学等式的时候,由于进行了一些预处理,所以计算其左右两边的等式的时候,就很快。

预处理:

分析在等式左边 或者 右边,同一个字母对于数学等式的贡献,比如 示例 1

words = ["SEND","MORE"], result = "MONEY"

对于字母 E而言,在等式左边,贡献了 100 + 1,也就是 101,那么当我们得到映射情况后,只需要把对应映射的 数字 * 101即可。同样是字母 E 在等式右边,贡献了 10。

所以预处理,就是得到相同字母对于等式 左边或者右边的贡献,这样子在计算等式两边的值的时候,就很快。

 

根据要求,我们需要有对应的几个数组存放,(用到了空间换时间,预处理的目的)

  • DFS枚举映射情况的 vis
  • 相同字母在等式左右两边的贡献,两个一维数组
  • 由于题目要求,第一个字母不能是 0,所以用一个数组判断对应的某个字母能不能为 0
  • 由于字母是 char,但是上面的 第2 和 3,数组的下标都是数字,我们可以将,等式中,所有不同的字母,先对应成 1 - 不同字母个数,这样子方便下标。(因此题目用到两次映射,一次映射是为了方便处理字母,变为数字来。第二次的映射才是我们答案所要求的)

因此,对于整个程序:

1)首先先初始(对一些数组的初始化)

2)接着,预处理,计算贡献(即权重)

3)然后再利用 DFS 枚举所有映射情况

 

剩下的具体看代代码,加入了注解

AC代码( C++)

class Solution {
public:

    // 用于DFS
    int vis[15];  

    // 第一次映射,将字母变为 数字 1 - 。方便数组的下标
    unordered_map mp;  

    //在上面的映射后,字母相当于是下标,所以某个下标的值,就是字母的对等式的贡献
    int leftWeight[15], rightWeight[15];  

    // 判断某个字母能不能为 0,也是下标对应字母
    int canZero[15];

    bool flag = false;
    
    // 计算贡献的函数
    void calWeight(vector& words, string result)
    {
        int w = 1;
        for(auto word : words)
        {
            w = 1;
            // 贡献是从 1,10,100计算,所以从最后开始
            for(int i = word.size()-1;i >= 0;i--)
            {
                // 此时这个字母对应贡献 += w
                leftWeight[mp[word[i]]] += w;
                w *= 10;
            }
        }
        // 同理,等式右边
        w = 1;
        for(int i = result.size()-1;i >= 0;i--)
        {
            rightWeight[mp[result[i]]] += w;
            w *= 10;
        }

    }

    // DFS
    void dfs(int cur, int n, int sumLeft, int sumRight)
    {
        if(flag == true)
            return;

        // 起点从1开始,n表示总共有几个字母,所以只有当大于 n了,说明,所有字母的都枚举了
        if(cur > n)  
        {
            if(sumLeft == sumRight)
            {
                flag = true;  
            }
            return;
        }

        // 遍历所有情况
        for(int i = 0;i <= 9;++i)
        {
            // 如果对于这个字母,不能放0,那么当为0的时候,不考虑。
            // 同时,因为要一一映射,所以已经用过的数字就不能再用
            if(i==0 && canZero[cur]==0) continue;
            if(vis[i] == 1) continue;

            // 往下 DFS + 回溯
            vis[i] = 1;

            // 向下一个字母判断,此时等式左右两边的值,原本的 + 映射后的数字 * 贡献
            dfs(cur+1, n, sumLeft + i*leftWeight[cur], sumRight + i*rightWeight[cur]);
            vis[i] = 0;
        }
    }

    bool isSolvable(vector& words, string result) {
        
        // init
        memset(vis, 0, sizeof(vis));
        memset(leftWeight, 0, sizeof(leftWeight));
        memset(rightWeight, 0, sizeof(rightWeight));
        for(int i = 0;i < 15;++i) canZero[i] = 1;

        // 第一次 字母映射成 数字
        int numDiffChar = 0;  
        for(auto word : words)
        {
            for(int i = 0;i < word.size();i++)
            {
            // 因为用于判断有没有这个字符,是次数为0,所以存在的字符给的次数要大于0,
            // 不然后面会覆盖,所以给的匹配的数是从 1 匹配的。
                if(mp[word[i]] == 0)  
                {
                    mp[word[i]] = ++numDiffChar;  // 字母先映射为数字
                }
                if(i == 0)
                {
                    canZero[mp[word[i]]] = 0;  // 这个位置不能放
                }
            }
        }
        for(int i = 0;i < result.size();++i)
        {
            if(mp[result[i]] == 0)
            {
                mp[result[i]] = ++numDiffChar;
            }
            if(i == 0)
            {
                canZero[mp[result[i]]] = 0;  // 这个位置不能放
            }
        }

        // 计算权重
        calWeight(words, result);
        
        // 枚举所有排列组合,DFS + 回溯
        dfs(1, numDiffChar, 0, 0);  // 第一次映射的时候,数是从 1开始,所以dfs起点是 1.

        return flag;
    }
};

 

你可能感兴趣的:(LeetCode刷题记录及题解,#,LeetCode比赛,DFS,BFS,二叉搜索树,回溯,空间换时间)