---
# System prepended metadata

title: '[CP]Leetcode難題 & 常見的tricks'
tags: [BOOK, CP]

---

###### 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
