第一題:模擬。
第二題:思維。
第三題:模擬。
第四題:動態規劃 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;
}
};