回家傻一天。在家做的第一场周赛,早上起来懵逼了一上午,最后惨不忍睹。晚上在写题解的时候,重新看题,思路清晰,反应快速。然而....,哇,掉分了。
第一题:模拟。
第二题:模拟。
第三题:树的操作。
第四题:贪心 或者 动态规划 DP。
详细题解如下。
1.6 和 9 组成的最大数字(Maximum 69 Number)
AC代码(C++)
2. 竖直打印单词(Print Words Vertically)
AC代码(C++)
3.删除给定值的叶子节点(Delete Leaves With A Given Value)
AC代码(C++)
4.灌溉花园的最少水龙头数目(Minimum Number of Taps to Open to Water A Garden)
AC代码(方法一 贪心 C++)
AC代码(方法二 DP C++)
LeetCode第172场周赛地址:
https://leetcode-cn.com/contest/weekly-contest-172/
https://leetcode-cn.com/problems/maximum-69-number/
给你一个仅由数字 6 和 9 组成的正整数
num
。你最多只能翻转一位数字,将 6 变成 9,或者把 9 变成 6 。
请返回你可以得到的最大数字。
示例 1:
输入:num = 9669 输出:9969 解释: 改变第一位数字可以得到 6669 。 改变第二位数字可以得到 9969 。 改变第三位数字可以得到 9699 。 改变第四位数字可以得到 9666 。 其中最大的数字是 9969 。
示例 2:
输入:num = 9996 输出:9999 解释:将最后一位从 6 变到 9,其结果 9999 是最大的数。
示例 3:
输入:num = 9999 输出:9999 解释:无需改变就已经是最大的数字了。
提示:
1 <= num <= 10^4
num
每一位上的数字都是 6 或者 9 。
根据题意和数据范围,num的最大输入可能是 9999。
根据题意进行,我们从高位往下遍历,当判断出第一次出现某一位是 6时,就将 6 替换成 9,即可。
因此我们要模拟,从高位往低位,那么就是从 1000,100,10,最后到 1。
当我们出现这一位时 6 时,变为 9,相当于原来的 num,再加上, 3 * 对应位数(1000,或100,或10,或1)。
class Solution {
public:
int maximum69Number (int num) {
int cur = num;
for(int p = 1000; p >= 1; p /= 10) // 从高位往低位
{
if(cur / p == 6) // 出现是 6,
{
num += 3 * p; // 变成 9 相当于是,加上 3* 对应位数的乘积
break;
}
cur %= p;
}
return num;
}
};
https://leetcode-cn.com/problems/print-words-vertically/
给你一个字符串 s。请你按照单词在 s 中的出现顺序将它们全部竖直返回。
单词应该以字符串列表的形式返回,必要时用空格补位,但输出尾部的空格需要删除(不允许尾随空格)。
每个单词只能放在一列上,每一列中也只能有一个单词。示例 1:
输入:s = "HOW ARE YOU" 输出:["HAY","ORO","WEU"] 解释:每个单词都应该竖直打印。 "HAY" "ORO" "WEU"
示例 2:
输入:s = "TO BE OR NOT TO BE" 输出:["TBONTB","OEROOE"," T"] 解释:题目允许使用空格补位,但不允许输出末尾出现空格。 "TBONTB" "OEROOE" " T"
示例 3:
输入:s = "CONTEST IS COMING" 输出:["CIC","OSO","N M","T I","E N","S G","T"]
提示:
1 <= s.length <= 200
s
仅含大写英文字母。- 题目数据保证两个单词之间只有一个空格。
根据题目的意思,将句子中的各个单词分出来,然后将单词竖着放,然后每个单词的同一行上的字符,组成新的一个字符串。然后同一行的单词长度不够,就补 空格,但是如果是 空格 在末尾,就不用补。
比如,对于示例2,具体的分析如下
所以这道题,不难,主要是理解题意,然后按照题目意思进行模拟即可。
因此,主要分为以下几步:
1)先将输入的字符串句子,拆分成,各个单词。
2)求出各个单词的最大长度maxLen。根据上面示例2的分析,最后的字符串数组答案,有几个字符串,取决于,各个单词的最大长度。
3)接着,我们遍历同一行,也就是 index 从 0 到 maxLen - 1,那么多行。
3.1)首先,分析,补空格是怎么来,就是当我们分析到某一行的时候,对于某个单词而言,已经超出它本身的长度,也就是已经没有字符了,那么就对这个单词而言,我们要取出这一行的字符,那就是给它补空格
3.2)对于末尾不用补空格。我们为了判断这个空格到底最后成不成为输出字符串的一部分。我们从最后一个单词到第一个单词的 index 来取,只要没有出现过是有效字符,那么出现的空格,那就是末尾空格,不用加上。但如果已经出现过有效字符,比如示例2中,第三个输出字符串,出现过 T了,那么前面再出现空格,也是要加上去的,因为要组成新的字符串,那么前面要补空格。
所以只要按照上面的分析,模拟就可以了
class Solution {
public:
vector printVertically(string s) {
// 拆分成各个单词
vector words;
string str = "";
for(int i = 0;i < s.size(); ++i)
{
if(s[i] == ' ')
{
words.push_back(str);
str = "";
}
else
{
str += s[i];
}
}
words.push_back(str);
// 找出最大长度
int maxLen = 0;
for(auto w : words)
{
maxLen = max(maxLen, (int)w.size());
}
vector ans;
int index = 0;
int n = words.size();
// 遍历所有行, 0 到 maxLen - 1
while(index < maxLen)
{
str = "";
bool sp = false;
for(int i = n - 1; i >= 0; --i)
{
if(index >= (int)words[i].size()) // 出现空格,要判断这个空格加不加
{
if(sp == true) // 这个用于标记,出现这个空格前,是不是已经出现过有效字符了
{
str = " " + str;
}
}
else // 出现有效字符,那就是组成新字符串的一部分,同时,表示,再出现空格,也要加进新字符串中
{
str = words[i][index] + str;
sp = true;
}
}
index++;
ans.push_back(str);
}
return ans;
}
};
https://leetcode-cn.com/problems/delete-leaves-with-a-given-value/
给你一棵以 root 为根的二叉树和一个整数 target ,请你删除所有值为 target 的 叶子节点 。
注意,一旦删除值为 target 的叶子节点,它的父节点就可能变成叶子节点;如果新叶子节点的值恰好也是 target ,那么这个节点也应该被删除。
也就是说,你需要重复此过程直到不能继续删除。
示例 1:
示例有图,具体看链接 输入:root = [1,2,3,2,null,2,4], target = 2 输出:[1,null,3,null,4] 解释: 上面左边的图中,绿色节点为叶子节点,且它们的值与 target 相同(同为 2 ),它们会被删除,得到中间的图。 有一个新的节点变成了叶子节点且它的值与 target 相同,所以将再次进行删除,从而得到最右边的图。
示例 2:
示例有图,具体看链接 输入:root = [1,3,3,3,2], target = 3 输出:[1,3,null,null,2]
提示:
1 <= target <= 1000
- 每一棵树最多有
3000
个节点。- 每一个节点值的范围是
[1, 1000]
。
二叉树的操作,那么考虑用dfs(也就是递归)。
对于一个节点而言,我们要判断这个节点是不是符合这个要求,本来是先判断这个节点是不是叶节点(也就是左右节点都是NULL),但是因为这个节点即使不是叶节点,但是可能由于叶节点满足要求被删除了,那么此时父节点就变成了叶节点。
因此,在判断这个节点的时候,我们先要对其左右节点进行 dfs,然后再判断这个节点满步满足要求。
由于二叉树的操作是删除之类的,那么dfs的要有返回值,返回值也是二叉树的某个节点值。
dfs其实就是递归,相当于我们要分析子问题间的关系。那么对于这道题其实就是
1)我们要判断一个节点,满不满足要求,就是这个节点是叶节点,同时 val == target
2)但是即使这个节点是父节点,但是可能它的子节点都被删除了,就变成了叶节点。所以再进行操作 1)之前,我们先要对这个节点的左右节点进行 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:
TreeNode* dfs(TreeNode* root, int target)
{
if(root == NULL) return NULL;
root->left = dfs(root->left, target);
root->right = dfs(root->right, target);
if(root->left==NULL && root->right==NULL && root->val==target) root = NULL;
return root;
}
TreeNode* removeLeafNodes(TreeNode* root, int target) {
return dfs(root, target);
}
};
https://leetcode-cn.com/problems/minimum-number-of-taps-to-open-to-water-a-garden/
在 x 轴上有一个一维的花园。花园长度为 n,从点 0 开始,到点 n 结束。
花园里总共有 n + 1 个水龙头,分别位于 [0, 1, ..., n] 。
给你一个整数 n 和一个长度为 n + 1 的整数数组 ranges ,其中 ranges[i] (下标从 0 开始)表示:如果打开点 i 处的水龙头,可以灌溉的区域为 [i - ranges[i], i + ranges[i]] 。
请你返回可以灌溉整个花园的 最少水龙头数目 。如果花园始终存在无法灌溉到的地方,请你返回 -1 。
示例 1:
输入:n = 5, ranges = [3,4,1,1,0,0] 输出:1 解释: 点 0 处的水龙头可以灌溉区间 [-3,3] 点 1 处的水龙头可以灌溉区间 [-3,5] 点 2 处的水龙头可以灌溉区间 [1,3] 点 3 处的水龙头可以灌溉区间 [2,4] 点 4 处的水龙头可以灌溉区间 [4,4] 点 5 处的水龙头可以灌溉区间 [5,5] 只需要打开点 1 处的水龙头即可灌溉整个花园 [0,5] 。
示例 2:
输入:n = 3, ranges = [0,0,0,0] 输出:-1 解释:即使打开所有水龙头,你也无法灌溉整个花园。
示例 3:
输入:n = 7, ranges = [1,2,1,0,2,1,0,1] 输出:3
示例 4:
输入:n = 8, ranges = [4,0,0,0,0,0,0,0,4] 输出:2
提示:
1 <= n <= 10^4
ranges.length == n + 1
0 <= ranges[i] <= 100
方法一:贪心
对于喷洒的范围,我们利用贪心的方法,
首先,类似示例 1 的操作,我们先把各个水龙头的喷洒区间(左边最小值应该为 0,右边最大值最多为 n)。
然后,我们找到已经喷洒的边界为 pos,一开始的pos = 0。我们本来贪心的思想是,遍历所有区间,找到区间的左边点,是 <= pos,也就是至少这个范围的起始点已经可以可以覆盖之前已经覆盖的,那么新覆盖的范围,我们要找到最大的,那么就找这些满足的区间的右边点的最大值。这样子使得新覆盖的范围最大,那就是需要最少的水龙头,然后新的边界 pos更新。然后继续。直到所有的区间都遍历完了。(这个地方,为了一次遍历完,我们可以将区间进行升序排序,保证了所有区间的左边点是升序的,那么比较的时候,刚好就区间从前往后,一次遍历完)。
最后就判断,pos==n,如果是,那就输出,水龙头个数。否则输出-1。
方法二:动态规划DP
看到题目,最少数目,那么想到,能不能使用DP
三步走
1)设变量,dp[ i ] 表示,前面已经喷洒的范围,到 i 的需要最少的数目
2)状态转移。对于每一个区间,这个范围的 dp[ 区间最左边 ] = min(本身,dp[区间内的点] + 1),因为对于一个区间而言,区间最左边的,就是新的覆盖的范围,那么他可以是从 区间中的任意一个点,转移过来的。所以取的是最小值。
3)起始条件,因为是最小,所以所有的 dp初始值都设为INF。然后 dp[ 0 ] = 0,起点,这个时候,还没开始覆盖,所以需要 0 个。
最后输出判断,输出 dp[ n ],如果还是INF,说明无法覆盖完全部,输出 -1。否则,直接输出 dp[ n ]。
class Solution {
public:
int minTaps(int n, vector& ranges) {
vector > r(n + 1, vector (2, 0));
// 区间排序
for(int i = 0;i < n + 1; ++i)
{
r[i][0] = max(i - ranges[i], 0);
r[i][1] = min(i + ranges[i], n);
}
sort(r.begin(), r.end());
if(r[0][0] != 0) return -1;
int ans = 0;
int pos = 0;
// 区间从头到尾遍历
for(int i = 0;i < n + 1; ++i)
{
int temp = pos;
// 找出所有满足条件的区间的左边点的最大值,也就是当前可以覆盖的最大范围,从而需要最少数目
while(i < n + 1 && r[i][0] <= pos)
{
temp = max(temp, r[i][1]);
++i;
}
if(temp > pos)
{
--i;
pos = temp;
ans++;
}
}
if(pos == n) return ans;
else return -1;
}
};
#define INF 0x3f3f3f3f
#define MAXN (int)(1e4 + 50)
class Solution {
public:
int dp[MAXN];
int minTaps(int n, vector& ranges) {
// 初始化
for(int i = 0;i <= n;i++)
dp[i] = INF;
dp[0] = 0;
for(int i = 0;i < n + 1; ++i)
{
int lf = max(i - ranges[i], 0);
int rt = min(i + ranges[i], n);
// 区间中所有的状态转移
for(int j = lf; j < rt; ++j)
{
dp[rt] = min(dp[rt], dp[j] + 1);
}
}
if(dp[n] == INF)
return -1;
else
return dp[n];
}
};