首先总结一下本周赛果:305周是我第一次参加力扣竞赛,怎么说呢做出来了两题,一言难尽不算差也不算好,毕竟第一次做力扣也只刷了300题,306周有所进步进军了第三题,但是第四题并没有写出来甚至连题目也没看多久,只能说下周加油,奥利给!!!
本周周赛最简单的其实还是第二题,话不多说先上题目
给你一个有向图,图中有 n 个节点,节点编号从 0 到 n - 1 ,其中每个节点都 恰有一条 出边。
图由一个下标从 0 开始、长度为 n 的整数数组 edges 表示,其中 edges[i] 表示存在一条从节点 i 到节点 edges[i] 的 有向 边。
节点 i 的 边积分 定义为:所有存在一条指向节点 i 的边的节点的 编号 总和。
返回 边积分 最高的节点。如果多个节点的 边积分 相同,返回编号 最小 的那个。
示例 1:
输入:edges = [1,0,0,0,0,7,7,5]
输出:7
解释:
- 节点 1、2、3 和 4 都有指向节点 0 的边,节点 0 的边积分等于 1 + 2 + 3 + 4 = 10 。
- 节点 0 有一条指向节点 1 的边,节点 1 的边积分等于 0 。
- 节点 7 有一条指向节点 5 的边,节点 5 的边积分等于 7 。
- 节点 5 和 6 都有指向节点 7 的边,节点 7 的边积分等于 5 + 6 = 11 。
节点 7 的边积分最高,所以返回 7 。
详细见力扣:6149. 边积分最高的节点 - 力扣(LeetCode)
这题其实很简单,命名一个数组记录每一个点的分数大小,再命名一个res变量记录值最大的节点就OK了,唯一需要注意的是在C++和java上面会出现栈溢出的问题,因为我对java并不了解所以这里在使用C++是可以用long型变量来解决栈溢出的问题,话不多说上代码:
class Solution {
public:
int edgeScore(vector<int>& edges) {
int size=edges.size();
vector<long long> fen(size);
long long res=0;
int ret=0;
for(int i=0;i<size;++i){
fen[edges[i]]+=i;
}
for(int i=0;i<size;++i){
if(res<fen[i]){
res=fen[i];
ret=i;
}
}
return ret;
}
};
我们在来看第一题:
给你一个大小为 n x n 的整数矩阵 grid 。
生成一个大小为 (n - 2) x (n - 2) 的整数矩阵 maxLocal ,并满足:
maxLocal[i][j] 等于 grid 中以 i + 1 行和 j + 1 列为中心的 3 x 3 矩阵中的 最大值 。
换句话说,我们希望找出 grid 中每个 3 x 3 矩阵中的最大值。
返回生成的矩阵。
示例 1:
输入:grid = [[9,9,8,1],[5,6,2,6],[8,2,6,4],[6,2,2,2]]
输出:[[9,9],[8,6]]
解释:原矩阵和生成的矩阵如上图所示。
注意,生成的矩阵中,每个值都对应 grid 中一个相接的 3 x 3 矩阵的最大值。
相信模拟(下次不用暴搜了,不好听doge)大家很容易都能解决,这里在看了灵神解析后介绍一下原地修改的方法,当然时间复杂度并未优化,但是空间复杂度优化到了O(1)的时间复杂度,其大致思路如下:
首先看图:
红圈是我们第一次暴力搜索所需要搜索的矩阵范围,我们将最大值赋值到res[0][0]这个结果对后续遍历矩阵范围并不会产生影响,即红圈得到的结果对黄圈和蓝圈的遍历结果并未产生影响,跟据这个原理我们可以进行原地修改将每个遍历的最大值都放置在左上角,最后对得到结果对原数组进行剪切pop()方法即可
class Solution {
public:
vector<vector<int>> largestLocal(vector<vector<int>> &grid)
{
int n=grid.size();
int temp;
for(int i=0;i<n-2;++i){
for(int j=0;j<n-2;++j){
temp=grid[i][j];
for(int x=i;x<i+3;++x)
for(int y=j;y<j+3;++y)
temp=max(temp,grid[x][y]);
grid[i][j]=temp;
}
}
for(int i=0;i<n;++i)
if(i<n-2)
for(int j=n-2;j<n;++j)
grid[i].pop_back();
else grid.pop_back();
return grid;
}
};
接下来看第三题
给你下标从 0 开始、长度为 n 的字符串 pattern ,它包含两种字符,'I' 表示 上升 ,'D' 表示 下降 。
你需要构造一个下标从 0 开始长度为 n + 1 的字符串,且它要满足以下条件:
num 包含数字 '1' 到 '9' ,其中每个数字 至多 使用一次。
如果 pattern[i] == 'I' ,那么 num[i] < num[i + 1] 。
如果 pattern[i] == 'D' ,那么 num[i] > num[i + 1] 。
请你返回满足上述条件字典序 最小 的字符串 num。、
示例 1:
输入:pattern = "IIIDIDDD"
输出:"123549876"
解释:
下标 0 ,1 ,2 和 4 处,我们需要使 num[i] < num[i+1] 。
下标 3 ,5 ,6 和 7 处,我们需要使 num[i] > num[i+1] 。
一些可能的 num 的值为 "245639871" ,"135749862" 和 "123849765" 。
"123549876" 是满足条件最小的数字。
注意,"123414321" 不是可行解因为数字 '1' 使用次数超过 1 次。
对应力扣题目:6150. 根据模式串构造最小数字 - 力扣(LeetCode)
本题采用暴力搜索也可通过因为当pattern的大小大于9是会出现1-9中数字的重复此时不满足题目要求可直接返回空字符串,我们会发现一个规律,我们要使得最后满足规则的数最小,运用贪心算法正常情况下只要pattern.size()<9时,只要不出现D我们就默认按照"123456789"的string字符串截取所需要的长度得到结果,此时必定是最小的字符串。如果出现D此时我们就计数D连续出现的次数,根据次数对 "123456789" 对应的位置和位数进行翻转这样保证了即使出现D还能保证一定是最小的结果,这样贪心算法的规则就出来了:
故贪心规则如下:1.初始字符串为 "123456789"
2.出现 I 则跳过
3.出现 D 则计数统计后面有几个连续的D然后翻转最开始的D的位置到D结束的位置对字符串进行翻转,得到最终的记过
上代码:
class Solution {
public:
string smallestNumber(string pattern){
string tempRes="123456789";
int index=0;
int size=pattern.size();
while(index<size){
if(pattern[index]=='I') index++;
else if(pattern[index]=='D'){
int start=index;
while(pattern[index]=='D') index++;
reverse(tempRes.begin()+start,tempRes.begin()+index+1);
}
}
return tempRes.substr(0,size+1);
}
};
第四题明天在总结,第四题为数位dp题目此种类型的题目有较为系统化的模板。这两天写完。
补充第四题
如果一个正整数每一个数位都是 互不相同 的,我们称它是 特殊整数 。
给你一个 正 整数 n ,请你返回区间 [1, n] 之间特殊整数的数目。
示例 1:
输入:n = 20
输出:19
解释:1 到 20 之间所有整数除了 11 以外都是特殊整数。所以总共有 19 个特殊整数。
对应力扣题号链接:2376. 统计特殊整数 - 力扣(LeetCode)
以123为例子其大致思路是:首先我们将函数中的isLimited置true表示此时收到123的限制,而i初始为0所以此时第一位收到限制<=1,再来看isNum其表示前面的数位是否有有效数字,那么什么是有效数字呢如果一个数字是这样的012此时我们需要的是12,而这里0就不是有效数字,而102中的0是有效数字。
再来介绍我们函数的各个规则:
1.选择递归函数来实现对每个位置的判断如果符合规则则计数
2.对应规则:如果i==m返回计数
3.如果当前当前数位有效并且未收到约束并且dp[i][mask]被记录res,那么返回res计数到最终的res中
4.如果此时数位为无效,我们继续遍历下一位
5.也是最关键的对数位相应的数字进行筛选查看其是否满足条件,具体情况已经在for循环代码部分分析。
class Solution {
public:
int countSpecialNumbers(int n){
string s=to_string(n);
int m=s.length();
vector<vector<int>> dp(m,vector<int>(1<<10,-1));
//i--当前位数
//mask--表示前面选过的数字集合,换句话说,第i为要选的数字不能在mask中
//isLimited--表示当前是否收到了n的约束,若为真,此时第i位最大为s[i],否则为9,如果收到了约束的情况下填入了s[i]那么后续的填入数字仍然收到约束
//isNum--表示当前数位的数字是否有效,若真则有效,若假为无效
function<int(int,int,bool,bool)> f=[&](int i,int mask,bool isLimited,bool isNum)->int{
//到达最后一位返回当前遍历的结果数
if(i==m) return isNum;
//当前数位有效并且未收到约束并且dp[i][mask]被记录res
if(!isLimited&&isNum&&dp[i][mask]>=0) return dp[i][mask];
int res=0;
//如果此时数位为无效,0无效
if(!isNum) res=f(i+1,mask,false,false);
//d=i-isNum--表示当前数位的数字,若isNum为真即没跳过则d从0开始遍历,若为假则可以从1开始(跳过一次)
//上面的过程已经充分分析了0开头的大多数情况
for(int d=1-isNum,up=isLimited?s[i]-'0':9;d<=up;++d){
//d不在mask中
if((mask>>d&1)==0) res+=f(i+1,mask|(1<<d),isLimited&&d==up,true);
}
//如果未被限制同时有效 记录dp[i][mask]为res
if(!isLimited&&isNum) dp[i][mask]=res;
return res;
};
return f(0,0,true,false);
}
};