###### tags: `Weekly Contest` # Weekly Contest 351 ## [2748. Number of Beautiful Pairs](https://leetcode.com/problems/number-of-beautiful-pairs/)(<font color=#00B8A3>Easy</font>) 限制 : <ul> <li><code>2 &lt;= nums.length &lt;= 100</code></li> <li><code>1 &lt;= nums[i] &lt;= 9999</code></li> <li><code>nums[i] % 10 != 0</code></li> </ul> ### Solution #### 時間複雜度: $O(n^2)$ #### 空間複雜度: $O(1)$ 程式碼: ```c++= class Solution { public: int countBeautifulPairs(vector<int>& nums) { int ans = 0; int n = nums.size(); for(int i=0;i<n;i++){ int digit; while(nums[i] !=0){ digit = nums[i]%10; nums[i]/=10; } for(int j=i+1;j<n;j++){ if(__gcd(digit, nums[j]%10)==1) ans++; } } return ans; } }; ``` ## [2749. Minimum Operations to Make the Integer Zero](https://leetcode.com/problems/minimum-operations-to-make-the-integer-zero/)(<font color=#FFC011>Medium</font>) 限制 : <ul> <li><code>1 &lt;= num1 &lt;= 10<sup>9</sup></code></li> <li><code><font face="monospace">-10<sup>9</sup> &lt;= num2 &lt;= 10<sup>9</sup></font></code></li> </ul> ### Solution 這題看不出來的話非常難,但題目沒有太多額外的因素干擾,不像 hard 題會有很多可能的解法,這應該也是為什麼這題會被分在 medium。 可以想像簡化版的題目,如果 operation 從減去 $2^i + num2$ 變成減去 $2^i$ 呢。如果我們最少需要花 $k$ 次的 operation 才能將 $num1$ 變成 0,那 $num1$ 應該可以寫成以下的式子: $num1 = (2^{i_1}+2^{i_2}+...+2^{i_k})$ 那顯而易見的,如果要使 $k$ 最小,那 $k$ 就應該等於 $num1$ 的二進位裡 1 的數量。 從這點出發,我們需要排除每次 operation 會額外減去 $num2$ 的困擾,可以思考如果我們最少需要花 $k$ 次 operation 後 $num1=0$ 的情況下,以下這個式子會成立: $num1=(num2*k)+(2^{i_1}+2^{i_2}+...+2^{i_k})$ 其中的 $i_1\text{ ~ }i_k$ 是你這 $k$ 次 operation 中每一輪所選的數字。 那我們可以對以上的式子進行移項,得到: $num1-(num2*k) = (2^{i_1}+2^{i_2}+...+2^{i_k})$ 有了這個式子以後,這個問題又變回我們簡化版的題目了,因為我們只要從 $k=1$ 開始測試是否符合條件就行,那檢測條件就是看 $num1-(num2*k)$ 裡的 1 的數量是否小於等於 $k$,只要小於等於 k 那必可以找到一組合理的解。 有一個額外要注意的點是,還需要檢測 $num1-(num2*k)$ 是否大於等於 $k$,因為 $i$ 的最小值為 0,那 $2^0=1$,所以這段式子 $(2^{i_1}+2^{i_2}+...+2^{i_k})$ 無論如何都會大於等於 $k$,所以如果左式的值小於 $k$ 也是不合法的。可以 trace $num1=85, num2=42$ 這個 case 看看應該就可以理解了。 另外[這篇](https://leetcode.com/problems/minimum-operations-to-make-the-integer-zero/solutions/3679094/linear-search-on-answer-ft-bit-count/)有證明為什麼答案不會超過 60。 #### 時間複雜度: $O(1)$ #### 空間複雜度: $O(1)$ 程式碼: ```c++= class Solution { public: int count_bit(long long num){ // Count the number of 1s in the binary representation of this number int ans = 0; while(num){ if(num&1) ans++; num>>=1; } return ans; } int makeTheIntegerZero(int num1, int num2) { if(num2>=num1) return -1; long long sum=num1; for(int i=0;sum>0&&i<=65;i++){ if(count_bit(sum)<=i&&sum>=i) return i; sum-=num2; } return -1; } }; ``` ## [2750. Ways to Split Array Into Good Subarrays](https://leetcode.com/problems/ways-to-split-array-into-good-subarrays/)(<font color=#FFC011>Medium</font>) 限制 : <ul> <li><code>1 &lt;= nums.length &lt;= 10<sup>5</sup></code></li> <li><code>0 &lt;= nums[i] &lt;= 1</code></li> </ul> ### Solution array 中有幾個 1 就可以分幾段,而 1 旁邊的 0 則是每段可以浮動的範圍。 觀察之後得出規律,答案就等於**每個 1 之間的距離**相乘。 寫成數學式子長這樣,假設在這個 array 裡有 $n$ 個 1,而 $dis_i$ 代表第 $i$ 個 1 與第 $i+1$ 個 1 之間的距離。 $$ ans = \begin{cases} \prod\limits_{i=1}^{n-1}dis_i, \quad &\text{if }n>1\\ 1, \quad &\text{if }n=1\\ 0, \quad &\text{if }n=0\\ \end{cases} $$ #### 時間複雜度: $O(n)$ #### 空間複雜度: $O(1)$ 程式碼: ```c++= class Solution { public: int numberOfGoodSubarraySplits(vector<int>& nums) { long long ans = 1; const int MOD = 1e9+7; int idx=-1; for(int i=0;i<nums.size();i++){ if(nums[i]==1){ if(idx !=-1) ans = ans * (i-idx)%MOD; idx = i; } } if(idx == -1) return 0; else return ans; } }; ``` ## [2751. Robot Collisions](https://leetcode.com/problems/robot-collisions/)(<font color=#FF375F>Hard</font>) 限制 : <ul> <li><code>1 &lt;= positions.length == healths.length == directions.length == n &lt;= 10<sup>5</sup></code></li> <li><code>1 &lt;= positions[i], healths[i] &lt;= 10<sup>9</sup></code></li> <li><code>directions[i] == 'L'</code> or <code>directions[i] == 'R'</code></li> <li>All values in <code>positions</code> are distinct</li> </ul> ### Solution 這題一開始我以為是像 [BiWeekly Contest 106 的第三題](https://hackmd.io/98yelHNQTyGumeyByPqh0A?view#2731-Movement-of-RobotsMedium),結果最後才發現跟括號配對問題很像。 在對位置排序的情況下,只有兩個機器人處於 RL 的情況下才會相撞,RR、LL、LR 都不會撞。 因此我們首先對機器人的位置做排序,然後從頭開始迭代,當遇到方向為 R 的機器人時,將它的生命值 push 進 stack 裡面。當遇到方向為 L 的機器人時,如果 stack 裡有東西就代表目前成立了一個 RL 的 pair。碰撞總共有三種情形: $$ \begin{cases} left\_health := left\_health-1 , \quad &\text{if }left\_health > right\_health\\ right\_health := right\_health-1, \quad &\text{if }left\_health < right\_health\\ left\_health :=0\ and\ right\_health :=0 , \quad &\text{if }left\_health = right\_health\\ \end{cases} $$ 在所有機器人都迭代結束後,沒有損壞的機器人就是還在 stack 裡方向為 R 的機器人與在迭代過程中將 stack 清空的方向為 L 的機器人,將其生命值照一開始的順序輸出即可。 #### 時間複雜度: $O(n*logn)$ #### 空間複雜度: $O(n)$ 程式碼: ```c++= class Solution { public: vector<int> survivedRobotsHealths(vector<int>& pos, vector<int>& heal, string dir) { int n = pos.size(); stack<pair<int, int> > si; vector<pair<pair<int, int>, pair<int, int> > > pvec(n); for(int i=0;i<n;i++) pvec[i] = {{pos[i], i}, {heal[i], dir[i]}}; sort(pvec.begin(), pvec.end()); vector<int> temp(n), ans; for(int i=0;i<n;i++){ int idx = pvec[i].first.second, d = pvec[i].second.second, h = pvec[i].second.first; if(d == 'R') si.push({h, idx}); else { while(!si.empty()&&si.top().first<h) h--, si.pop(); if(si.empty()) temp[idx] = h; else if(si.top().first == h) si.pop(); else si.top().first--; } } while(!si.empty()){ temp[si.top().second] = si.top().first; si.pop(); } for(int i=0;i<n;i++){ if(temp[i]) ans.push_back(temp[i]); } return ans; } }; ```