###### tags: `CP` `BOOK` # [CP]Leetcode & 奇怪的tricks > 本篇記錄寫力扣題目遇到的一些tricks以及一些常見的作法。 ::: danger 這篇可能有部分內容會偏離CP本身,因為裡面會提及一些比較適合面試的實作,以及面試可能會被要求的樣式,但在CP中根本不會有這種要求。 ::: ### 1. 較不直觀的狀態轉移 [446. Arithmetic Slices II - Subsequence](https://leetcode.com/problems/arithmetic-slices-ii-subsequence/description/) #### 題解 定義的狀態沒有辦法很直觀求到答案,而是在求解過程中算答案。 $f[i][d]$為下標i元素為數列最末元素,且公差為d時,得到的等差數列數量;**其中,在這個狀態中,長度為2也要被算進去!!**。因此有 $$f[i][d] = f[j][d]+1, j<i$$ 假設有數列[2,4,6],則$f[1][2] = 1$,但數組[2,4]不為題目下合格的AP;而$f[2][2] = 2$,其中包含$[2,4,6]$和$[2,4]$。 當我們在統計答案的時候,長度為2的解不能被算進去。 有以下兩種方法: #### AC Code [法一] ```cpp= #define vt vector #define ll long long class Solution { public: int numberOfArithmeticSlices(vector<int>& nums) { //f[i][d] = f[i-j][d]+1, d = nums[i]-nums[j] ll n = nums.size(), ans = 0; vt<map<ll,ll>> f(n); for(ll i = 1; i<n; ++i){ for(ll j = 0; j<i; ++j){ ll d = (ll)nums[i]-(ll)nums[j]; if(f[j].count(d)){ f[i][d]+=f[j][d]; ans+=f[j][d]; } f[i][d]+=1; } } return ans; } }; ``` #### AC Code [法二] ```cpp= #define vt vector #define ll long long class Solution { public: int numberOfArithmeticSlices(vector<int>& nums) { //f[i][d] = f[i-j][d]+1, d = nums[i]-nums[j] ll n = nums.size(), ans = 0; vt<map<ll,ll>> f(n); for(ll i = 1; i<n; ++i){ for(ll j = 0; j<i; ++j){ ll d = (ll)nums[i]-(ll)nums[j]; if(f[j].count(d)){ f[i][d]+=f[j][d]; //ans+=f[j][d]; } f[i][d]+=1; } } for(ll i = 1; i<n; ++i){ for(auto& d:f[i]){ans+=d.second;} ans-=i; } return ans; } }; ``` - 法一不直觀 - 法二最後在計算答案的時候,要扣除所有長度為2的子序列,也就是$i-1$,因為第i個元素可以和前面所有$i-1$的元素形成等差子序列(雖然說2個不叫等差) 這一點的難點在為了讓轉移式成立,定義的狀態與題意的不一樣(也和等差的定義不同;3個才能叫等差)。 ### 2. Exactly k times Exactly k = (At most k) - (At most k-1) #### 例題:[992. Subarrays with K Different Integers](https://leetcode.com/problems/subarrays-with-k-different-integers/description/?envType=daily-question&envId=2024-03-30) ### 3. BFS & 層序 BFS & Dijkstra 題目:給一張圖,邊的權重為1,求兩點之間的最短路徑。 分析:套用最短路徑演算法即可;但權值為1時,可以用BFS求解。 #### 例題:[752. Open the Lock](https://leetcode.com/problems/open-the-lock/description/?envType=daily-question&envId=2024-04-22) #### AC Code [BFS] ```cpp= #define P pair<string,int> class Solution { public: static const int mxN = 1e9+7; map<string, int> vis; map<string, int> de; pair<string,string> neighbor(string a, int x){ string r1, r2; r1 = r2 = a; char c = a[x]; if(c=='0'){ r1[x] = '1'; r2[x] = '9'; } else if(c=='9'){ r1[x] = '8'; r2[x] = '0'; } else{ r1[x] = c+1; r2[x] = c-1; } return {r1, r2}; } int openLock(vector<string>& deadends, string target) { for(auto& d:deadends){ de[d] = 1; } queue<P> q; if(de["0000"]){return -1;} q.push({"0000", 0}); while(q.size()){ auto x = q.front(); q.pop(); if(x.first==target){return x.second;} for(int i = 0; i<4; ++i){ auto [p1, p2] = neighbor(x.first, i); if(!vis[p1] && !de[p1]){ vis[p1] = 1; q.push({p1, x.second+1}); } if(!vis[p2]&& !de[p2]){ vis[p2] = 1; q.push({p2, x.second+1}); } } } return -1; } }; ``` #### AC Code [層序BFS] 在上面的程式中,放進queue裡面的元素有個,第二個元素`int`是為了記錄當前的最短路徑。實際上,BFS有貪婪的特性,在**權值為1下**,先遍歷到的節點可以視為最短路徑;因此,可以透過層序式的BFS來直接對最短路徑**計數**。 ```cpp= #define P pair<string,int> class Solution { public: static const int mxN = 1e9+7; map<string, int> vis; map<string, int> de; pair<string,string> neighbor(string a, int x){ string r1, r2; r1 = r2 = a; char c = a[x]; if(c=='0'){ r1[x] = '1'; r2[x] = '9'; } else if(c=='9'){ r1[x] = '8'; r2[x] = '0'; } else{ r1[x] = c+1; r2[x] = c-1; } return {r1, r2}; } int openLock(vector<string>& deadends, string target) { for(auto& d:deadends){ de[d] = 1; } if(de["0000"]){return -1;} queue<string> q; q.push("0000"); int c = 0; while(q.size()){ int t = q.size(); while(t--){ auto x = q.front(); q.pop(); if(x==target){return c;} for(int i = 0; i<4; ++i){ auto [p1, p2] = neighbor(x, i); if(!vis[p1] && !de[p1]){ vis[p1] = 1; q.push(p1); } if(!vis[p2]&& !de[p2]){ vis[p2] = 1; q.push(p2); } } } c++; } return -1; } }; ``` #### AC Code [Dijkstra] 當然,單源最短路徑問題,還是可以使用較為泛用的演算法。Dijkstra在權值不為負值的時候都能在$O(E + VlogV)$求得最短路徑。但顯然地,Dijkstra會比前面上述兩種的複雜度還要高,因此用string作為key時(開一大堆Maps),會被卡常數(超笨)。在下面的程式碼中還是能AC的,但要把key換為int。 ```cpp= #define P pair<int,int> #define vt vector class Solution { public: set<int> de; pair<int,int> neighbor(int a, int x){ string s = to_string(a); while(s.size()<4){ s.insert(s.begin(), '0'); } string r1, r2; r1 = r2 = s; char c = s[x]; if(c=='0'){ r1[x] = '1'; r2[x] = '9'; } else if(c=='9'){ r1[x] = '8'; r2[x] = '0'; } else{ r1[x] = c+1; r2[x] = c-1; } return {stoi(r1), stoi(r2)}; } int openLock(vector<string>& deadends, string target) { const int mxN = 1e9+7; for(auto e:deadends){ de.insert(stoi(e)); } if(de.count(0)){return -1;} priority_queue<P, vt<P>, greater<P>> pq; int n = 1e4; vt<int> vis(n, 0); vt<int> dis(n, mxN); int t = stoi(target); dis[t] = 0; pq.push({0, t}); while(pq.size()){ auto [mn, mnI] = pq.top(); pq.pop(); if(vis[mnI]){continue;} vis[mnI] = 1; vt<int> nxt; for(int i = 0; i<4; ++i){ auto [p1, p2] = neighbor(mnI,i); if(!de.count(p1)){nxt.push_back(p1);} if(!de.count(p2)){nxt.push_back(p2);} } for(auto& e:nxt){ int u = mnI, v = e, w = 1; if(dis[v]>dis[u]+w){ dis[v] = dis[u]+w; pq.push({dis[v], v}); } } } return dis[0]==mxN?-1:dis[0]; } }; ``` ### 4. Find the duplicate number Leetcode - 287. Find the Duplicate Number 題目:N個大的數組,裡面元素為值為[1, N]。但數組中,有一個數重複了,找出他。 題解: 假設數組為 [1, 2, 3, 4, 5],那對於所有i,都恰有i個數小於等於i,否則,重複數可能出現在1~i中(折半 :::danger 此處就是一個可能不一定適用於CP的trick。當我們給出hashing的方法時,通常不是面試官想看到的。 ::: ### 5. 最少次數區間更新,使數組變為0 問題:一個數組$A, a_i \le 0, \forall a_i \in A$。每次更新操作,可以使區間$[i, j], i\le j \le n$的數值減1,求最少次能將數組所有數值變為0的次數。 題目: [3229. Minimum Operations to Make Array Equal to Target](https://leetcode.com/problems/minimum-operations-to-make-array-equal-to-target/description/) [1526. Minimum Number of Increments on Subarrays to Form a Target Array](https://leetcode.com/problems/minimum-number-of-increments-on-subarrays-to-form-a-target-array/description/) 題解:把圖畫出來之後,有很直觀的解法。 如圖,我們很直觀的要把凸出來的部分,一層一層壓下去,這樣總共壓下去的距離會最短。 ![image](https://hackmd.io/_uploads/HJQ1_I5_C.png) 圖中,紅色和粉色的線段代表要壓下去的部分(最佳的壓法)。經過觀察可以發現,紅色和粉色的線段之總和,就是整個高度數組的上升部分的總和。因此,此題的答案可以直接等於**元素上升的總和** ### 6. One to one hashing 給一個字串$s$,經過轉換後,能否轉為$t$,轉換過程為對於給一個字符,可以轉換為另一個,但其關係必須為一對一。 (在這個設定下,若$s$能轉成$t$,那$t$必能轉成$s$) 題目: - 205. Isomorphic Strings - 290. Word Pattern