公司里共有 n
名员工,按从 0
到 n - 1
编号。每个员工 i
已经在公司工作了 hours[i]
小时。
公司要求每位员工工作 至少 target
小时。
给你一个下标从 0 开始、长度为 n
的非负整数数组 hours
和一个非负整数 target
。
请你用整数表示并返回工作至少 target
小时的员工数。
示例 1:
输入:hours = [0,1,2,3,4], target = 2
输出:3
解释:公司要求每位员工工作至少 2 小时。
- 员工 0 工作 0 小时,不满足要求。
- 员工 1 工作 1 小时,不满足要求。
- 员工 2 工作 2 小时,满足要求。
- 员工 3 工作 3 小时,满足要求。
- 员工 4 工作 4 小时,满足要求。
共有 3 位满足要求的员工。
示例 2:
输入:hours = [5,1,4,2,2], target = 6
输出:0
解释:公司要求每位员工工作至少 6 小时。
共有 0 位满足要求的员工。
提示:
1 <= n == hours.length <= 50
0 <= hours[i], target <= 105
class Solution {
public:
int numberOfEmployeesWhoMetTarget(vector<int>& hours, int target) {
int count = 0;
for(int i=0; i<hours.size(); i++) {
if(hours[i] >= target)
count++;
}
return count;
}
};
给你一个由 正 整数组成的数组 nums
。
如果数组中的某个子数组满足下述条件,则称之为 完全子数组 :
返回数组中 完全子数组 的数目。
子数组 是数组中的一个连续非空序列。
示例 1:
输入:nums = [1,3,1,2,2]
输出:4
解释:完全子数组有:[1,3,1,2]、[1,3,1,2,2]、[3,1,2] 和 [3,1,2,2] 。
示例 2:
输入:nums = [5,5,5,5]
输出:10
解释:数组仅由整数 5 组成,所以任意子数组都满足完全子数组的条件。子数组的总数为 10 。
提示:
1 <= nums.length <= 1000
1 <= nums[i] <= 2000
首先我们需要知道有多少种不同的元素,然后使用滑动窗口的方式,一直扩大右边界,直到窗口内的元素种类与原数组相同,此时缩小左边界,每次缩小左边界,都可以对答案进行累加,因为滑动窗口内的子数组都是满足条件的。
注意,我们为了统计所有满足条件的子数组个数,滑动窗口的left左移需要同时统计map里面元素的个数。
class Solution {
public:
int countCompleteSubarrays(vector<int>& nums) {
int n=nums.size();
unordered_map<int,int>count;
unordered_set<int>uniqueNums;
//先得到所有元素的种类数目
for(int num:nums){
uniqueNums.insert(num);//uset插入元素只能用insert
//count[num]++;count必须在窗口内部更新
}
int result=0;
int left=0;
for(int i=0;i<nums.size();i++){
count[nums[i]]++;//nums[i]累积数目
//元素种类相同,缩小左边界
while(count.size()==uniqueNums.size()){
result+=nums.size()-1-i+1;//包含所有的子数组情况
count[nums[left]]--;
if(count[nums[left]]==0){
count.erase(nums[left]);
}
left++;
}
//右边界i++包含在for循环里面了
}
return result;
}
};
给你三个字符串 a
,b
和 c
, 你的任务是找到长度 最短 的字符串,且这三个字符串都是它的 子字符串 。
如果有多个这样的字符串,请你返回 字典序最小 的一个。
请你返回满足题目要求的字符串。
注意:
a
和 b
,如果在第一个不相同的字符处,a
的字母在字母表中比 b
的字母 靠前 ,那么字符串 a
比字符串 b
字典序小 。示例 1:
输入:a = "abc", b = "bca", c = "aaa"
输出:"aaabca"
解释:字符串 "aaabca" 包含所有三个字符串:a = ans[2...4] ,b = ans[3..5] ,c = ans[0..2] 。结果字符串的长度至少为 6 ,且"aaabca" 是字典序最小的一个。
示例 2:
输入:a = "ab", b = "ba", c = "aba"
输出:"aba"
解释:字符串 "aba" 包含所有三个字符串:a = ans[0..1] ,b = ans[1..2] ,c = ans[0..2] 。由于 c 的长度为 3 ,结果字符串的长度至少为 3 。"aba" 是字典序最小的一个。
提示:
1 <= a.length, b.length, c.length <= 100
a
,b
,c
只包含小写英文字母。本题的思路是枚举abc三个字符串所有的拼接情况,一边枚举一边进行字母的”复用“
我们的目的是找到一个最短的字符串t
,它既包含了a
,也包含了b
。为了得到最短字符串,需要使用尽可能少的b
的字符。
复用这一步的核心思想就是,如果a
和b
有重叠(也就是可以复用)的部分,那么这个重叠部分肯定出现在a
的末尾和b
的开头。因此,对于字符串b,我们可以选择倒着拼接,尝试找到这个b开头和a的重叠部分,以减少最终生成的字符串t
的长度。
如果我们正着拼接,即从b
的开头开始,那么这个重叠部分可能无法有效复用。例如,如果a
=“abc”,b
=“bcd”,那么正着拼接的结果可能是"abc"(不使用b
的任何字符),而实际上我们可以通过使用b
的一个字符得到更短的结果"abcd"。所以,我们选择从b
的末尾开始倒着拼接,检查倒着拼接从不使用b的任何字符,到使用b的所有字符这个范围内,能不能通过复用b开头和a末尾重叠的部分,来使得原字符串包含b。
为什么只能一次性合并两个字符串,而不是一口气把三个字符串全处理完?
这是因为我们需要处理的问题是一个三元问题(有三个字符串需要合并)。当我们一次处理两个字符串时,我们可以将问题简化为两个二元问题,这使得问题更容易处理。一次处理两个字符串,我们可以找到一个字符串,它包含了这两个字符串,然后我们再用这个字符串和第三个字符串进行合并,这样问题就被简化了。
可以在代码中进行进一步的理解。
class Solution {
public:
string minimumString(string a, string b, string c) {
// 创建一个 vector 来存储所有可能的字符串组合
vector<string> combinations;
// 生成所有可能的字符串组合
combinations.push_back(combine(a, b, c));
combinations.push_back(combine(a, c, b));
combinations.push_back(combine(b, a, c));
combinations.push_back(combine(b, c, a));
combinations.push_back(combine(c, a, b));
combinations.push_back(combine(c, b, a));
// 对字符串组合进行排序,首先比较长度,如果长度相同,则按字典序排序
sort(combinations.begin(), combinations.end(), [](string &s1, string &s2) {
if (s1.size() == s2.size()) return s1 < s2;
return s1.size() < s2.size();
});
// 返回最短的字符串组合
return combinations.front();
}
// 生成一个既包含 a 又包含 b 的字符串,然后将这个字符串和 c 组合,生成一个既包含 a, b, c 的字符串
string combine(string a, string b, string c) {
return combine2(combine2(a, b), c);
}
// 生成一个既包含 a 又包含 b 的字符串
string combine2(string a, string b) {
// 从使用 b 的所有字符开始,逐步减少使用的字符数量
for (int i = 0; i <= b.size(); ++i) {
// 拼接 a 和 b 的后缀,生成一个新的字符串
string t = a + b.substr(b.size() - i);
// 如果这个新的字符串包含 b,则它一定也包含 a,所以返回这个字符串
if (t.find(b) != string::npos) return t;
}
// 如果 a 和 b 完全不重叠,则返回 a 和 b 的拼接
return "";
}
};
combine2
函数的作用是生成一个同时包含两个输入字符串a
和b
的最小字符串。combine
函数则是用于生成同时包含a
、b
和c
三个输入字符串的最小字符串。为了实现这个目标,我们需要两次调用combine2
函数,先将a
和b
合并,然后将合并结果与c
进行合并。为了避免代码冗余,我们将combine2
的功能单独作为一个函数实现。
如果将combine1和combine2两个函数合并,也就是一次性处理三个字符串,会导致代码重复。比如,需要写两次类似的循环来分别处理三个字符串的组合,这会使代码变得复杂且难以维护。另一方面,通过将combine2
函数单独实现,我们可以更清晰地表达出代码的逻辑,并使得代码更易于阅读和理解。
比如,如果真的要一次性处理三个字符串,我们只能这么写:
string combine(string a, string b, string c = "") {
// 内部函数,用于合并两个字符串
auto merge = [&](string s1, string s2) {
for (int i = 0; i <= s2.size(); ++i) {
string t = s1 + s2.substr(s2.size() - i);
if (t.find(s2) != string::npos) return t;
}
return "";
};
// 首先合并a和b
string ab = merge(a, b);
// 如果c为空,直接返回ab
if (c == "") return ab;
// 否则,继续合并ab和c
return merge(ab, c);
}
在这个版本中,使用了一个Lambda表达式来处理两个字符串的合并,然后根据 c
是否为空来判断是否需要进一步合并。但是这个版本的代码显然更复杂,因为我们需要在函数内部再定义一个函数,并且引入了一个新的条件判断。所以从代码的清晰性和可维护性角度来说,原始的设计(将 combine2
函数单独分出来)更好。
复用部分的代码:
// 生成一个既包含 a 又包含 b 的字符串
string combine2(string a, string b) {
// 从使用 b 的所有字符开始,逐步减少使用的字符数量
for (int i = 0; i <= b.size(); ++i) {
// 拼接 a 和 b 的后缀,生成一个新的字符串
string t = a + b.substr(b.size() - i);
// 如果这个新的字符串包含 b,则它一定也包含 a,所以返回这个字符串
// 如果 a 和 b 完全不重叠,则返回 a 和 b 的拼接
if (t.find(b) != string::npos) return t;
}
//随意返回一个数值即可
return "";
}
这段复用逻辑,实际上是在判断,从不包含b任何字符,到包含b所有字符这个范围内,b开头的元素能不能和a末尾的元素实现复用。一旦能够复用(在倒着拼接的新字符串t里出现了完整的b),那么就可以直接返回t。
例子:a=“abbc” b=“bb”,那么最开始string t = a + b.substr(b.size() - i);,也就是t=a+b.substr(2),实际上此时b.substr(2)是空的,也就是最开始是在判断a里面是不是本来就包含了b。