第一题:模拟。
第二题:思维。
第三题:模拟。
第四题:动态规划 DP。
详细题解如下。
1.检查整数及其两倍数是否存在(Check If N And Its Double Exist)
2. 制造字母异位词的最小步骤数(Minimum Number of Steps to Make Two Strings Anagram)
3.推文计数(Tweet Counts Per Frequency)
4.参加考试的最大学生数(Maximum Students Taking Exam)
LeetCode第175场周赛地址:
https://leetcode-cn.com/contest/weekly-contest-175/
1.检查整数及其两倍数是否存在(Check If N And Its Double Exist)
题目链接
https://leetcode-cn.com/problems/check-if-n-and-its-double-exist/
题意
给你一个整数数组 arr,请你检查是否存在两个整数 N 和 M,满足 N 是 M 的两倍(即,N = 2 * M)。
更正式地,检查是否存在两个下标 i 和 j 满足:
- i != j
- 0 <= i, j < arr.length
- arr[i] == 2 * arr[j]
示例 1:
输入:arr = [10,2,5,3] 输出:true 解释:N = 10 是 M = 5 的两倍,即 10 = 2 * 5 。
示例 2:
输入:arr = [7,1,14,11] 输出:true 解释:N = 14 是 M = 7 的两倍,即 14 = 2 * 7 。
提示:
2 <= arr.length <= 500
-10^3 <= arr[i] <= 10^3
解题思路
根据数据范围,直接暴力枚举任意两个数,判断是不是两倍关系即可。
时间复杂度 O(N ^ 2),不会超时
AC代码(C++)
class Solution {
public:
bool checkIfExist(vector<int>& arr) {
int n = arr.size();
for(int i = 0;i < n;++i)
{
for(int j = 0;j < n;++j)
{
if(i == j) continue;
if(arr[i] == 2 * arr[j])
return true;
}
}
return false;
}
};
2. 制造字母异位词的最小步骤数(Minimum Number of Steps to Make Two Strings Anagram)
题目链接
https://leetcode-cn.com/problems/minimum-number-of-steps-to-make-two-strings-anagram/
题意
给你两个长度相等的字符串 s 和 t。每一个步骤中,你可以选择将 t 中的 任一字符 替换为 另一个字符。
返回使 t 成为 s 的字母异位词的最小步骤数。
字母异位词 指字母相同,但排列不同的字符串。
示例 1:
输出:s = "bab", t = "aba" 输出:1 提示:用 'b' 替换 t 中的第一个 'a',t = "bba" 是 s 的一个字母异位词。
示例 2:
输出:s = "leetcode", t = "practice" 输出:5 提示:用合适的字符替换 t 中的 'p', 'r', 'a', 'i' 和 'c',使 t 变成 s 的字母异位词。
示例 3:
输出:s = "anagram", t = "mangaar" 输出:0 提示:"anagram" 和 "mangaar" 本身就是一组字母异位词。
提示:
1 <= s.length <= 50000
s.length == t.length
s
和t
只包含小写英文字母
解题思路
根据题目意思,我们先分别统计两个字符串的字母情况
当 字符串 s 中,某个字母出现次数 不为 0 时,说明字符串 s 中有这个字母(假设当前字母在 s 出现次数为a,在 t 中出现次数为 b)
1)那么 当 a == b,说明这个字母不用变化
2)当 a > b,说明 s 中这个字母出现的多,那么 t 中就需要其他字母变动过来 (所以就有替换次数)
3) 当 a < b,说明 s 中这个字母次数不需要那么多(也就是 t 中多出现的次数,是后面要变成其他字母的,而变成什么字母,由第 2 步出现的情况来确定),所以对于这个字母而言,t 不需要变化,因为够了(所以没有替换次数)
因此,计算次数只有当 a >= b才计算,此时对于这个字母的操作次数 = a - b。对每个字母都判断,累加,最后就是答案。
AC代码(C++)
class Solution {
public:
int minSteps(string s, string t) {
int a[26], b[26];
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
int n = s.size();
for(int i = 0;i < n;++i)
{
a[s[i] - 'a']++;
b[t[i] - 'a']++;
}
int ans = 0;
for(int i = 0;i < 26;++i)
{
if(a[i] >= b[i])
ans += (a[i] - b[i]);
}
return ans;
}
};
3.推文计数(Tweet Counts Per Frequency)
题目链接
https://leetcode-cn.com/problems/tweet-counts-per-frequency/
题意
具体题目看链接
解题分析
这道题,我们就根据题目进行模拟即可,设一个可以存,string(名字),time(发表推特的时候),还有num(对应这个人,在这个时间发的数量)
所以我们可以用 unordered_map<string, map<int, int> >,两个map的套用,第一个map用unordered_map,是因为对于人名而言,不需要排序,但是第二个用map,是因为我们要按时间统计,所以用map可以自动对 time (key)进行排序。
题目过程
1)如果是人发推特,那我们就记录下, 这个人,在对应的时间,推特数 + 1
2)如果是要求我们统计,那我们先根据fre,计算出周期长度(分- 60,时 - 60*60,天 - 24*60*60),然后根据起始时间和终点时间,分成若干区域,对这个区域里进行统计。
这里我们找到一个区间的[s, e],开始时间和结束时间,那么我们要对应找到 map<time , num>中的位置,由于 map 自动对 time 排序了,我们可以用map 的lower_bound和upper_bound 函数 :
map::lower_bound(key):返回map中第一个大于或等于key的迭代器指针
map::upper_bound(key):返回map中第一个大于key的迭代器指针
所以我们统计一个区间的[s, e]中的迭代器的范围 beg - end,然后统计这个迭代器中的第二个元素(发送推特数量)之和,就是在这个时间区间内,总的发推特数之和。
最后每个区间的统计结果存放到一个数组中,最后返回即可。
AC代码(C++)
class TweetCounts {
unordered_map<string, map<int, int> > recd;
public:
TweetCounts() {
recd.clear(); // 先清空
}
void recordTweet(string tweetName, int time) {
// 对应数量 + 1
if(recd[tweetName][time] == 0)
recd[tweetName][time] = 1;
else
++recd[tweetName][time];
}
vector<int> getTweetCountsPerFrequency(string freq, string tweetName, int startTime, int endTime) {
int gap = 0;
if (freq == "minute") gap = 60;
else if (freq == "hour") gap = 60 * 60;
else if (freq == "day") gap = 24 * 60 *60;
if(recd[tweetName].empty()) return {};
vector<int> ans;
// 在总的时间内,可以分为几个区间
while(startTime <= endTime){
int cnt = 0;
// 每一个区间 [startTime, min(startTime + gap - 1, endTime)]
auto beg = recd[tweetName].lower_bound(startTime);
auto end = recd[tweetName].upper_bound( min(startTime + gap - 1, endTime) );
// 统计 map 的第二个元素之和(也就是存放的推文数量)
for(auto it = beg; it != end; ++it)
{
cnt += it->second;
}
ans.push_back(cnt);
// 区间的起点,变化
startTime += gap;
}
return ans;
}
};
/**
* Your TweetCounts object will be instantiated and called as such:
* TweetCounts* obj = new TweetCounts();
* obj->recordTweet(tweetName,time);
* vector<int> param_2 = obj->getTweetCountsPerFrequency(freq,tweetName,startTime,endTime);
*/
4.参加考试的最大学生数(Maximum Students Taking Exam)
题目链接
https://leetcode-cn.com/problems/maximum-students-taking-exam/
题意
给你一个 m * n 的矩阵 seats 表示教室中的座位分布。如果座位是坏的(不可用),就用 '#' 表示;否则,用 '.' 表示。
学生可以看到左侧、右侧、左上、右上这四个方向上紧邻他的学生的答卷,但是看不到直接坐在他前面或者后面的学生的答卷。请你计算并返回该考场可以容纳的一起参加考试且无法作弊的最大学生人数。
学生必须坐在状况良好的座位上。
示例 1:
示例有图,具体看链接 输入:seats = [["#",".","#","#",".","#"], [".","#","#","#","#","."], ["#",".","#","#",".","#"]] 输出:4 解释:教师可以让 4 个学生坐在可用的座位上,这样他们就无法在考试中作弊。
示例 2:
输入:seats = [[".","#"], ["#","#"], ["#","."], ["#","#"], [".","#"]] 输出:3 解释:让所有学生坐在可用的座位上。
示例 3:
输入:arr = [7,6,5,4,3,2,1], d = 1 输出:7 解释:从下标 0 处开始,你可以按照数值从大到小,访问所有的下标。
示例 4:
输入:arr = [7,1,7,1,7,1], d = 2 输出:2
提示:
seats 只包含字符 '.' 和'#'
m == seats.length
n == seats[i].length
1 <= m <= 8
1 <= n <= 8
解题分析
一开始的想法是,利用 暴力对 每个点进行 dfs,那么这个的时间复杂度(因为要对每一个点判断,每一个点都可放和不放两种情况)为 O(2 ^ (n * m)) = O(2 ^ 64) 会超时。
然后我们发现,每一行只有最多 8 个位置,那么一共由 2^ 8 种 可能,我们用 1 表示这个位置坐下学生,0 表示这个位置不坐学生
同时,每一行的状态,只与上一行存放的状态有关,所以是 考虑使用动态规划 DP。
动态规划三步走 :
1) 设变量, dp[ i ][ cur ] 表示,第 i 行的安排学生状态为 cur是,前 i 行可以坐下的学生最大值
2)状态转移,如果对于这个状态 cur (要判断是否满足条件),也就是枚举上一行的所有可能转移到 cur 的状态 pre,如果都满足条件,那么就是 + 当前行 cur 的 坐下的学生数(也就是 1 的 个数)
3)初始化,因为有一些状态不符合条件,我们初始化为 -1 ( - 1 表示这个状态不符合条件),dp[ 0 ][ 0 ] ,(我们安排学生的是从第一行开始),所以前面没安排学生,那么状态肯定是 0,学生数 = 0;(dp[ 0 ][ 其他状态 ] = -1,没安排学生,也就是不会由其他状态存在,所以这些状态都是不符合要求的 )
所以整个程序的过程
1)枚举所有行,所有当前状态,所有上一行的状态
2)发现上一行的状态 = -1,那就是不可能从上一行转移过来,那么这个上一行状态可以不考虑,跳过
3)对于当前状态 满不满足要求(也就是,在有学生的位置,这个位置不能是座位,也不能这一行的左右两边是学生,也不能是上一行的左右两边是学生)
4)如果这个状态满足要求,那就可以状态转移了
5)最后的答案是 dp[ n ][ 所有状态 ] 中的最大值(因为不知道哪种状态时最大值,所有从所有状态中选)
时间复杂度 O(n * 2^m * 2^m * m),第一个是所有行,第二个是当前状态,第三个是上一行状态,第四个是对于这一行的状态,位运算判断这个位满不满足条件
AC代码(C++)
class Solution {
public:
int maxStudents(vector<vector<char>>& seats) {
int n = seats.size(), m = seats[0].size();
int lim = (1 << m);
// -1 表示这个状态实现不了
vector<vector<int> > dp(n + 1, vector<int>(lim, -1));
// 0 行只有 0状态 = 0 (其他状态实现不了,错误的 = -1)
dp[0][0] = 0;
for(int i = 1;i <= n; ++i)
{
for(int cur = 0; cur < lim; ++cur)
{
for(int pre = 0; pre < lim; ++pre)
{
// 表示上一行状态是无效的,不会转移过来
if(dp[i - 1][pre] == -1) continue;
// 判断当前状态 cur 满不满足要求,同时计算cur 中 1 的个数,也就是学生数量
bool flag = true;
int cnt = 0;
for(int j = 0;j < m; ++j)
{
// 如果这个是0 ,不安排学生,那就满足条件
if(((cur >> j) & 1) == 0) continue;
// 1 的个数
++cnt;
// 这个 位置是 1,同时,是座位,不满足条件
if(seats[i - 1][j] == '#'){
flag = false;
break;
}
// 当前行,这个位置坐了学生,左右两边应该不能是学生,如果是,不满足条件
if(j + 1 < m && (((cur >> (j + 1)) & 1) == 1)){
flag = false;
break;
}
if(j - 1 >= 0 && (((cur >> (j - 1)) & 1) == 1)){
flag = false;
break;
}
// 当前行这个位置是学生,那么上一行的左右两边不能是学生,是的话,这个状态不满足条件
if(j + 1 < m && (((pre >> (j + 1)) & 1) == 1)){
flag = false;
break;
}
if(j - 1 >= 0 && (((pre >> (j - 1)) & 1) == 1)){
flag = false;
break;
}
}
// 满足条件才状态转移
if(flag)
dp[i][cur] = max(dp[i][cur], dp[i - 1][pre] + cnt);
}
}
}
int ans = 0;
for(int cur = 0; cur < lim; ++cur)
{
ans = max(ans, dp[n][cur]);
}
return ans;
}
};