# Weekly Contest 400
日期 2024/06/02
LeetCode 週賽網站變得不穩定,介面跳了兩次。
這次做了前三題題目,最後一題沒想出來,根據討論區的解法,比想像中簡單很多。
該[解題達人](https://leetcode.com/u/votrubac/)在 LinkedIn 有創立[解題與面試社團](https://www.linkedin.com/groups/13938406/),順手就加入該社團。
- [Minimum Number of Chairs in a Waiting Room](https://leetcode.com/problems/minimum-number-of-chairs-in-a-waiting-room/)
- [Count Days Without Meetings](https://leetcode.com/problems/count-days-without-meetings/)
- [Lexicographically Minimum String After Removing Stars](https://leetcode.com/problems/lexicographically-minimum-string-after-removing-stars/)
- [Find Subarray With Bitwise AND Closest to K](https://leetcode.com/problems/find-subarray-with-bitwise-and-closest-to-k/)
### 第一題
```clike
int minimumChairs(string s) {
const int n = s.size();
int ret = 0;
int cur = 0;
for(char x: s) {
cur += x == 'E';
cur -= x == 'L';
ret = max(ret, cur);
}
return ret;
}
```
### 第二題
這題是 Blind 75 中 Interval 題型。
```clike
int countDays(int days, vector<vector<int>>& meetings) {
const int n = meetings.size();
sort(meetings.begin(), meetings.end());
/*
[[5,7],[1,3],[9,10]] --> [1, 3], [5, 7], [9, 10];
*/
vector<vector<int>> mm;
mm.push_back(meetings[0]);
for(int i = 1; i < n; i++) {
vector<int> &b = mm.back();
if(b[1] >= meetings[i][0]) {
b[1] = max(b[1], meetings[i][1]);
}else{
mm.push_back(meetings[i]);
}
}
int sum = 0;
for(auto &x: mm) {
sum += x[1] - x[0] + 1;
}
return days - sum;
}
```
### 第三題
剛開始我是想用 stack 保存位址與字元使用,後來改為使用 `priority_queue` 。自訂結構體 `struct foo`,保存字元與位址。
1. 遇到 `*`,就要將目前最小數值的字元從字串移除,複數個相同字元則移除距離最近的字元。因此需要自訂排序,對照 `cmp` 函式。
2. 將`priority_queue` 元素全部取出,並以位址從小到大排序,對照 `cmp2` 函式。
3. 計算結果並輸出。
```clike
class Solution {
public:
struct foo {
char v;
int pos;
};
using ff = struct foo;
string clearStars(string s) {
const int n = s.size();
// greater, increasing
auto cmp = [](ff f1, ff f2) {
if (f1.v == f2.v)
return f1.pos < f2.pos;
return f1.v > f2.v;
};
priority_queue<ff, vector<ff>, decltype(cmp)> pq;
for(int i = 0; i < n; i++) {
if(s[i] != '*') {
pq.push({.v=s[i], .pos=i});
}else{
if(pq.empty())
continue;
ff f1 = pq.top();
pq.pop();
}
}
auto cmp2 = [](ff &f1, ff &f2){
return f1.pos < f2.pos;
};
vector<ff> med;
while(!pq.empty()) {
med.push_back(pq.top());
pq.pop();
}
sort(med.begin(), med.end(), cmp2);
string ret;
for(ff &f1: med) {
ret.push_back(f1.v);
}
return ret;
}
};
```
### 第四題
第四題題目定義非常簡單,我剛開始的想法為應用位元操作,使得每個子陣列的 AND 結果計算時間複雜度降為 $O(1)$,最終整體時間複雜度為 $O(N^2)$,但實際上沒有這種作法。
#### 第一個版本
這是第一次就能想到的程式碼,但是在 LeetCode 最後幾個測資會超時。
```clike
int minimumDifference(vector<int>& nums, int k) {
const int n = nums.size();
int ret = INT_MAX;
for(int i = 0; i < n; i++) {
int val = nums[i];
for(int j = i; j < n; j++) {
val &= nums[j];
int kv = abs(k - val);
ret = min(ret, kv);
}
}
return ret;
}
```
#### 第二個版本
參考[討論區解答](https://leetcode.com/problems/find-subarray-with-bitwise-and-closest-to-k/solutions/5243278/pruning),使用 Dynamic Programming 剪枝不必要的計算,時間複雜度為 $O(N^2)$
之前學 DP 都是避免重複計算與保留狀態,這裡我是第一次見到可以用來剪枝不必要的運算。
作者表示:
1. 子陣列在 AND 操作過程中會變為非遞增陣列
2. DP Table 定義為以第 j 個元素作為結尾的最大數值(`dp[j]` stores the maximum bitwise and of a subarray that ends at index j)
3. 剪枝運算
- `dp[j]` 最終數值即為 `nums[j]`
- 在第 12 行中,作者是用 `k - val >= ret || val <= dp[j]` 當作 `break` 條件,因為當 `k >= val` 且 `k - val >= ret`,表示接下來的 `val` 數值只會越小且 `|k - val|` 會更大
```clike=
int minimumDifference(vector<int>& nums, int k) {
const int n = nums.size();
// dp[j] stores the minimum bitwise and of a subarray that ends at index j
vector<int> dp(n + 1, 0);
int ret = INT_MAX;
for(int i = 0; i < n; i++) {
int val = nums[i];
for(int j = i; j < n; j++) {
val &= nums[j];
int kv = abs(k - val);
ret = min(ret, kv);
// if(k - val >= ret || val <= dp[j])
if(val <= dp[j])
break;
dp[j] = val;
}
}
return ret;
}
```