这周的周赛还行,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/
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 即可。
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;
}
};
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遍历树,存放所有出现的数,最后对数组进行排序即可。
/**
* 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;
}
};
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。
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;
}
};
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;
}
};
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。
所以预处理,就是得到相同字母对于等式 左边或者右边的贡献,这样子在计算等式两边的值的时候,就很快。
根据要求,我们需要有对应的几个数组存放,(用到了空间换时间,预处理的目的)
因此,对于整个程序:
1)首先先初始(对一些数组的初始化)
2)接着,预处理,计算贡献(即权重)
3)然后再利用 DFS 枚举所有映射情况
剩下的具体看代代码,加入了注解
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;
}
};