LeetCode 第38次双周赛
本次双周赛题目想要通过还是比较简单的,这里做一个简要的回顾。
No 1.按照频率将数组升序排序
给你一个整数数组 nums ,请你将数组按照每个值的频率 升序 排序。如果有多个值的频率相同,请你按照数值本身将它们 降序 排序。
请你返回排序后的数组。
示例 1:
输入:nums = [1,1,2,2,2,3]
输出:[3,1,1,2,2,2]
解释:‘3’ 频率为 1,‘1’ 频率为 2,‘2’ 频率为 3 。
示例 2:
输入:nums = [2,3,1,3,2]
输出:[1,3,3,2,2]
解释:‘2’ 和 ‘3’ 频率都为 2 ,所以它们之间按照数值本身降序排序。
解析
本题要求我们对输入的数组,按照元素的出现频率升序(出现越少越靠前)同频率按照数值降序(数字越大越靠前)的顺序输出,注意这里的输出使每个数字都要输出,比如实例1有2个1,有3个2,都要输出,而不是排好序返回3,1,2就行了。但显然,相同的数字都是放在一起的,所以我们还是只要知道数字的顺序,以及每个数字的数量即可。
本题最直接的方法,就是先记录每个数字及其出现次数,然后按照“频率小的优先,相同评率数字大的优先”的原则排序,可以借助vector<pair<int,int>>这样的数据结构实现。排序后将每个数字输出,每个数字的输出次数要与数组中真实出现次数一致。
C++代码如下:
//No 1
vector<int> frequencySort(vector<int>& nums) {
unordered_map<int, int> m;
for (auto n : nums)m[n]++;
vector<pair<int, int>> vp;
for (auto mm : m) {
vp.push_back({ mm.second,mm.first });
}
sort(vp.begin(), vp.end(), [](pair<int, int>& p1, pair<int, int>& p2) {return p1.first<p2.first || p1.first == p2.first && p1.second>p2.second; });
vector<int>ans;
for (auto p : vp) {
for(int i=0;i<p.first;++i)
ans.push_back(p.second);
}
return ans;
}
No 2. . 两点之间不包含任何点的最宽垂直面积
给你 n 个二维平面上的点 points ,其中 points[i] = [xi, yi] ,请你返回两点之间内部不包含任何点的 最宽垂直面积 的宽度。
垂直面积 的定义是固定宽度,而 y 轴上无限延伸的一块区域(也就是高度为无穷大)。 最宽垂直面积 为宽度最大的一个垂直面积。
请注意,垂直区域 边上 的点 不在 区域内。
示例 1:
输入:points = [[8,7],[9,9],[7,4],[9,7]]
输出:1
示例 2:
输入:points = [[3,1],[9,0],[1,0],[1,4],[5,3],[8,8]]
输出:3
提示:
n == points.length
2 <= n <= 10^5
points[i].length == 2
0 <= xi, yi <= 109
解析
本题给定一个点集,分别记录这些点的横纵坐标,要求我们找到一个垂直区域,其中不包含任何点(但边界上要有点,否则就不是两点之间了)求这个最大宽度。
本题其实也很简单。首先垂直面积的定义就保证了两条边界的确定其实只跟x坐标有关,y坐标无所谓,如果我们按照x坐标对每个点进行排序,那么所有能取得的垂直面积只能是相邻的点中间区域,如果不相邻就意味着中间包含了其他点,不合题意。而且最左端的点也不能作为右边界向左无限延伸,不符合两点之间的题目要求,最右端点也是同理。
输入横坐标排序复杂度为
O
(
N
l
o
g
N
)
O(NlogN)
O(NlogN)对于本题
1
0
5
10^5
105的数据量完全可行。
代码如下:
//No 2
int maxWidthOfVerticalArea(vector<vector<int>>& points) {
vector<int>xs;
for (auto p : points) {
xs.push_back(p[0]);
}
sort(xs.begin(), xs.end());
int len = xs.size(), maxL = 0;
for (int i = 0; i < len - 1; ++i) {
maxL = max(maxL, xs[i + 1] - xs[i]);
}
return maxL;
}
No 3. 统计只差一个字符的子串数目
给你两个字符串 s 和 t ,请你找出 s 中的非空子串的数目,这些子串满足替换 一个不同字符 以后,是 t 串的子串。换言之,请你找到 s 和 t 串中 恰好 只有一个字符不同的子字符串对的数目。
比方说, “computer” 和 “computation” 加粗部分只有一个字符不同: ‘e’/‘a’ ,所以这一对子字符串会给答案加 1 。
请你返回满足上述条件的不同子字符串对数目。
一个 子字符串 是一个字符串中连续的字符。
示例 1:
输入:s = “aba”, t = “baba”
输出:6
s第一个a与t中的2个b,2
s第二个b与t中2个a,2
s第三个a与t中2个b,2
总计6
提示:
1 <= s.length, t.length <= 100
s 和 t 都只包含小写英文字母。
解析
本题给定我们两个字符串,分别找到两个字符串的非空子串中,只差一个字符的子串对的数目。换言之,找一个s的非空子串,一个t的非空子串,如果二者只差一个字符,就符合要求,那么这样符合要求的有多少对。
本题数据范围,两字符串长度为100,能够暴力求解。
首先我们考虑,给定两个字符串,判断二者是否只差一个字符,要如何判断。实际上也就是首先长度一致,其次从头到尾顺序遍历,记录不一样的字符个数,检查是否恰好一个不同即可。若字符串长度为N,则此处需要时间复杂度 O ( N ) O(N) O(N)
接下来如何找到所有的符合条件的子串对并记录数量呢?首先符合条件的子串长度一定相等,否则不可能之差一个。因此子串的长度取值范围,最小是1,最大是两个输入字符串中较短的长度。
然后我们可以枚举每一个长度,分别讨论长度为1,2,3…,min(length)时,有多少符合条件的子串。当长度一定时,我们只需要两重循环,分别确定s和t两个字符串中的子串起始位置,从而枚举当前长度下的两个子串,检验是否符合并记录即可。
C++代码如下:
//No 3
bool checkStrings(string s, string t) {
if (s.size() != t.size()) return false;
else {
int count = 0,i=0;
while (i < s.size() && count <= 1) {
if (s[i] != t[i])++count;
++i;
}
return count == 1;
}
}
int countSubstrings(string s, string t) {
int lenS = s.size(), lenT = t.size(), len = min(lenS, lenT);
int ans = 0;
for (int i = 1; i <= len; ++i) {
for (int s1 = 0; s1 <= lenS - i; ++s1) {
for (int s2 = 0; s2 <= lenT - i; ++s2) {
string ss = s.substr(s1, i), tt = t.substr(s2, i);
if (checkStrings(ss, tt)) {
++ans;
}
}
}
}
return ans;
}
checkStrings 函数用于检验两个字符串是否只差一个字符,count用于记录同一位置不同字符的个数。跳出循环时,要么count达到2,要么遍历了全部字符串count为0或1,分别对应了不同字符超过1、完全相同、以及只差1,这三种情况。跳出后只要检验count是否为1即可。
countSubstrings中,首先根据两字符串长度确定子串长度取值范围,对每个长度,枚举所有子串对,检验是否满足条件。
整体而言,复杂度为 O ( N 4 ) O(N^4) O(N4),对于长度100能够不超时通过。
No 4. 通过给定词典构造目标字符串的方案数
给你一个字符串列表 words 和一个目标字符串 target 。words 中所有字符串都 长度相同 。
你的目标是使用给定的 words 字符串列表按照下述规则构造 target :
从左到右依次构造 target 的每一个字符。
为了得到 target 第 i 个字符(下标从 0 开始),当 target[i] = words[j][k] 时,你可以使用 words 列表中第 j 个字符串的第 k 个字符。
一旦你使用了 words 中第 j 个字符串的第 k 个字符,你不能再使用 words 字符串列表中任意单词的第 x 个字符(x <= k)。也就是说,所有单词下标小于等于 k 的字符都不能再被使用。
请你重复此过程直到得到目标字符串 target 。
请注意, 在构造目标字符串的过程中,你可以按照上述规定使用 words 列表中 同一个字符串 的 多个字符 。
请你返回使用 words 构造 target 的方案数。由于答案可能会很大,请对 109 + 7 取余 后返回。
(译者注:此题目求的是有多少个不同的 k 序列,详情请见示例。)
示例 1:
输入:words = [“acca”,“bbbb”,“caca”], target = “aba”
输出:6
解释:总共有 6 种方法构造目标串。
“aba” -> 下标为 0 (“acca”),下标为 1 (“bbbb”),下标为 3 (“caca”)
“aba” -> 下标为 0 (“acca”),下标为 2 (“bbbb”),下标为 3 (“caca”)
“aba” -> 下标为 0 (“acca”),下标为 1 (“bbbb”),下标为 3 (“acca”)
“aba” -> 下标为 0 (“acca”),下标为 2 (“bbbb”),下标为 3 (“acca”)
“aba” -> 下标为 1 (“caca”),下标为 2 (“bbbb”),下标为 3 (“acca”)
“aba” -> 下标为 1 (“caca”),下标为 2 (“bbbb”),下标为 3 (“caca”)
提示:
1 <= words.length <= 1000
1 <= words[i].length <= 1000
words 中所有单词长度相同。
1 <= target.length <= 1000
words[i] 和 target 都仅包含小写英文字母。
解析
本题给定一个包含相同长度字符串的字典,以及一个目标字符串target,要求我们从字典中选择字符,从左到右顺序的构造出target,但要求一旦使用了某单词的下标为k的字符,接下来就不能选择下标小于等于k的字符了。
本题相当于在一个二维字符串矩阵中,“走”出一条路径,只能向右,不能回头,不能从上到下直上直下的走,这条路径拼起来相当于target字符串。
但实际上如果我们真的按照路径进行DFS或BFS可能由于分支过多而造成超时。
那么我们通过动态规划能够解决此题呢。
我们讨论使用字典的前i列,构造出字符串前j个的情况,首先,我们完全可以使用前i-1列,跳过当前列不使用;其次,我们如果一定要使用第i列,那首先假设前i-1列构造字符串前j-1个一共有k中方案,接下来我们需要在第i列选择一个字符串的第j和字符,假设有p种选法,那么这种情况两部分可以任意组合,共有
k
∗
p
k*p
k∗p种方法。
同时,我们考虑空串(长度0)的情况,对于m列的字典和l长的目标串,我们定义(m+1)*(l+1)的二维动规数组。
因此,状态转移方程可以这样定义,
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示到字典
i
−
1
i-1
i−1列,构造出目标串前
j
−
1
j-1
j−1个字符的方法数量,
则
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
+
d
p
[
i
−
1
]
[
j
−
1
]
∗
n
u
m
O
f
C
h
a
r
(
t
a
r
g
e
t
[
j
−
1
]
)
i
n
(
w
o
r
d
s
[
a
l
l
]
[
i
−
1
]
)
dp[i][j] = dp[i-1][j] +dp[i-1][j-1]*numOfChar(target[j-1]) in(words[all][i-1])
dp[i][j]=dp[i−1][j]+dp[i−1][j−1]∗numOfChar(target[j−1])in(words[all][i−1])
简言之,到字典
i
−
1
i-1
i−1列,构造出目标串前
j
−
1
j-1
j−1个字符的方法数,就是字典
i
−
2
i-2
i−2列,构造出目标串前
j
−
1
j-1
j−1个字符的方法数(不用第i-1列),再加上到字典
i
−
2
i-2
i−2列,构造出目标串前
j
−
2
j-2
j−2个字符的方法数乘字典
i
−
1
i-1
i−1列有几个target[j-1]字符。
显然如果直接按照上述方法去做,二维动规本身是 O ( N 2 ) O(N^2) O(N2),检索每一列有几个特定的字符,又需要遍历一整列,整体复杂度是 O ( N 3 ) O(N^3) O(N3),对于长度为1000的数据范围肯定会超时。
这里我们选择空间换时间的策略,首线构造二维数组记录每一列包含的每个字符的数量,由于字符只包含小写字母,共26个,故构造26*列数的表就够了,先通过 O ( N 2 ) O(N^2) O(N2)的方法记录每个字符在每一列的出现次数,用到的时候直接查表 O ( 1 ) O(1) O(1)就得到了,这样整体算法复杂度就是 O ( N 2 ) O(N^2) O(N2),不会超时。
注意每次乘或加要取余,动规数组的数据类型可选64位整型避免溢出。
C++代码如下:
//No 4
int numWays(vector<string>& words, string target) {
int64_t n = words.size(), m = words[0].size(),l=target.size(),M=1e9+7;
vector<vector<int64_t>>charMap(26, vector<int64_t>(m, 0));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
charMap[words[i][j]-'a'][j]++;
}
}
vector<vector<int64_t>>dp(m + 1, vector<int64_t>(l + 1, 0));
for (int i = 0; i <= m; ++i) dp[i][0] = 1;
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= l; ++j) {
if (j > i) dp[i][j] = 0;
else {
dp[i][j] = (dp[i-1][j] + (dp[i - 1][j - 1] * charMap[target[j - 1]-'a'][i - 1]) % M) % M;
}
}
}
return dp[m][l];
}

1181

被折叠的 条评论
为什么被折叠?



