1. 程式人生 > >LeetCode #188場周賽題解

LeetCode #188場周賽題解

[A題連結](https://leetcode-cn.com/problems/build-an-array-with-stack-operations/) 給你一個目標陣列 target 和一個整數 n。每次迭代,需要從 list = {1,2,3..., n} 中依序讀取一個數字。 請使用下述操作來構建目標陣列 target : * Push:從 list 中讀取一個新元素, 並將其推入陣列中。 * Pop:刪除陣列中的最後一個元素。 * 如果目標陣列構建完成,就停止讀取更多元素。 題目資料保證目標陣列嚴格遞增,並且只包含 1 到 n 之間的數字。 請返回構建目標陣列所用的操作序列。 題目資料保證答案是唯一的。 **示例 1** ```pascal 輸入:target = [1,3], n = 3 輸出:["Push","Push","Pop","Push"] 解釋: 讀取 1 並自動推入陣列 -> [1] 讀取 2 並自動推入陣列,然後刪除它 -> [1] 讀取 3 並自動推入陣列 -> [1,3] ``` **示例 2:** ```pascal 輸入:target = [1,2,3], n = 3 輸出:["Push","Push","Push"] ``` **示例 3:** ```pascal 輸入:target = [1,2], n = 4 輸出:["Push","Push"] 解釋:只需要讀取前 2 個數字就可以停止。 ``` **提示:** - `1 <= target.length <= 100` - `1 <= target[i] <= 100` - `1 <= n <= 100` - `target` 是嚴格遞增的 ```cpp class Solution { public: //正常判斷是否需要在target陣列中儲存,同時如果足夠了即可break vector buildArray(vector& target, int n) { vectorv; int len = target.size(); int j = 0; for(int i = 1;i <=n ;++i){ if(j == len)break; if(i == target[j]){ v.push_back("Push"); ++j; } else{ v.push_back("Push"); v.push_back("Pop"); } } return v; } }; ```
[B題](https://leetcode-cn.com/problems/count-triplets-that-can-form-two-arrays-of-equal-xor/) 給你一個整數陣列 arr 。 現需要從陣列中取三個下標 i、j 和 k ,其中 (0 <= i < j <= k < arr.length) 。 a 和 b 定義如下: * a = arr[i] ^ arr[i + 1] ^ ... ^ arr[j - 1] * b = arr[j] ^ arr[j + 1] ^ ... ^ arr[k] 注意:^ 表示 按位異或 操作。 請返回能夠令 `a == b` 成立的三元組$ (i, j , k)$ 的數目。 **示例 1:** ```pascal 輸入:arr = [2,3,1,6,7] 輸出:4 解釋:滿足題意的三元組分別是 (0,1,2), (0,2,2), (2,3,4) 以及 (2,4,4) ``` **示例2:** ```pascal 輸入:arr = [1,1,1,1,1] 輸出:10 ``` **示例 3:** ```pascal 輸入:arr = [2,3] 輸出:0 ``` **思路:** arr[i]^...^arr[j-1]的異或結果可以轉化為(arr[0]^...^arr[j-1])^(arr[0]^...^arr[i-1]),因為相同的值異或為0,而異或一個0是不影響原結果的。因此事先計算出會用到的異或結果,用陣列dp儲存。 ```cpp class Solution { public: int countTriplets(vector& a) { int n = a.size(); vector s(n+1); for (int i = 1; i <= n; ++ i) s[i] = s[i-1]^a[i-1]; int ret = 0; for (int i = 1; i <= n; ++ i) for (int j = i+1; j <= n; ++ j) for (int k = j; k <= n; ++ k) { if ((s[j-1]^s[i-1]) == (s[k]^s[j-1])) ret ++; } return ret; } }; ```

[C題](https://leetcode-cn.com/problems/minimum-time-to-collect-all-apples-in-a-tree/) 給你一棵有 n 個節點的無向樹,節點編號為 0 到 n-1 ,它們中有一些節點有蘋果。通過樹上的一條邊,需要花費 1 秒鐘。你從 節點 0 出發,請你返回最少需要多少秒,可以收集到所有蘋果,並回到節點 0 。 無向樹的邊由 edges 給出,其中 edges[i] = [fromi, toi] ,表示有一條邊連線 from 和 toi 。除此以外,還有一個布林陣列 hasApple ,其中 hasApple[i] = true 代表節點 i 有一個蘋果,否則,節點 i 沒有蘋果。 **示例 1:** ![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/05/10/min_time_collect_apple_1.png) ```pascal 輸入:n = 7, edges = [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]], hasApple = [false,false,true,false,true,true,false] 輸出:8 解釋:上圖展示了給定的樹,其中紅色節點表示有蘋果。一個能收集到所有蘋果的最優方案由綠色箭頭表示。 ``` **示例 2:** ![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/05/10/min_time_collect_apple_2.png) ```pascal 輸入:n = 7, edges = [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]], hasApple = [false,false,true,false,false,true,false] 輸出:6 解釋:上圖展示了給定的樹,其中紅色節點表示有蘋果。一個能收集到所有蘋果的最優方案由綠色箭頭表示。 ``` **思路:** 只要那個節點是true,向上一直將父節點同化,那麼路徑就等於:每兩個連線(子、父都為true)的點的那條線x 2 後的和 ```cpp class Solution { public: int minTime(int n, vector>& edges, vector& hasApple) { int i,res=0; //若子節點為true,則父節點同化為true for(i=edges.size()-1; i>=0; i--) if(hasApple[edges[i][1]]==true) hasApple[edges[i][0]]=true; // 收集蘋果的路徑即為所有節點為true的拓撲圖的所有連線*2 for(i=0; i [D題](https://leetcode-cn.com/problems/number-of-ways-of-cutting-a-pizza/) 給你一個 rows x cols 大小的矩形披薩和一個整數 k ,矩形包含兩種字元: 'A' (表示蘋果)和 '.' (表示空白格子)。你需要切披薩 k-1 次,得到 k 塊披薩並送給別人。 切披薩的每一刀,先要選擇是向垂直還是水平方向切,再在矩形的邊界上選一個切的位置,將披薩一分為二。如果垂直地切披薩,那麼需要把左邊的部分送給一個人,如果水平地切,那麼需要把上面的部分送給一個人。在切完最後一刀後,需要把剩下來的一塊送給最後一個人。 請你返回確保每一塊披薩包含 至少 一個蘋果的切披薩方案數。由於答案可能是個很大的數字,請你返回它對 10^9 + 7 取餘的結果。 **示例 1:** ![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/05/10/ways_to_cut_apple_1.png) ```pascal 輸入:pizza = ["A..","AAA","..."], k = 3 輸出:3 解釋:上圖展示了三種切披薩的方案。注意每一塊披薩都至少包含一個蘋果。 ``` **示例 2:** ```pascal 輸入:pizza = ["A..","AA.","..."], k = 3 輸出:1 ``` **示例 3:** ```pascal 輸入:pizza = ["A..","A..","..."], k = 1 輸出:1 ``` * 1 <= rows, cols <= 50 * rows == pizza.length * cols == pizza[i].length * 1 <= k <= 10 * pizza 只包含字元 'A' 和 '.' 。 **思路:** 本題是統計切割方案數,一看就是使用dp,怎麼來思考呢? 首先我們考慮存在的狀態數: 毫無疑問,披薩被切成k塊肯定是狀態之一。 而如何表示當前剩餘部分的披薩呢?題中說把左邊和上邊給一個人,可以知道,右下部分總是會剩餘下來。 所以可以記錄左上角的位置來表示剩餘的披薩。 因此一個三維陣列可以做為dp陣列:dp[i][j][k] i,j表示披薩剩餘部分的左上角,k表示當前披薩被切成k塊 初始狀態顯而易見,由於只有一塊,沒有切,左上角為(0,0),所以dp[0][0][1]=1 **狀態轉移** 知道初始狀態後,我們就要開始進行狀態轉移了~ 首先讓我們來考慮怎麼從一塊變成兩塊 ```cpp 披薩: A.. AAA ... ``` 由於我們知道可以水平切和垂直切,左上角為(i,j),一刀切下去,可以從k變成k+1 因此,我們可以窮舉每個狀態水平切和垂直切的所有切法,來得到k+1的狀態。 因為每切一次得到的剩餘披薩左上角都不同,所以不會出現重複。 首先水平切: ```cpp 左上角為(0,0),k=1 A.. A.. +++ AAA AAA +++ ... ... 剩餘部分:左上角為(1,0),k=2 剩餘部分:左上角為(2,0),k=2 (不合理) ``` 有這兩種切法,很清楚看出來,第二種切法是不可以的,因為下面那一部分不存在A,不符合題意。 如何判斷剩餘和切出來的披薩存不存在A,我們先記下這個問題,後面會提到。 所以可以得到水平切的狀態轉移方程: ```cpp 記原來的左上角為(i,j),新的左上角為(x,y) if(兩部分都存在A){ dp[x][y][k+1]+=dp[i][j][k] } ``` 垂直切是和水平切一樣的,就不說了。 **解決存在A的問題** 我們如何判斷切開後的兩塊披薩是否存在A呢? 方法1:直接暴力求解,我不知道會不會超時,我沒有試,有興趣可以寫一下。 方法2:利用數學知識,計算出來對應披薩塊中A的數量,假如數量大於0,則存在A 方法3:別的方法,假如有人願意分享更簡單的,可以在評論分享,大家一起進步 我使用方法2,所以就寫一下方法2: 用陣列num[i][j]表示以(0,0)為左上角,(i,j)為右下角的披薩塊中包含的A數量 ```cpp 上例中: num: 1 1 1 2 3 4 2 3 4 ``` 怎麼計算num陣列使用簡單的dp和數學知識就可以了,這裡就不再贅述。 通過num陣列和獲得披薩塊的左上角和右下角就可以輕易地算出A的個數: ```cpp 這個大家肯定都會,就舉個例子吧,就是簡單的數學關係: 例:計算以(1,0)為左上角,(2,2)為右下角的披薩塊A數目: num[2][2]-num[0][2]-num[2][-1]+num[0][-1]; 下標中出現-1的num值都用0代替:所以為4-1-0-0=3 ``` 看不懂的可以直接看程式碼如何計算A數目,一看就明白了 ```cpp #define ll long long int class Solution { public: const ll mod=1e9+7; int ways(vector& pizza, int k) { int row=pizza.size(),col=pizza[0].length(); //計算num vector> num(row,vector(col,0)); if(pizza[0][0]=='A') num[0][0]=1; for(int i=1;i>> dp(row,vector>(col,vector(k+1,0))); dp[0][0][1]=1; //從k=2開始填充 for(int x=2;x<=k;x++){ for(int i=0;i>& num,int sr,int sc,int er,int ec){ int num1=0,num2=0,num3=0,res; if(sr!=0 && sc!=0) num1=num[sr-1][sc-1]; if(sr!=0) num2=num[sr-1][ec]; if(sc!=0) num3=num[er][sc-1]; return num[er][ec]-num2-num3+num1>0;