Leetcode刷题心得

目录

1. 动态规划

对于大部分动态规划类型的题目,都可以根据主问题分解出其子问题,子问题与主问题相似87.扰乱字符串】,这也是动态规划类问题与深度优先、记忆化搜索相关、回溯相关的原因,可以从这一方面入手【887.鸡蛋掉落】,先自顶向下写成dfs,再将dfs修改成为自底向上的动态规划【1092.最短公共超序列】。同时,这类问题通常都有多条分叉路可以到达终点,让你很难使用贪心的思路一次性解决该问题【354.俄罗斯套娃信封】、【714.买卖股票的最佳时机含手续】,需要记录各个状态的中间结果,进而推导出最终结果,有几维的状态变化就使用几个维度的数组【1463.摘樱桃】。

注:记忆化搜索有时候会遇到难以优化的情况【837.新21点】,这时候需要重新回归自底向上的思想,根据动态规划分析状态转移方程,根据状态转移方程进行优化【1690.石子游戏Ⅶ】。

1.1 背包问题

背包问题主要是用于处理组合、最值和存在问题,即在给定的集合中选择一部分的数,使其达到目标状态。通常有两种思路,一种是根据目标状态进行拆分,判断各个中间状态能否达到,最终得到目标状态能否达到(即固定背包大小,然后循环各个元素,本质是一种排列的方式)。另一种是考虑各个数选取或者不选取(即固定各个元素,然后循环背包大小,本质是一种组合的方式)。根据各个数是否可以重复选取分为01背包问题和完全背包问题。

1.1 01背包问题

805. 均值分割

在这里插入图片描述

bool splitArraySameAverage(vector<int>& nums) {
    // 如果存在,那么那个平均值则是可以计算的
    double total = 0;
    for(int i = 0; i < nums.size(); ++i) {
        total += nums[i];
    }

    double average = total / nums.size();   // 求出均值

    // 即选择k个数令他们的和为average * k,判断有没有存在
    // 由于数组的个数一定,可以选择判断元素个数较小的那一部分,即n/2;
    int n = nums.size();
    int m = n / 2;
    vector<unordered_set<int>> dp(m + 1);   // dp[i]表示选取在目前已经考虑的元素中选取i个元素可能的值
    dp[0].insert(0);

    for(int i = 0; i < nums.size(); ++i) {
        for(int j = m - 1; j >= 0; --j) {   // 避免覆盖原始值
            for(auto preSum : dp[j]) {
                int curSum = preSum + nums[i];
                if(curSum * n == total * (j + 1)) { // 注意浮点数判断精度的问题
                    return true;
                } else {
                    dp[j + 1].insert(curSum);
                }
            }
        }
    }

    return false;
}

1.2 完全背包问题

377. 组合总和IV

在这里插入图片描述

// 该问题为排列问题,即跟元素的顺序相关
int combinationSum4(vector<int>& nums, int target) {
    vector<int> dp(target + 1, 0);
    dp[0] = 1;
    for(int i = 1; i <= target; ++i) {		// 固定背包大小,遍历所有元素
        for(int j = 0; j < nums.size(); ++j) {
            if(i >= nums[j] && dp[i] < INT_MAX - dp[i - nums[j]]) {
                dp[i] += dp[i - nums[j]]; 
            }
        }
    }
    return dp[target];
}
518. 零钱兑换Ⅱ

在这里插入图片描述

// 该问题为组合问题,跟元素的顺序无关
int change(int amount, vector<int>& coins) {
    vector<int> dp(amount + 1, 0);
    dp[0] = 1;
    for(int i = 0; i < coins.size(); ++i) {		// 固定元素,遍历所有背包大小
        for(int j = coins[i]; j <= amount; ++j) {
            dp[j] = dp[j] + dp[j - coins[i]];
        }
    }

    return dp[amount];
}

1.2 一维DP

1.3 二维DP

97. 交错字符串

在这里插入图片描述

bool isInterleave(string s1, string s2, string s3) {
    int m = s1.length();
    int n = s2.length();
    if(m + n != s3.length()) return false;
    // dp[i][j] 表示使用s1的i个元素和s2的j个元素,构成s3的i+j个元素是否可行
    vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
    dp[0][0] = true;

    // 取出s3的i个元素
    for(int i = 1; i <= s3.length(); ++i) {
        for(int j = max(0, i - m); j <= min(i, n); ++j) {
            int indexS1 = i - j;    // 表示取出s1的元素个数
            if(indexS1 > 0 && s1[indexS1 - 1] == s3[i - 1]) {
                dp[indexS1][j] = dp[indexS1][j] || dp[indexS1 - 1][j];
            }
            
            if(j > 0 && s2[j - 1] == s3[i - 1]) {
                dp[indexS1][j] = dp[indexS1][j] || dp[indexS1][j - 1];
            }
        }
    }

    return dp[m][n];
}

72. 编辑距离

在这里插入图片描述

int minDistance(string word1, string word2) {
    int m = word1.length();
    int n = word2.length();
    // dp[i][j] 表示word1的i个字符变成word2的j个字符需要的最少操作数
    vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
    for(int i = 0; i <= n; ++i) {
        dp[0][i] = i;
    }

    for(int i = 1; i <= m; ++i) {
        for(int j = 0; j <= n; ++j) {
            if(j == 0) dp[i][j] = i;
            else {
                if(word1[i - 1] == word2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {    // 这里不能只考虑替换的操作
                    dp[i][j] = min(dp[i - 1][j - 1] + 1, min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));
                }
            }
        }
    }

    return dp[m][n];
}

115. 不同子序列

在这里插入图片描述

int numDistinct(string s, string t) {
    int m = s.length();
    int n = t.length();
    // dp[i][j] 表示t[j]以前的所有元素出现在s[i]中有几种方式
    vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
    for(int i = 0; i <= m; ++i) {
        dp[i][0] = 1;
    }
    
    // 未优化版本
    // for(int i = 0; i < m; ++i){
    //     for(int j = 0; j <= min(i, n); ++j) {
    //         for(int k = j; k <= i; ++k) {
    //             if(s[k] == t[j]) {
    //                 dp[i + 1][j + 1] = ((long long)dp[i + 1][j + 1] + dp[k][j]) > INT_MAX ? 0 : (dp[i + 1][j + 1] + dp[k][j]);
    //             }
    //         }
    //     }
    // }

    // 优化版本
    for(int i = 0; i < m; ++i){
        for(int j = 0; j < n; ++j) {
            if(s[i] == t[j]) {
                dp[i + 1][j + 1] = (long long)dp[i][j] + dp[i][j + 1] >= INT_MAX ? INT_MAX : dp[i][j] + dp[i][j + 1];
            } else {
                dp[i + 1][j + 1] = dp[i][j + 1];
            }
        }
    }

    return dp[m][n] == INT_MAX ? -1 : dp[m][n];
}

1.3. 三维DP

87. 扰乱字符串

在这里插入图片描述

bool isScramble(string s1, string s2) {
    // 1. 深度优先+记忆化搜索
    // unordered_map<string, bool> memo;
    // function<bool(string, string)> dfs = [&](string left, string right) {
    //     if(memo.count(left + right)) {
    //         return memo[left + right];
    //     }

    //     if(left == right) {
    //         return true;
    //     }

    //     bool result = false;
    //     int n = left.length();
    //     // 通过枚举的方式将其进行分割
    //     for(int i = 1; i < left.length(); ++i) {
    //         string leftTemp = left.substr(0, i);    // 确定分割方式
    //         string rightTemp = right.substr(0, i);  // 保持两个子字符串的顺序不变
    //         result = result || (dfs(leftTemp, rightTemp) && dfs(left.substr(i), right.substr(i)));
    //         if(result == true) {
    //             memo[left + right] = true;
    //             return true;
    //         }

    //         rightTemp = right.substr(n - i);        // 交换两个子字符串的顺序
    //         result = result || (dfs(leftTemp, rightTemp) && dfs(left.substr(i), right.substr(0, n - i)));
    //         if(result == true) {
    //             memo[left + right] = true;
    //             return true;
    //         }
    //     }

    //     memo[left + right] = false;
    //     return false;  // 只需要有一种分割方式满足即可
    // };

    // return dfs(s1, s2);



    // 动态规划
    // dp[i][j][len] 表示i为起点,长度为len的子字符串可以扰乱得到j为起点,长度为len的子字符串
    int n = s1.length();
    if (s2.length() != n) return false;
    bool ***dp = new bool **[n];
    for (int i = 0; i < n; i++){
        dp[i] = new bool *[n];
        for (int j = 0; j < n; j++){
            dp[i][j] = new bool[n + 1]();
            // 初始化单个字符的情况
            dp[i][j][1] = (s1[i] == s2[j]);
        }
    }
    // 枚举区间长度 2~n
    for (int len = 2; len <= n; len++){
        // 枚举 S 中的起点位置
        for (int i = 0; i <= n - len; i++){
            // 枚举 T 中的起点位置
            for (int j = 0; j <= n - len; j++){
                // 枚举划分位置
                for (int k = 1; k <= len - 1; k++){
                    // 第一种情况:S1 -> T1, S2 -> T2
                    if (dp[i][j][k] && dp[i + k][j + k][len - k]){
                        dp[i][j][len] = true; break;
                    }
                    // 第二种情况:S1 -> T2, S2 -> T1
                    if (dp[i][j + len - k][k] && dp[i + k][j][len - k]){
                        dp[i][j][len] = true; break;
                    }
                }
            }
        }
    }
    return dp[0][0][n];
}

1.4 线性DP

10. 正则表达式匹配

在这里插入图片描述

bool isMatch(string s, string p) {
    int m = s.length();
    int n = p.length();

    // dp[i][j]表示s的前i个字符是否可以被p的前j个字符匹配
    vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
    dp[0][0] = true;

    // 特判
    for(int i = 0; i < n; ++i) {
        if(p[i] == '*') {
            dp[0][i + 1] = dp[0][i - 1];
        }
    }

    for(int i = 0; i < m; ++i) {
        for(int j = 0; j < n; ++j) {
            if(p[j] == '*') {   // 可以有两种表示方式,一种是不匹配,另一种是复制前面出现的字符
                dp[i + 1][j + 1] = dp[i + 1][j - 1];
                if(p[j - 1] == s[i] || p[j - 1] == '.') {   
                    dp[i + 1][j + 1] = dp[i + 1][j + 1] || dp[i][j + 1]; // 这里的j+1有点难理解,如果写成j
                    // 会有aa -> aa* 无法被匹配到
                }
            } else if(p[j] == '.' || s[i] == p[j]) {
                dp[i + 1][j + 1] = dp[i][j];
            }
        }
    }

    return dp[m][n];
}

44. 通配符匹配

在这里插入图片描述

bool isMatch(string s, string p) {
    int m = s.length();
    int n = p.length();

    vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
    dp[0][0] = true;

    for(int i = 1; i <= n; ++i) {
        if(p[i - 1] == '*') {
            dp[0][i] = dp[0][i - 1];
        }
    }

    for(int i = 0; i < m; ++i) {
        for(int j = 0; j < n; ++j) {
            if(p[j] == '?' || s[i] == p[j]) {
                dp[i + 1][j + 1] = dp[i][j];
            } else if(p[j] == '*') {
                dp[i + 1][j + 1] = dp[i][j] || dp[i][j + 1] || dp[i + 1][j];	
                // dp[i][j + 1] 很重要
            }
        }
    }

    return dp[m][n];
}

300. 最长递增子序列*(注意贪心的做法)

在这里插入图片描述

int lengthOfLIS(vector<int>& nums) {
	// 1. 动态规划
	// int res = 1;
	// dp[i]表示以i结尾的最长递增子序列
    // vector<int> dp(nums.size(), 1);

    // for(int i = 1; i < nums.size(); ++i) {
    //     for(int j = 0; j < i; ++j) {
    //         if(nums[j] < nums[i]) {
    //             dp[i] = max(dp[i], dp[j] + 1);
    //         }
    //     }
    //     res = max(res, dp[i]);
    // }

    // return res;
	
	// 2. 贪心+二分查找
    vector<int> d_len;
    d_len.push_back(nums[0]);

    for(int i = 1; i < nums.size(); ++i) {
        // 当前遍历的数值比最长序列的最末尾数大
        if(nums[i] > d_len[d_len.size() - 1]) {
            d_len.push_back(nums[i]);
        }
        else {  // 当前遍历到的数比较小
            int begin = 0, end = d_len.size() - 1;
            while(begin <= end) {
                int mid = (begin + end) / 2;
                if(d_len[mid] >= nums[i]) {  // 当前找到的值比较大
                    end = mid - 1;
                } else if(d_len[mid] < nums[i]) {
                    begin = mid + 1;
                }
            }
            d_len[end + 1] = nums[i];   // 找到第一个比nums[i]小的值
        }
    }

    return d_len.size();
}

354. 俄罗斯套娃信封问题

动态规划的解法会超时,可以参考【300.最长递增子序列

714. 买卖股票的最佳时机含手续费*

在这里插入图片描述

int maxProfit(vector<int>& prices, int fee) {
    // // 1. 动态规划,遍历所有情况
    // int n = prices.size();
    // vector<vector<int>> dp(n, vector<int>(2));
    // dp[0][0] = 0; dp[0][1] = -prices[0];

    // // 0:第i天交易完成后手里没有股票
    // // 1:第i天交易完成后手里有股票
    // for(int i = 1; i < prices.size(); ++i) {
    //     dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee);
    //     dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
    // }

    // return dp[n - 1][0];

    // 2. 贪心
    int result = 0;
    int buy = prices[0] + fee;

    for(int i = 1; i < prices.size(); ++i) {
        // 交易需要手续费,因此需要买入的价格比卖出的价格加上手续费低,才可以
        if(prices[i] + fee < buy) {
            buy = prices[i] + fee;
        } else if(prices[i] > buy) {
            result += prices[i] - buy;  // 纯利润
            buy = prices[i];
        }
    }

    return result;
}

887. 鸡蛋掉落*

在这里插入图片描述

unordered_map<int, int> memo;
int dp(int k, int n) {
    if (memo.find(n * 100 + k) == memo.end()) {
        int ans;
        if (n == 0) {
            ans = 0;
        } else if (k == 1) {
            ans = n;
        } else {
            int lo = 1, hi = n;
            while (lo + 1 < hi) {
                int x = (lo + hi) / 2;
                int t1 = dp(k - 1, x - 1);
                int t2 = dp(k, n - x);

                if (t1 < t2) {
                    lo = x;
                } else if (t1 > t2) {
                    hi = x;
                } else {
                    lo = hi = x;
                }
            }

            ans = 1 + min(max(dp(k - 1, lo - 1), dp(k, n - lo)),
                               max(dp(k - 1, hi - 1), dp(k, n - hi)));
        }

        memo[n * 100 + k] = ans;
    }

    return memo[n * 100 + k];
}

int superEggDrop(int k, int n) {
    // 1. 动态规划(超时)
    // vector<vector<int>> dp(k + 1, vector<int>(n + 1, 0));
    // for(int i = 1; i <= n; ++i) {
    //     dp[0][i] = INT_MAX;
    // }

    // for(int i = 1; i <= k; ++i) {
    //     for(int j = 1; j <= n; ++j) {
    //         int temp = INT_MAX;
    //         for(int x = 1; x <= j; ++x) {   // 选择在x层扔苹果需要的操作数
    //             int tempPreLayer = max(dp[i][j - x], dp[i - 1][x - 1]);
    //             temp = min(temp, tempPreLayer);
    //         }
    //         dp[i][j] = 1 + temp;
    //     }
    // }

    // return dp[k][n];


    // 2. 动态规划+二分查找优化
    vector<vector<int>> dp(k + 1, vector<int>(n + 1, 0));
    for(int i = 1; i <= n; ++i) {
        dp[0][i] = INT_MAX;
    }

    for(int i = 1; i <= k; ++i) {
        for(int j = 1; j <= n; ++j) {
            int left = 0;
            int right = j + 1;
            while(left + 1 != right) {
                int mid = left + (right - left) / 2;
                if(dp[i][j - mid] >= dp[i - 1][mid - 1]) {
                    left = mid;
                } else {
                    right = mid;
                }
            }
            int temp = 0;
            if(left != 0) {
                temp = max(dp[i][j - left], dp[i - 1][left - 1]);
            }
            if(right != j + 1) {
                temp = min(temp, max(dp[i][j - right], dp[i - 1][right - 1]));
            }
            dp[i][j] = 1 + temp;
        }
    }

    return dp[k][n];

    // 3. 深度优先+记忆化搜索
    // return dp(k, n);
}

940. 不同的子序列*

在这里插入图片描述

int distinctSubseqII(string s) {
    // 1. 动态规划(无优化)
    // int n = s.length();
    // vector<int> dp(n, 1);

    // for(int i = 1; i < n; ++i) {
    //     unordered_set<char> strSet;
    //     for(int j = i - 1; j >= 0; --j) {
    //         if(strSet.find(s[j]) == strSet.end()) {
    //             dp[i] = (dp[i] + dp[j]) % (int(1e9) + 7);
    //             strSet.insert(s[j]);
    //         } 
    //     }
    // }

    // int result = 0;
    // unordered_set<char> strSet;
    // for(int i = n - 1; i >= 0; --i) {
    //     if(strSet.find(s[i]) == strSet.end()) {
    //         result = (result + dp[i]) % (int(1e9) + 7);
    //         strSet.insert(s[i]);
    //     }
    // }

    // return result;

    // 2. 记录出现过的字符的最远下标,直接去找所有出现过的字符
    int n = s.length();
    vector<int> dp(n, 1);

    vector<int> last(26, -1);   // -1表示未出现过
    for(int i = 0; i < n; ++i){
        for(int j = 0; j < 26; ++j) {
            if(last[j] != -1) {
                dp[i] = (dp[i] + dp[last[j]]) % (int(1e9) + 7);
            }
        }
        last[s[i] - 'a'] = i;
    }

    int result = 0;
    for(int i = 0; i < 26; ++i) {
        if(last[i] != -1) {
            result = (result + dp[last[i]]) % (int(1e9) + 7);
        }
    }

    return result;
}

1092. 最短公共超序列*

在这里插入图片描述

string shortestCommonSupersequence(string str1, string str2) {
    // 1. 深度优先+记忆化搜索(超时:对字符串做切割)
    // int m = str1.length();
    // int n = str2.length();
    // unordered_map<int, string> strMap;
    // function<string(string, string)> dfs = [&](string s1, string s2) {
    //     int key = s1.length() * n + s2.length();
    //     if(strMap.count(key)) return strMap[key];
    //     if(s1 == "") return s2;
    //     if(s2 == "") return s1;

    //     string s1Pre = s1.substr(0, s1.length() - 1);
    //     string s2Pre = s2.substr(0, s2.length() - 1);
    //     // 如果最后一个元素相同
    //     if(s1[s1.length() - 1] == s2[s2.length() - 1]) {
    //         strMap[key] = dfs(s1Pre, s2Pre) + s1[s1.length() - 1];
    //     } else {
    //         string temp1 = dfs(s1Pre, s2);
    //         string temp2 = dfs(s1, s2Pre);
    //         if(temp1.length() <= temp2.length()) {
    //             strMap[key] = temp1 + s1[s1.length() - 1];
    //         } else {
    //             strMap[key] = temp2 + s2[s2.length() - 1];
    //         }
    //     }

    //     return strMap[key];s
    // };

    // return dfs(str1, str2);


    // int m = str1.length();
    // int n = str2.length();
    // vector<vector<int>> memo(m, vector<int>(n, -1));

    // function<int(int, int)> dfs = [&](int i, int j) {
    //     if(i < 0) return j + 1;
    //     if(j < 0) return i + 1;
    //     if(memo[i][j] != -1) return memo[i][j];

    //     if(str1[i] == str2[j]) {
    //         memo[i][j] = dfs(i - 1, j - 1) + 1;
    //     }
    //     else {
    //         memo[i][j] = min(dfs(i - 1, j), dfs(i, j - 1)) + 1;
    //     }

    //     return memo[i][j];
    // };

    // function<string(int, int)> makeAns = [&](int i, int j) {
    //     if(i < 0) return str2.substr(0, j + 1);
    //     if(j < 0) return str1.substr(0, i + 1);

    //     if(str1[i] == str2[j]) return makeAns(i - 1, j - 1) + str1[i];
    //     else {
    //         int len1 = dfs(i - 1, j);
    //         int len2 = dfs(i, j - 1);
    //         if(len1 <= len2) {
    //             return makeAns(i - 1, j) + str1[i];
    //         } else {
    //             return makeAns(i, j - 1) + str2[j];
    //         }
    //     }
    // };

    // return makeAns(m - 1, n - 1);


    // 3. 根据深度优先+记忆化搜索构造动态规划
    int m = str1.length();
    int n = str2.length();
    // dp[i][j] 表示str1的i个字符与str2的j个字符构成的最短公共超序列的长度
    vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
    for(int i = 1; i <= n; ++i) {
        dp[0][i] = i;
    }

    for(int i = 1; i <= m; ++i) {
        dp[i][0] = i;
    }

    // 正
    for(int i = 1; i <= m; ++i) {
        for(int j = 1; j <= n; ++j) {
            if(str1[i - 1] == str2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + 1;
            }
        }
    }

    // 反
    string result;
    int i = m - 1, j = n - 1;
    while(i >= 0 && j >= 0) {
        if(str1[i] == str2[j]) {
            result.push_back(str1[i]);
            --i;
            --j;
        } else {
            if(dp[i][j + 1] <= dp[i + 1][j]) {
                result.push_back(str1[i--]);
            } else {
                result.push_back(str2[j--]);
            }
        }
    }

    reverse(result.begin(), result.end());

    return str1.substr(0, i + 1) + str2.substr(0, j + 1) + result;
}

1463. 摘樱桃Ⅰ*

在这里插入图片描述

int cherryPickup(vector<vector<int>>& grid) {
    int m = grid.size();
    int n = grid[0].size();
    int dp[m][n][n];
    for(int i = 0; i < n; ++i) {
        for(int j = 0; j < n; ++j) {
            if(i == j) {
                dp[m - 1][i][j] = grid[m - 1][i];
            } else {
                dp[m - 1][i][j] = grid[m - 1][i] + grid[m - 1][j];
            }
        }
    }

    for(int i = m - 2; i >= 0; --i) {
        for(int j = 0; j < n; ++j) {
            for(int k = 0; k < n; ++k) {
                dp[i][j][k] = 0;
                int temp = j == k ? grid[i][j] : grid[i][j] + grid[i][k];
                for(int dy0 = -1; dy0 <= 1; ++dy0) {
                    for(int dy1 = -1; dy1 <= 1; ++dy1) {
                        int y0 = j + dy0, y1 = k + dy1;
                        if(y0 >= 0 && y0 < n && y1 >= 0 && y1 < n) {
                            dp[i][j][k] = max(dp[i][j][k], dp[i + 1][y0][y1] + temp);
                        }
                    }
                }
            }
        }
    }

    return dp[0][0][n - 1];
}

1696. 跳跃游戏Ⅵ

int maxResult(vector<int>& nums, int k) {
    // 1. 动态规划(超时)
    // dp[i] 表示以nums[i]结尾的间隔不超过k的子序列的最大值
    // vector<int> dp(nums.size(), INT_MIN);
    // dp[0] = nums[0];

    // for(int i = 1; i < nums.size(); ++i) {
    //     for(int j = 1; j <= k; ++j) {
    //         if(i - j < 0) break;
    //         dp[i] = max(dp[i], dp[i - j] + nums[i]);
    //     }
    // }

    // return dp[nums.size() - 1];


    // 2. 动态规划+优先队列优化
    // vector<int> dp(nums.size(), INT_MIN);
    // dp[0] = nums[0];

    // priority_queue<pair<int, int>> mQueue;
    // mQueue.push({dp[0], 0});
    // for(int i = 1; i < nums.size(); ++i) {
    //     while(mQueue.top().second < i - k) {
    //         mQueue.pop();
    //     }

    //     dp[i] = mQueue.top().first + nums[i];
    //     mQueue.push({dp[i], i});
    // }

    // return dp[nums.size() - 1];

    // 3. 空间优化
    int result = nums[0];

    priority_queue<pair<int, int>> mQueue;
    mQueue.push({result, 0});
    for(int i = 1; i < nums.size(); ++i) {
        while(mQueue.top().second < i - k) {
            mQueue.pop();
        }

        result = mQueue.top().first + nums[i];
        mQueue.push({result, i});
    }

    return result;
}

1.5 区间DP

区间DP问题通常可以转换为填充问题,先使用自顶向下的深度优先+记忆化搜索,再转换为自底向上的动态规划。动态规划常常需要从n-1推到0。

312. 戳气球

在这里插入图片描述

int maxCoins(vector<int>& nums) {
    // 深度优先+记忆化搜索
    // unordered_map<int, int> memo;
    // int n = nums.size();
    // // 将问题转换为自顶向下填充气球
    // function<int(int, int)> dfs = [&](int left, int right) {
    //     int key = (left + 1) * (n + 2) + right + 1;
    //     if(memo.count(key)) return memo[key];

    //     if(left + 1 >= right) {
    //         return 0;
    //     }

    //     // 枚举在各个位置填充气球
    //     int result = 0;
    //     int l = left == -1 ? 1 : nums[left];
    //     int r = right == nums.size() ? 1 : nums[right];

    //     for(int i = left + 1; i < right; ++i) {
    //         int tempLeft = dfs(left, i);
    //         int tempRight = dfs(i, right);
    //         result = max(result, tempLeft + tempRight + nums[i] * l * r);
    //     }

    //     memo[key] = result;
    //     return memo[key];
    // };

    // return dfs(-1, nums.size());


    // 动态规划
    int n = nums.size();
    // dp[i][j]表示填充区间[i, j]的气球获得最大硬币的数量
    vector<vector<int>> dp(n, vector<int>(n, 0));

    for(int i = n - 1; i >= 0; --i) {
        for(int j = i; j < n; ++j) {    // 注意1个值的时候不能直接赋值为nums[i]
            int tempMax = 0;
            int l = i - 1 == -1 ? 1 : nums[i - 1];
            int r = j + 1 == n ? 1 : nums[j + 1];
            for(int k = i; k <= j; ++k) {
                int tempValue = 0;
                if(k - 1 >= 0) tempValue += dp[i][k - 1];
                if(k + 1 < n) tempValue += dp[k + 1][j];
                tempMax = max(tempMax, nums[k] * l * r + tempValue);
            }
            dp[i][j] = tempMax;
        }
    }

    return dp[0][n - 1];

}

516. 最长回文子序列

在这里插入图片描述

int longestPalindromeSubseq(string s) {
    int n = s.length();
    vector<vector<int>> dp(n, vector<int>(n, 0));
    for(int i = 0; i < n; ++i) {
        dp[i][i] = 1;
    }

    for(int i = n - 1; i >= 0; --i) {
        for(int j = i + 1; j < n; ++j) {
            if(s[i] == s[j]) {
                dp[i][j] = i + 1 == j ? 2 : dp[i + 1][j - 1] + 2;
            } else {
                dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
            }
        }
    }

    return dp[0][n - 1];
}

664. 奇怪的打印机

在这里插入图片描述

int strangePrinter(string s) {
    // 1. 深度优先+记忆化搜索
    // int n = s.length();
    // unordered_map<int, int> memo;

    // function<int(int, int)> dfs = [&](int left, int right) {
    //     int key = left * n + right;
    //     if(memo.count(key)) return memo[key];

    //     if(left == right) {
    //         return 1;
    //     }

    //     if(s[left] == s[right]) {
    //         memo[key] = dfs(left, right - 1);
    //     } else {
    //         int temp = INT_MAX;
    //         for(int i = left; i < right; ++i) {
    //             temp = min(temp, dfs(left, i) + dfs(i + 1, right));
    //         }
    //         memo[key] = temp;
    //     }

    //     return memo[key];
    // };

    // return dfs(0, s.length() - 1);


    // 2. 动态规划
    int n = s.length();
    vector<vector<int>> dp(n, vector<int>(n, 0));
    for(int i = 0; i < n; ++i) {
        dp[i][i] = 1;
    }

    for(int i = n - 1; i >= 0; --i) {
        for(int j = i + 1; j < n; ++j) {
            if(s[i] == s[j]) {
                dp[i][j] = dp[i][j - 1];
            } else {
                int temp = INT_MAX;
                for(int k = i; k < j; ++k) {
                    temp = min(temp, dp[i][k] + dp[k + 1][j]);
                }
                dp[i][j] = temp;
            }
        }
    }

    return dp[0][n - 1];
}

730. 统计不同回文子序列

在这里插入图片描述

int countPalindromicSubsequences(string s) {
    // 1. 三维动态规划
    // int n = s.length();
    // vector<vector<vector<int>>> dp(4, vector<vector<int>>(n, vector<int>(n, 0)));
    // for(int i = 0; i < n; ++i) {
    //     dp[s[i] - 'a'][i][i] = 1;
    // }

    // for(int i = n - 1; i >= 0; --i) {
    //     for(int j = i + 1; j < n; ++j) {
    //         for(int k = 0; k < 4; ++k) {
    //             if(s[i] == k + 'a' && s[j] == k + 'a') {
    //                 dp[k][i][j] = ((long long)2 + dp[0][i + 1][j - 1] + dp[1][i + 1][j - 1]
    //                     + dp[2][i + 1][j - 1] + dp[3][i + 1][j - 1]) % ((int)1e9 + 7);
    //             } else if(s[i] == k + 'a') {
    //                 dp[k][i][j] = dp[k][i][j - 1];
    //             } else if(s[j] == k + 'a') {
    //                 dp[k][i][j] = dp[k][i + 1][j];
    //             } else {
    //                 dp[k][i][j] = dp[k][i + 1][j - 1];
    //             }
    //         }
    //     }
    // }

    // return ((long long)dp[0][0][n - 1] + dp[1][0][n - 1] + dp[2][0][n - 1] + dp[3][0][n - 1]) % ((int)1e9 + 7);

    // 2. 二维动态规划
    int n = s.length();
    // dp[i][j] 表示s的i个字符到j号字符之间不同的回文子序列个数
    
    vector<vector<int>> next(n, vector<int>(4, n));
    vector<vector<int>> pre(n, vector<int>(4, -1));
    vector<int> pos(4, n);
    for(int i = n - 1; i >= 0; --i) {
        next[i] = pos;
        pos[s[i] - 'a'] = i;
    }

    pos[0] = -1, pos[1] = -1, pos[2] = -1, pos[3] = -1;
    for(int i = 0; i < n; ++i) {
        pre[i] = pos;
        pos[s[i] - 'a'] = i;
    }

    vector<vector<int>> dp(n, vector<int>(n, 0));
    for(int i = 0; i < n; ++i) {
        dp[i][i] = 1;
    }

    for(int i = n - 1; i >= 0; --i) {
        for(int j = i + 1; j < n; ++j) {
            if(s[i] == s[j]) {
                int low = next[i][s[i] - 'a'];
                int high = pre[j][s[i] - 'a'];
                if(low == j) {
                    dp[i][j] = (2 + 2 * (long long)dp[i + 1][j - 1]) % ((int)1e9 + 7);
                } else if(low == high) {
                    dp[i][j] = (1 + 2 * (long long)dp[i + 1][j - 1]) % ((int)1e9 + 7);
                } else {
                    // 注意取模可能会导致相减出现负值
                    dp[i][j] = (2 * (long long)dp[i + 1][j - 1] - dp[low + 1][high - 1] + (int)1e9 + 7) % ((int)1e9 + 7);
                }
            } else {
                dp[i][j] = ((long long)dp[i + 1][j] + dp[i][j - 1] - dp[i + 1][j - 1] + (int)1e9 + 7) % ((int)1e9 + 7);
            }
        }
    }

    return dp[0][n - 1];
}

1039. 多边形三角剖分的最低的得分

在这里插入图片描述

int minScoreTriangulation(vector<int>& values) {
    // 1. 深度优先+记忆化搜索
    // int n = values.size();
    // unordered_map<int, int> memo;

    // function<int(int, int)> dfs = [&](int left, int right) {
    //     int key = left * n + right;
    //     if(memo.count(key)) return memo[key];

    //     if(left + 2 == right) {
    //         return values[left] * values[left + 1] * values[right];
    //     }

    //     if(left + 2 > right) {
    //         return 0;
    //     }

    //     int result = INT_MAX / 2;
    //     for(int i = left + 1; i < right; ++i) {
    //         result = min(result, values[left] * values[right] * values[i] + dfs(left, i) + dfs(i, right));
    //     }

    //     memo[key] = result;

    //     return memo[key];
    // };

    // return dfs(0, n - 1);

    // 2. 动态规划
    int n = values.size();
    vector<vector<int>> dp(n, vector<int>(n, 0));
    for(int i = 0; i < n - 2; ++i) {
        dp[i][i + 2] = values[i] * values[i + 1] * values[i + 2];
    }

    for(int i = n - 1; i >= 0; --i) {
        for(int j = i + 3; j < n; ++j) {
            int temp = INT_MAX;
            for(int k = i + 1; k < j; ++k) {
                temp = min(temp, values[k] * values[i] * values[j] + dp[i][k] + dp[k][j]);
            }
            dp[i][j] = temp;
        }
    }

    return dp[0][n - 1];
}

813. 最大平均值和的分组

在这里插入图片描述

double largestSumOfAverages(vector<int>& nums, int k) {
    // 1. 深度优先+记忆化搜索
    int n = nums.size();
    unordered_map<int, double> memo;

    function<double(int, int, int)> dfs = [&](int l, int r, int t) {
        int key = l * n * n + r * n + t;
        if(memo.count(key)) return memo[key];

        if (r - l + 1 == t) {
            double sum = 0.0;
            for (int i = l; i <= r; ++i) {
                sum += nums[i];
            }
            memo[key] = sum;
            return memo[key];
        }

        if (t == 1) {
            double sum = 0.0;
            for (int i = l; i <= r; ++i) {
                sum += nums[i];
            }
            memo[key] = sum / (r - l + 1);
            return memo[key];
        }

        double result = 0.0;
        for(int i = l; i <= r - t + 1; ++i) {
            double sum = 0;
            for(int j = l; j <= i; ++j) {
                sum += nums[j];
            }
            result = max(result, sum / (i - l + 1) + dfs(i + 1, r, t - 1));
        }
        memo[key] = result;
        return memo[key];
    };

    return dfs(0, n - 1, k);
}

1278. 分割回文串Ⅲ

在这里插入图片描述

int palindromePartition(string s, int k) {
    int n = s.length();
    unordered_map<int, int> memo;
    // dp[i][j] 表示i-j变成回文串需要修改的最少字符数
    vector<vector<int>> dp(n, vector<int>(n, 0));
    for(int i = n - 1; i >= 0; --i) {
        for(int j = i + 1; j < n; ++j) {
            if(s[i] == s[j]) {
                dp[i][j] = dp[i + 1][j - 1];
            } else {
                dp[i][j] = dp[i + 1][j - 1] + 1;
            }
        }
    }

    function<int(int, int, int)> dfs = [&](int l, int r, int t) {
        int key = l * n * n + r * n + t;
        if(memo.count(key)) return memo[key];
        if(t == 1) {
            return dp[l][r];
        }

        if(r - l + 1 == t) {
            return 0;
        }

        int result = INT_MAX;
        for(int i = l; i <= r - t + 1; ++i) {
            result = min(result, dp[l][i] + dfs(i + 1, r, t - 1));
        }

        memo[key] = result;
        return memo[key];
    };

    return dfs(0, n - 1, k);
}

1.6 树形DP

124. 二叉树中的最大路径和

在这里插入图片描述

int maxPathSum(TreeNode* root) {
    int result = INT_MIN;
    function<int(TreeNode*)> dfs = [&](TreeNode* node) {
        if(!node) {
            return 0;
        }

        int left = max(dfs(node->left), 0);
        int right = max(dfs(node->right), 0);
        int temp = left + right + node->val;
        result = max(result, temp);

        return node->val + max(left, right);
    };

    dfs(root);
    return result;
}

337. 打家劫舍Ⅲ

在这里插入图片描述

int rob(TreeNode* root) {
    unordered_map<TreeNode*, int> memo;
    function<int(TreeNode*)> dfs = [&](TreeNode* node) {
        if(!node) return 0;

        if(memo.count(node)) return memo[node];

        // 1.盗取该节点
        int result1 = node->val;
        if(node->left) {
            result1 += dfs(node->left->left) + dfs(node->left->right);
        }
        if(node->right) {
            result1 += dfs(node->right->left) + dfs(node->right->right);
        }

        // 2.不盗取该节点
        int result2 = 0;
        result2 += dfs(node->left) + dfs(node->right);

        memo[node] = max(result1, result2);
        return memo[node];
    };

    return dfs(root);
}

1.7 位数DP

2376. 统计特殊整数

在这里插入图片描述

int countSpecialNumbers(int n) {
    string strNum = to_string(n);
    vector<vector<int>> memo(strNum.length(), vector<int>(1 << 10, -1));

    // 从curIndex开始填数字,前面填的数字集合是curSet,能构造出的特殊整数的数目
    // isLimited表示前面填的数字是否都是n对应位上的,如果为true,那么当前位至多为s[curIndex],否则位'9'
    // isNum表示前面是否填了数字,如果为false,那么当前位要么不取,要么从1开始取
    function<int(int, int, bool, bool)> dfs = [&](int curIndex, int curSet, bool isLimited, bool isNum) {
        if(curIndex == strNum.size()) {
            if(isNum) return 1;
            return 0;
        }
        if(!isLimited && memo[curIndex][curSet] != -1) return memo[curIndex][curSet];
        
        int result = 0;
        if(!isNum) result += dfs(curIndex + 1, curSet, false, isNum);
        
        int up = isLimited ? strNum[curIndex] - '0' : 9;
        for(int i = isNum ? 0 : 1; i <= up; ++i) {
            if((curSet >> i) & 1) continue;
            result += dfs(curIndex + 1, curSet | (1 << i), isLimited 
                && i == strNum[curIndex] - '0', true);
        }

        if(!isLimited)
            memo[curIndex][curSet] = result;
        return result;
    };

    return dfs(0, 0, true, false);
}

902. 最大为N的数字组合

在这里插入图片描述

int atMostNGivenDigitSet(vector<string>& digits, int n) {
    // int result = 0;
    // long long temp = 10;
    // int i = 0;
    // for(i = 1; temp <= n + 1; ++i) {
    //     result = result + pow(digits.size(), i);
    //     temp *= 10;
    // }
    
    // vector<bool> isExist(10, false);
    // for(int i = 0; i < digits.size(); ++i) {
    //     isExist[digits[i][0] - '0'] = true;
    // }

    // int num = n;
    // bool flag = true;
    // while(num) {
    //     if(isExist[num % 10] == false) {
    //         flag = false;
    //         break;
    //     }
    //     num /= 10;
    // }

    // if(flag) result += 1;


    // // 单独讨论构成与n相同位数的情况
    // function<int(int, int, int)> dfs = [&](int num, long long highest, int bitNum) {
    //     if(num == 0) return 0;
    //     int high = num / highest;
    //     int left = -1;
    //     int right = digits.size();
    //     while(left + 1 != right) {
    //         int mid = left + (right - left) / 2;
    //         if(digits[mid] < to_string(high)) {
    //             left = mid;
    //         } else {
    //             right = mid;
    //         }
    //     }
    //     int resultTemp = 0;
    //     resultTemp += (left + 1) * pow(digits.size(), bitNum - 1);
    //     if(left + 1 < digits.size() && digits[left + 1] == to_string(high))
    //         resultTemp += dfs(num % highest, highest / 10, bitNum - 1);

    //     return resultTemp;
    // };

    // return result + dfs(n, temp / 10, i);

    string strNum = to_string(n);
    vector<int> memo(strNum.size(), -1);
    // 从curIndex开始填数字,使用digits中的数,能构造出的小于等于n的正整数个数
    function<int(int, bool, bool)> dfs = [&](int curIndex, bool isLimited, bool isNum) {
        if(curIndex == strNum.length()) {
            if(isNum) return 1;
            return 0;
        }
        if(!isLimited && isNum && memo[curIndex] != -1) return memo[curIndex];

        int result = 0;
        if(!isNum) result += dfs(curIndex + 1, false, isNum);

        char up = isLimited ? strNum[curIndex] : '9';
        for(int i = 0; i < digits.size(); ++i) {
            if(digits[i][0] > up) break;
            result += dfs(curIndex + 1, isLimited && digits[i][0] == strNum[curIndex], true);
        }

        if(!isLimited && isNum) memo[curIndex] = result;
        return result;
    };

    return dfs(0, true, false);
}

600. 不含连续1的非负整数

在这里插入图片描述

int findIntegers(int n) {
    string strNum = bitset<32>(n).to_string();
    vector<vector<int>> memo(strNum.length(), vector<int>(2, -1));

    function<int(int, int, bool)> dfs = [&](int curIndex, int pre, bool isLimited) {
        if(curIndex == strNum.length()) {
            return 1;
        }

        if(!isLimited && memo[curIndex][pre] > 0) return memo[curIndex][pre];

        int result = 0;
        int up = isLimited ? strNum[curIndex] - '0' : 1;
        for(int i = 0; i <= up; ++i) {
            if(i == 1 && pre == 1) continue;
            result += dfs(curIndex + 1, i, isLimited && i == up);
        }

        if(!isLimited) memo[curIndex][pre] = result;

        return result;      // 这里不能返回memo[curIndex][pre]、
    };

    return dfs(0, 0, true);
}

1012. 至少有一位重复的数字

在这里插入图片描述

int numDupDigitsAtMostN(int n) {
    string strNum = to_string(n);
    vector<vector<int>> memo(strNum.length(), vector<int>(1 << 10, - 1));

    function<int(int, int, bool, bool)> dfs = 
        [&](int curIndex, int curSet, bool isLimited, bool isNum) {
        if(curIndex == strNum.length()) {
            if(isNum) return 1;
            return 0;
        }

        if(!isLimited && isNum && memo[curIndex][curSet] != -1) return memo[curIndex][curSet];

        int result = 0;
        if(!isNum) result += dfs(curIndex + 1, curSet, false, isNum);

        int up = isLimited ? strNum[curIndex] - '0' : 9;

        for(int i = isNum ? 0 : 1; i <= up; ++i) {
            if((curSet >> i) & 1) continue;
            result += dfs(curIndex + 1, curSet | (1 << i), isLimited && i == up, true);
        }

        if(!isLimited && isNum) memo[curIndex][curSet] = result;
        return result;
    };

    return n - dfs(0, 0, true, false);

}

1.8 状态压缩DP

464. 我能赢吗(博弈论)

在这里插入图片描述

bool canIWin(int maxChoosableInteger, int desiredTotal) {
    if((1 + maxChoosableInteger) * maxChoosableInteger / 2 < desiredTotal) {
        return false;
    }

    unordered_map<int, bool> memo;

    // 判断在当前使用集合,当前整数和情况下,先手是否能赢
    function<bool(int, int)> dfs = [&](int curSet, int curSum) {
        if(memo.count(curSet)) return memo[curSet];
        bool result = false;
        for(int i = 1; i <= maxChoosableInteger; ++i) {
            if(((curSet >> i) & 1) == 0) {
                if(i + curSum >= desiredTotal) {
                    result = true;
                    break;
                } else if(!dfs(curSet | (1 << i), curSum + i)) {
                    result = true;
                    break;
                }
            }
        }

        memo[curSet] = result;
        return result;
    };

    return dfs(0, 0);
}

526. 优美的排列

![在这里插入图片描述](https://img-blog.csdnimg.cn/66aaf8464c394a7681793d2b4196d251.png

int countArrangement(int n) {
    unordered_map<int, int> memo;

    function<int(int, int)> dfs = [&](int curSet, int count) {
        if(count == n + 1) {
            return 1;
        }

        if(memo.count(curSet)) return memo[curSet];

        int result = 0;
        for(int i = 1; i <= n; ++i) {
            if((count % i == 0 || i % count == 0) && ((curSet >> i) & 1) == 0) {
                result += dfs(curSet | (1 << i), count + 1);
            }
        }

        memo[curSet] = result;
        return result;

    };

    return dfs(0, 1);
}

1349. 参加考试的最大学生数

1986. 完成任务的最少工作时间段

1.9 计数型DP

96. 不同的二叉搜索树

在这里插入图片描述

int numTrees(int n) {
    unordered_map<int, int> memo;
    function<int(int, int)> dfs = [&](int left, int right) {
        if(left >= right) return 1;
        int key = left * n  + right;
        if(memo.count(key)) return memo[key];

        int result = 0;
        for(int i = left; i <= right; ++i) {
            result += dfs(left, i - 1) * dfs(i + 1, right);
        }

        memo[key] = result;
        return result;
    };

    return dfs(1, n);
}

1.10 递推型DP

957. N天后的牢房

在这里插入图片描述

vector<int> prisonAfterNDays(vector<int>& cells, int n) {
    unordered_map<int, int> memo;
    int state = 0;
    for(int i = 0; i < cells.size(); ++i) {
        if(cells[i] == 1) state |= (1 << i);
    }
    memo[state] = 0;

    bool flag = true;
    for(int i = 0; i < n; ++i) {
        int temp = 0;
        for(int j = 1; j < 7; ++j) {
            if(((state >> (j - 1)) & 1) == ((state >> (j + 1)) & 1)) {
                temp |= (1 << j);
            }
        }

        if(flag) {
            if(memo.count(temp)) {
                int cycle = i + 1 - memo[temp]; // 周期
                int remain = (n - i - 1) % cycle;   // 剩余次数
                i = n - remain - 1;
                flag = false;
            } else {
                memo[temp] = i + 1;
            }
        }
        state = temp;
    }

    vector<int> result;
    for(int i = 0; i < cells.size(); ++i) {
        result.push_back((state >> i) & 1);
    }

    return result;
}

1.11 概率型DP

808. 分汤

在这里插入图片描述

vector<vector<int>> options = {{-4, 0}, {-3, -1}, {-2, -2}, {-1, -3}};

double soupServings(int n) {
    n = (n + 24) / 25;
    if(n > 180) return 1;
    unordered_map<long long, double> memo;

    function<double(int, int)> dfs = [&](int leaveA, int leaveB) {
        double result = 0.0; 
        if(leaveA == 0 && leaveB == 0) {
            return 0.5;
        }

        if(leaveA == 0) {
            return 1.0;
        }

        if(leaveB == 0) {
            return 0.0;
        }

        long long key = (long long)leaveA * n + leaveB;
        if(memo.count(key)) return memo[key];

        for(int i = 0; i < options.size(); ++i) {
            result += 0.25 * dfs(max(leaveA + options[i][0], 0), max(leaveB + options[i][1], 0));
        }

        memo[key] = result;
        return result;

    };
    
    return dfs(n, n);
}

837. 新21点

在这里插入图片描述

double new21Game(int n, int k, int maxPts) {
    // 1. 深度优先+记忆化搜索(超时)
    // unordered_map<int, double> memo;
    // function<double(int)> dfs = [&](int curNum) {
    //     if(curNum >= k) {
    //         if(curNum <= n) {
    //             return 1.0;
    //         } else {
    //             return 0.0;
    //         }
    //     }

    //     if(memo.count(curNum)) return memo[curNum];
    //     double result = 0.0;
    //     for(int i = 1; i <= maxPts; ++i) {
    //         result += 1.0 / maxPts * dfs(curNum + i);
    //     }

    //     memo[curNum] = result;
    //     return result;
    // };

    // return dfs(0);

    // 2. 动态规划(自底向上)
    // dp[i] 表示从i分开始,得分≥k且≤n的概率
    vector<double> dp(n + 1, 0.0);
    if(k == 0) return 1;
    for(int i = k; i <= n; ++i) {
        dp[i] = 1.0;
    }
    
    for(int i = 1; i <= maxPts && (i + k - 1 <= n); ++i) {
        dp[k - 1] += 1.0 / maxPts * dp[k - 1 + i];
    }

    for(int i = k - 2; i >= 0; --i) {
        dp[i] = dp[i + 1] - (i + maxPts + 1 > n ? 0 : dp[i + maxPts + 1] / maxPts) + dp[i + 1] / maxPts;
    }

    return dp[0];
}

1.12 博弈型DP

292. Nim游戏

在这里插入图片描述

bool canWinNim(int n) {
    // 1. 深度优先+记忆化搜索(栈溢出)
    // unordered_map<int, bool> memo;
    // function<bool(int)> dfs = [&](int remain) {
    //     if(remain <= 3) {
    //         return true;
    //     }

    //     if(memo.count(remain)) return memo[remain];

    //     bool result = false;
    //     for(int i = 1; i <= 3; ++i) {
    //         if(!dfs(remain - i)) {
    //             result = true;
    //             break;
    //         }
    //     }

    //     memo[remain] = result;

    //     return result;
    // };

    // return dfs(n);

    // 2. 动态规划
    // vector<bool> dp(n + 1, false);
    // for(int i = 1; i <= 3; ++i) {
    //     dp[i] = true;
    // }

    // for(int i = 4; i <= n; ++i) {
    //     for(int j = 1; j <= 3; ++j) {
    //         dp[i] = dp[i] || !dp[i - j];
    //         if(dp[i]) break;
    //     }
    // }

    // return dp[n];

    if(n % 4 == 0) return false;
    return true;
}

794. 有效的井字游戏

在这里插入图片描述

bool validTicTacToe(vector<string>& board) {
    int row = 3, col = 3;
    int countX = 0, countO = 0;
    for(int i = 0; i < row; ++i) {
        for(int j = 0; j < col; ++j) {
            if(board[i][j] == 'X') {
                ++countX;
            }
            if(board[i][j] == 'O') {
                ++countO;
            }
        }
    }

    if(countX != countO && countX != countO + 1) return false;

    bool flagX = false, flagO = false;
    for(int i = 0; i < row; ++i) {
        if(board[i][0] == board[i][1] && board[i][1] == board[i][2]) {
            if(board[i][0] == 'X') {
                flagX = true;
            } 

            if(board[i][0] == 'O') {
                flagO = true;
            }
        }

        if(board[0][i] == board[1][i] && board[1][i] == board[2][i]) {
            if(board[0][i] == 'X') {
                flagX = true;
            }

            if(board[0][i] == 'O') {
                flagO = true;
            }
        }
    }

    if(board[0][0] == board[1][1] && board[1][1] == board[2][2]) {
        if(board[0][0] == 'X') {
            flagX = true;
        } 

        if(board[0][0] == 'O') {
            flagO = true;
        }
    }

    if(board[0][2] == board[1][1] && board[1][1] == board[2][0]) {
        if(board[0][2] == 'X') {
            flagX = true;
        } 

        if(board[0][2] == 'O') {
            flagO = true;
        }
    }

    if(flagX && flagO) return false;
    if(flagX && countX != countO + 1) return false;
    if(flagO && countO != countX) return false;

    return true;
}

877. 石子游戏

在这里插入图片描述

bool stoneGame(vector<int>& piles) {
    int n = piles.size();
    vector<vector<bool>> dp(n, vector<bool>(n, false));

    for(int i = 0; i < n; ++i) {
        dp[i][i] = true;
        if(i + 1 < n) dp[i][i + 1] = true;
    }
    
    for(int i = n - 1; i >= 0; --i) {
        for(int j = i + 2; j < n; ++j) {
            dp[i][j] = !dp[i + 1][j] || !dp[i][j - 1];
        }
    }

    return dp[0][n - 1];
}

1140. 石子游戏Ⅱ

在这里插入图片描述

int stoneGameII(vector<int>& piles) {
    int sum = 0;
    for(int i = 0; i < piles.size(); ++i) {
        sum += piles[i];
    }
    unordered_map<int, int> memo;

    // 返回该状态下先拿者可以比后拿者最多多的石子数
    function<int(int, int)> dfs = [&](int curIndex, int M) {
        if(curIndex == piles.size()) return 0;
        int temp = 0;
        int result = INT_MIN;
        int key = M * piles.size() + curIndex;
        if(memo.count(key)) return memo[key];

        for(int i = 1; i <= 2 * M; ++i) {
            if(curIndex + i - 1 >= piles.size()) break;
            temp += piles[curIndex + i - 1];
            result = max(result, temp - dfs(curIndex + i, max(M, i)));
        }

        memo[key] = result;
        return result;
    };

    return (dfs(0, 1) + sum) / 2;
}

1690. 石子游戏Ⅶ

在这里插入图片描述

int stoneGameVII(vector<int>& stones) {
    // 1.深度优先+记忆化搜索(超时)
    // int n = stones.size();
    // unordered_map<int, int> memo;
    // // 当前状态下,先取者最多比后取者多的分数
    // function<int(int, int)> dfs = [&](int left, int right) {
    //     if(left == right) return 0;

    //     int key = left * n + right;
    //     if(memo.count(key)) return memo[key];

    //     int result = INT_MIN;
    //     int sum = 0;
    //     for(int i = left; i <= right; ++i) {
    //         sum += stones[i];
    //     }

    //     result = max(result, sum - stones[left] - dfs(left + 1, right));
    //     result = max(result, sum - stones[right] - dfs(left, right - 1));

    //     memo[key] = result;
    //     return result;
    // };

    // return dfs(0, n - 1);

    // 2.动态规划
    int n = stones.size();
    vector<vector<int>> dp(n, vector<int>(n, 0));
    for(int i = n - 1; i >= 0; --i) {
        int sum = stones[i];
        for(int j = i + 1; j < n; ++j) {
            sum += stones[j];
            dp[i][j] = max(sum - stones[i] - dp[i + 1][j], sum - stones[j] - dp[i][j - 1]);
        }
    }

    return dp[0][n - 1];
}

1.13 乘法原理

2147. 分隔长廊的方案数

在这里插入图片描述

int numberOfWays(string corridor) {
    // 深度优先+记忆化搜索(超时)
    // int n = corridor.length();
    // int mod = 1e9 + 7;
    // vector<int> dp(n, -1);
    // function<int(int)> dfs = [&](int curIndex) {
    //     if(curIndex == n) return 1;
    //     if(dp[curIndex] != -1) return dp[curIndex];

    //     int index = curIndex;
    //     int countS = 0;
    //     while(index < n && countS < 2) {
    //         if(corridor[index] == 'S') {
    //             ++countS;
    //         }
    //         ++index;
    //     }

    //     if(countS != 2) {
    //         return 0;
    //     }

    //     int result = 0;
    //     // 在后面添加屏风
    //     int i = index - 1;
    //     do {
    //         result = (result + dfs(i + 1)) % mod;
    //         ++i;
    //     } while (i < n && corridor[i] != 'S');
        
    //     dp[curIndex] = result;
    //     return result;
    // };

    // return dfs(0); 

    int mod = 1e9 + 7;
    vector<int> vec;
    for(int i = 0; i < corridor.length(); ++i) {
        if(corridor[i] == 'S') {
            vec.push_back(i);
        }
    }

    int n = vec.size();
    if(n == 0 || n % 2 != 0) return 0;
    int result = 1;
    for(int i = 1; i < n - 1; i += 2) {
        result = ((long long)result * (vec[i + 1] - vec[i])) % mod;
    }

    return result;
}

1.14 序列DP

446. 等差数列划分Ⅱ-子序列

在这里插入图片描述

int numberOfArithmeticSlices(vector<int>& nums) {
    // 1.深度优先(超时)
    // int result = 0;
    // unordered_map<int, int> memo;
    // function<void(int, vector<int>&)> dfs = [&](int curIndex, vector<int>& temp) {
    //     if(curIndex == nums.size()) {
    //         if(temp.size() >= 3) ++result;
    //         return;
    //     }

    //     int tempSize = temp.size();
    //     if(tempSize <= 1) {
    //         temp.push_back(nums[curIndex]);
    //         dfs(curIndex + 1, temp);
    //         temp.pop_back();
    //         dfs(curIndex + 1, temp);
    //     } else {
    //         if((long long)temp[tempSize - 1] - temp[tempSize - 2] == (long long)nums[curIndex] - temp[tempSize - 1]) {
    //             temp.push_back(nums[curIndex]);
    //             dfs(curIndex + 1, temp);
    //             temp.pop_back();
    //             dfs(curIndex + 1, temp);
    //         } else {
    //             dfs(curIndex + 1, temp);
    //         }
    //     }
    // };

    // function<int(int, int)> dfs = [&](int curIndex, int preNum, long long offset) {
    //     if(curIndex == nu)
    // };
    // vector<int> temp;
    // dfs(0, temp);
    // return result;

    // 2. 动态规划
    int n = nums.size();
    // dp[i]表示以nums[i]结尾的所有等差序列差值以及对应的个数
    vector<unordered_map<long long, int>> dp(n);
    for(int i = 1; i < n; ++i) {
        for(int j = 0; j < i; ++j) {
            long long offset = nums[i] * 1LL - nums[j];
            dp[i][offset] += 1;
            if(dp[j].count(offset)) {
                dp[i][offset] += dp[j][offset];
            }
        }
    }
    int result = 0;
    for(int i = 0; i < n; ++i) {
        for(auto iter : dp[i]) {
            result += iter.second;
        }
    }

    return result - n * (n - 1) / 2;
}

2. 图

无向图的连通性问题需要使用到并查集,有向图通常需要创建邻接矩阵,除此之外,深搜和广搜也是解决图问题的手段【785.判断二分图】。广度优先搜索常常用于解决最短路径问题,深度优先搜索常常由于解决最大值问题【104.二叉树的最大深度】,有一些深度优先题目适合利用一个变量记录计算结果,有一些则适合将计算结果作为返回值传递。

2.1 图的表示

210. 课程表Ⅱ(拓扑排序)

拓扑排序类问题可以使用广度优先搜索或者深度优先搜索完成。
在这里插入图片描述

vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
    // 1. 广度优先搜索
    // ① 通过一个表记录各个节点的入度
    // ② 通过邻接表记录各个节点之间的有向关系
    // ③ 利用广度优先选取入度为0的节点,利用邻接表更新相关节点
    // vector<int> result;
    // vector<vector<int>> coursesMap(numCourses);
    // vector<int> inCount(numCourses, 0);

    // for(int i = 0; i < prerequisites.size(); ++i) {
    //     coursesMap[prerequisites[i][1]].push_back(prerequisites[i][0]);
    //     inCount[prerequisites[i][0]]++;
    // }

    // queue<int> coursesQueue;

    // for(int i = 0; i < numCourses; ++i) {
    //     if(inCount[i] == 0) {
    //         coursesQueue.push(i);
    //     }
    // }

    // while(!coursesQueue.empty()) {
    //     int size = coursesQueue.size();
    //     for(int i = 0; i < size; ++i) {
    //         int course = coursesQueue.front();
    //         coursesQueue.pop();
    //         result.push_back(course);
    //         for(int j = 0; j < coursesMap[course].size(); ++j) {
    //             inCount[coursesMap[course][j]]--;
    //             if(inCount[coursesMap[course][j]] == 0) {
    //                 coursesQueue.push(coursesMap[course][j]);
    //             }
    //         }
    //     }
    // }

    // if(result.size() != numCourses) return {};
    // return result;

    // 深度优先搜索
    vector<int> result;
    vector<vector<int>> courseMap(numCourses);
    vector<int> isVisited(numCourses, 0);
    bool isValid = true;
    for(int i = 0; i < prerequisites.size(); ++i) {
        courseMap[prerequisites[i][0]].push_back(prerequisites[i][1]);
    }

    // 对于存在环的情况,则直接返回
    function<void(int)> dfs = [&](int course) {
        isVisited[course] = 1;
        for(int i = 0; i < courseMap[course].size(); ++i) {
            if(isVisited[courseMap[course][i]] == 0) {
                dfs(courseMap[course][i]);
            } else if(isVisited[courseMap[course][i]] == 1) {
                isValid = false;
                break;
            }
            if(!isValid) return;
        }

        isVisited[course] = 2;
        result.push_back(course);
        return;
    };

    for(int i = 0; i < numCourses; ++i) {
        if(isVisited[i] == 0) {
            dfs(i);
        }
    }

    if(!isValid) return {};

    return result;
}

980. 不同路径Ⅲ

在这里插入图片描述

vector<int> direction = {-1, 0, 1, 0, -1};
int uniquePathsIII(vector<vector<int>>& grid) {
    // 1. 简单回溯
    // int m = grid.size();
    // int n = grid[0].size();
    // int count0 = 0, startX = -1, startY = -1;

    // for(int i = 0; i < m; ++i) {
    //     for(int j = 0; j < n; ++j) {
    //         if(grid[i][j] == 0) {           // 计算出所有要经过点的数目
    //             ++count0;
    //         } else if(grid[i][j] == 1) {
    //             startX = i;
    //             startY = j;
    //         }
    //     }
    // }
    // ++count0;

    // // left:记录剩余空方格的数目
    // function<int(int, int, int)> dfs = [&](int x, int y, int left) {
    //     if(grid[x][y] == 2) {
    //         if(left == 0) {
    //             return 1;
    //         } else {
    //             return 0;
    //         }
    //     }

    //     --left;
    //     grid[x][y] = -1;    // 标记为已经访问过
    //     int result = 0;

    //     for(int i = 0; i < 4; ++i) {
    //         int row = x + direction[i];
    //         int col = y + direction[i + 1];
    //         if(row >= 0 && row < m && col >= 0 && col < n && 
    //             (grid[row][col] == 0 || grid[row][col] == 2)) {
    //             result += dfs(row, col, left);
    //         }
    //     }

    //     grid[x][y] = 0;
        
    //     return result;
    // };

    // return dfs(startX, startY, count0);

    // 动态规划+记忆化搜索
    // 即利用一个memo的哈希表记录已经计算过的每一种状态下的结果,后续遇到该状态直接返回结果
    int m = grid.size();
    int n = grid[0].size();
    unordered_map<int, int> memo;
    int isVisited = 0, startX = 0, startY = 0;
    for(int i = 0; i < m; ++i) {
        for(int j = 0; j < n; ++j) {
            if(grid[i][j] == -1) {
                isVisited |= 1 << (i * n + j);  // 记录所有已经访问过的点
            } else if(grid[i][j] == 1) {
                startX = i;
                startY = j;
            }
        }
    }

    int all = (1 << (m * n)) - 1;

    function<int(int, int, int)> dfs = [&](int x, int y, int isVisit) {
        int p = x * n + y;
        isVisit |= 1 << p;
        if(grid[x][y] == 2) {
            if(isVisit == all) {
                return 1;
            } else {
                return 0;
            }
        }
        int key = (p << (m * n)) | isVisit;
        if(memo.count(key)) return memo[key];
        int result = 0;
        for(int i = 0; i < 4; ++i) {
            int row = x + direction[i];
            int col = y + direction[i + 1];
            int pTemp = row * n + col;
            if(row >= 0 && row < m && col >= 0 && col < n && !(isVisit >> pTemp & 1)
                && (grid[row][col] == 0 || grid[row][col] == 2)) {
                result += dfs(row, col, isVisit);
            }
        }
        memo[key] = result;

        return memo[key];

    };

    return dfs(startX, startY, isVisited);
}

1128. 等价多米诺骨牌对的数量(组合问题)

在这里插入图片描述

int numEquivDominoPairs(vector<vector<int>>& dominoes) {
    vector<int> num(100);
    int ret = 0;
    for (auto& it : dominoes) {
        int val = it[0] < it[1] ? it[0] * 10 + it[1] : it[1] * 10 + it[0];
        ret += num[val];
        num[val]++;
    }
    return ret;
}

133. 克隆图

在这里插入图片描述

Node* cloneGraph(Node* node) {
    unordered_map<int, Node*> nodeMap;

    function<Node*(Node*)> dfs = [&](Node* node) -> Node* {
        if(!node) return nullptr;
        Node* result = new Node(node->val);
        nodeMap[result->val] = result;
        result->neighbors.resize(node->neighbors.size());
        for(int i = 0; i < node->neighbors.size(); ++i) {
            if(nodeMap.count(node->neighbors[i]->val) == 0) {
                result->neighbors[i] = dfs(node->neighbors[i]);
            } else {
                result->neighbors[i] = nodeMap[node->neighbors[i]->val];
            }
        }
        return result;
    };

    return dfs(node);
}

2.2 广度优先搜索

127. 单词接龙(双向广度优先搜索)

在这里插入图片描述

int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
    // 1. 单向广度优先搜索
    // unordered_map<string, bool> wordMap;

    // for(int i = 0; i < wordList.size(); ++i) {
    //     wordMap[wordList[i]] = false;
    // }

    // if(wordMap.find(endWord) == wordMap.end()) {
    //     return 0;
    // }

    // queue<string> wordQueue;
    // wordQueue.push(beginWord);

    // int result = 0;
    // while (!wordQueue.empty()) {
    //     result++;
    //     int queueSize = wordQueue.size();
    //     for (int i = 0; i < queueSize; ++i) {
    //         string word = wordQueue.front();
    //         wordQueue.pop();
    //         for (int j = 0; j < word.length(); ++j) {
    //             string temp = word;
    //             for (char w = 'a'; w <= 'z'; ++w) {
    //                 temp[j] = w;
    //                 auto iter = wordMap.find(temp);
    //                 if (iter == wordMap.end() || iter->second == true) {
    //                     continue;
    //                 }

    //                 if (temp == endWord) return result + 1;

    //                 wordQueue.push(temp);
    //                 wordMap[temp] = true;
    //             }
    //         }

    //     }
    // }


    // 2. 双向广度优先搜索
    unordered_map<string, bool> wordMap;
    for(int i = 0; i < wordList.size(); ++i) {
        wordMap[wordList[i]] = false;
    }

    if(wordMap.find(endWord) == wordMap.end()) {
        return 0;
    }

    unordered_set<string> beginSet;
    unordered_set<string> endSet;

    beginSet.insert(beginWord);
    endSet.insert(endWord);

    int result = 0;
    while(!beginSet.empty() && !endSet.empty()) {
        ++result;
        if(beginSet.size() > endSet.size()) {
            swap(beginSet, endSet);
        }

        unordered_set<string> newSet;
        for(auto word : beginSet) {
            for(int i = 0; i < word.length(); ++i) {
                string temp = word;
                for(char w = 'a'; w <= 'z'; ++w) {
                    temp[i] = w;
                    auto iter = wordMap.find(temp);
                    
                    if(endSet.find(temp) != endSet.end()) {
                        return result + 1;
                    }

                    if(iter == wordMap.end() || iter->second == true) {
                        continue;
                    }

                    newSet.insert(temp);
                    wordMap[temp] = true;
                }
            }
        }

        beginSet = newSet;
    }

    return 0;
}

126. 单词接龙Ⅱ

在这里插入图片描述

vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
    vector<vector<string>> result;
    unordered_set<string> wordSet;
    for(int i = 0; i < wordList.size(); ++i) {
        wordSet.insert(wordList[i]);
    }

    if(wordSet.find(endWord) == wordSet.end()) return {};

    wordSet.erase(beginWord);

    unordered_map<string, int> stepMap;
    unordered_map<string, unordered_set<string>> from;
    queue<string> wordQueue;
    wordQueue.push(beginWord);

    int layer = 0;
    bool found = false;

    while(!wordQueue.empty()) {
        ++layer;
        int curSize = wordQueue.size();
        for(int i = 0; i < curSize; ++i) {
            string word = wordQueue.front();
            wordQueue.pop();
            for(int j = 0; j < word.length(); ++j) {
                string temp = word;
                for(char w = 'a'; w <= 'z'; ++w) {
                    temp[j] = w;

                    if(stepMap[temp] == layer) { // 可能存在一个访问过的
                        from[temp].insert(word);
                    }
                    if(wordSet.find(temp) == wordSet.end()) {
                        continue;
                    }

                    from[temp].insert(word);
                    wordSet.erase(temp);
                    stepMap[temp] = layer;
                    wordQueue.push(temp);
                    if(temp == endWord) {
                        found = true;
                    }
                }
            }
        }
        if(found) {
            break;
        }
    }

    if(found) {
        vector<string> temp = {endWord};
        backTrack(from, result, endWord, temp);
    }

    return result;
}

void backTrack(unordered_map<string, unordered_set<string>>& from, vector<vector<string>>& result, string& node, vector<string>& temp) {
    if(from[node].empty()) {
        result.push_back({temp.rbegin(), temp.rend()});
        return;
    }

    for(auto parentNode : from[node]) {
        temp.push_back(parentNode);
        backTrack(from, result, parentNode, temp);
        temp.pop_back();
    }
}

113. 路径总和Ⅱ

在这里插入图片描述

vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
    // 1. 深度优先搜索
    // vector<vector<int>> result;
    // vector<int> temp;

    // dfs(root, targetSum, result, temp, 0);

    // return result;


    // 2. 广度优先搜索
    if(!root) return {};
    vector<vector<int>> result;
    unordered_map<TreeNode*, TreeNode*> parent;
    getParent(root, parent);

    auto getPath = [&](TreeNode* node, vector<int>& temp) {
        while(parent.find(node) != parent.end()) {
            temp.push_back(node->val);
            node = parent[node];
        }

        temp.push_back(node->val);
    };

    queue<TreeNode*> treeQueue;
    queue<int> sumQueue;
    treeQueue.push(root);
    sumQueue.push(0);

    while(!treeQueue.empty()) {
        int size = treeQueue.size();
        for(int i = 0; i < size; ++i) {
            TreeNode* node = treeQueue.front();
            treeQueue.pop();
            int curSum = sumQueue.front() + node->val;
            sumQueue.pop();
            if(!node->left && !node->right) {
                if(curSum == targetSum) {
                    vector<int> temp;
                    getPath(node, temp);
                    result.push_back({temp.rbegin(), temp.rend()});
                }
            } else {
                if(node->left) {
                    treeQueue.push(node->left);
                    sumQueue.push(curSum);
                }
                if(node->right) {
                    treeQueue.push(node->right);
                    sumQueue.push(curSum);
                }
            }

        }
    }

    return result;

    
}

void dfs(TreeNode* node, int targetSum, vector<vector<int>>& result, vector<int>& temp, int sum) {
    if(!node) return;
    sum += node->val;
    temp.push_back(node->val);

    if(!node->left && !node->right && sum == targetSum) {
        result.push_back(temp);
    }

    dfs(node->left, targetSum, result, temp, sum);
    dfs(node->right, targetSum, result, temp, sum);

    temp.pop_back();

}


void getParent(TreeNode* node, unordered_map<TreeNode*, TreeNode*>& parent) {
    if(!node) return;

    if(node->left) {
        parent[node->left] = node;
        getParent(node->left, parent);
    }
    if(node->right) {
        parent[node->right] = node;
        getParent(node->right, parent);
    }
}

2.3 深度优先搜索

463. 岛屿的周长

在这里插入图片描述

vector<int> direction = {-1, 0, 1, 0, -1};
int islandPerimeter(vector<vector<int>>& grid) {
    int m = grid.size();
    int n = grid[0].size();

    function<int(int, int)> dfs = [&](int x, int y) {
        if (x < 0 || x >= m || y < 0 || y >= n || grid[x][y] == 0) {
            return 1;
        }
        if(grid[x][y] == 2) return 0;

        grid[x][y] = 2;
        int res = 0;
        for(int i = 0; i < 4; ++i) {
            int row = x + direction[i];
            int col = y + direction[i + 1];
            res += dfs(row, col);
        }
        return res;

    };


    for(int i = 0; i < m; ++i) {
        for(int j = 0; j < n; ++j) {
            if(grid[i][j] == 1) {
                return dfs(i ,j);
            }
        }
    }

    return 0;
}

827. 最大人工岛

在这里插入图片描述

vector<int> direction = {-1, 0, 1, 0, -1};
int largestIsland(vector<vector<int>>& grid) {
    int n = grid.size();
    vector<int> fa(n * n);
    vector<int> sz(n * n, 1);
    iota(fa.begin(), fa.end(), 0);
    int result = 0;

    function<int(int)> find = [&](int x) {
        return x == fa[x] ? x : fa[x] = find(fa[x]);
    };

    auto merge = [&](int from, int to) {
        from = find(from);
        to = find(to);
        if(from != to) {
            fa[from] = to;
            sz[to] = sz[to] + sz[from];
        }
    };

    for(int i = 0; i < n; ++i) {
        for(int j = 0; j < n; ++j) {
            if(grid[i][j] == 1) {
                for(int k = 0; k < 4; ++k) {
                    int row = i + direction[k];
                    int col = j + direction[k + 1];
                    if(row >= 0 && row < n && col >= 0 && col < n && grid[row][col] == 1){
                        merge(i * n + j, row * n + col);
                    }
                }
            }
        }
    }

    for(int i = 0; i < n; ++i) {
        for(int j = 0; j < n; ++j) {
            if(grid[i][j] == 1) {
                result = max(result, sz[find(i * n + j)]);
            } else {
                int tempArea = 1;
                unordered_set<int> isConnected;
                for(int k = 0; k < 4; ++k) {
                    int row = i + direction[k];
                    int col = j + direction[k + 1];
                    if(row >= 0 && row < n && col >= 0 && col < n && grid[row][col] == 1 
                        && isConnected.count(find(row * n + col)) == 0) {
                        tempArea += sz[find(row * n + col)];
                        isConnected.insert(find(row * n + col));
                    }
                }

                result = max(result, tempArea);
            }
        }
    }

    return result;
}

226. 翻转二叉树

在这里插入图片描述

TreeNode* invertTree(TreeNode* root) {
    dfs(root);

    return root;
}


void dfs(TreeNode* node) {
    if(!node) return;

    TreeNode* temp = node->left;
    node->left = node->right;
    node->right = temp;
    dfs(node->left);
    dfs(node->right);
}

419. 甲板上的战舰

在这里插入图片描述

int countBattleships(vector<vector<char>>& board) {
    int m = board.size();
    int n = board[0].size();

    std::function<void(int, int)> dfsRight = [&](int x, int y)
    {
        if(x >= m || board[x][y] == '.') return;
        board[x][y] = '.';
        dfsRight(x + 1, y);
    };

    std::function<void(int, int)> dfsBottom = [&](int x, int y)
    {
        if(y >= n || board[x][y] == '.') return;
        board[x][y] = '.';
        dfsBottom(x, y + 1);
    };

    int res = 0;
    for(int i = 0; i < m; ++i)
    {
        for(int j = 0; j < n; ++j)
        {
            if(board[i][j] == 'X')
            {
                dfsRight(i + 1, j);
                dfsBottom(i, j + 1);
                ++res;
                board[i][j] = '.';
            }
        }
    }
    return res;
}

2.4 最小生成树问题

1584. 连接所有点的最小费用

在这里插入图片描述

class DisjointSetUnion {
private:
    vector<int> fa, rank;
public:
    DisjointSetUnion(int n) {
        fa.resize(n);
        rank.resize(n, 1);
        iota(fa.begin(), fa.end(), 0);
    }

    int find(int x) {
        return x == fa[x] ? x : fa[x] = find(fa[x]);
    }

    bool merge(int a, int b) {
        a = find(a);
        b = find(b);

        if(a != b) {
            if(rank[a] <= rank[b]) {
                fa[a] = b;
            } else {
                fa[b] = a;
            }
            if(rank[a] == rank[b]) {
                rank[b]++;
            }
            return true;
        }
        return false;
    }
};

struct Edge {
    int len, a, b;       
};

int minCostConnectPoints(vector<vector<int>>& points) {
    vector<Edge> edges;
    DisjointSetUnion dsu(points.size());

    auto getDistance = [&](int i, int j) {
        return abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1]);
    };

    for(int i = 0; i < points.size(); ++i) {
        for(int j = i + 1; j < points.size(); ++j) {
            edges.push_back({getDistance(i, j), i, j});
        }
    }

    sort(edges.begin(), edges.end(), [](Edge& a, Edge& b) {
        return a.len < b.len;
    });

    int result = 0;
    int num = 0;

    for(int i = 0; i < edges.size(); ++i) {
        if(dsu.merge(edges[i].a, edges[i].b)) {
            result += edges[i].len;
            num++;
        }
        if(num == points.size() - 1) {
            break;
        }
    }

    return result;
}

1131. 绝对值表达式的最大值

在这里插入图片描述

int maxAbsValExpr(vector<int>& arr1, vector<int>& arr2) {
    int max1 = INT_MIN, max2 = INT_MIN, max3 = INT_MIN, max4 = INT_MIN;
    int min1 = INT_MAX, min2 = INT_MAX, min3 = INT_MAX, min4 = INT_MAX;
    for(int i = 0; i < arr1.size(); ++i) {
        max1 = max(max1, arr1[i] + arr2[i] + i);
        min1 = min(min1, arr1[i] + arr2[i] + i);
        max2 = max(max2, arr1[i] + arr2[i] - i);
        min2 = min(min2, arr1[i] + arr2[i] - i);
        max3 = max(max3, arr1[i] - arr2[i] + i);
        min3 = min(min3, arr1[i] - arr2[i] + i);
        max4 = max(max4, arr1[i] - arr2[i] - i);
        min4 = min(min4, arr1[i] - arr2[i] - i);
    }

    return max(max(max1 - min1, max2 - min2), max(max3 - min3, max4 - min4));
}

1631. 最小体力消耗路径*

在这里插入图片描述

class DisjointSetUnion {
private:
    vector<int> fa;
    vector<int> rank;

public:
    DisjointSetUnion(int n) {
        fa.resize(n);
        rank.resize(n, 1);
        iota(fa.begin(), fa.end(), 0);
    }

    int find(int x) {
        return x == fa[x] ? x : fa[x] = find(fa[x]);
    }

    void merge(int a, int b) {
        a = find(a);
        b = find(b);
        if(a != b) {
            if(rank[a] <= rank[b]) {
                fa[a] = b;
            } else {
                fa[b] = a;
            }

            if(rank[a] == rank[b]) {
                rank[b]++;
            }
        }
    }

    bool isConnected(int a, int b) {
        a = find(a);
        b = find(b);
        if(a != b)  {
            return false;
        }
        return true;
    }
};

struct Edge {
    int weight, x, y;
};

int minimumEffortPath(vector<vector<int>>& heights) {
    int m = heights.size();
    int n = heights[0].size();
    vector<Edge> edges;
    DisjointSetUnion dsu(m * n);

    for(int i = 0; i < m; ++i) {
        for(int j = 0; j < n; ++j) {
            if(i > 0) {
                edges.push_back({abs(heights[i][j] - heights[i - 1][j]), (i - 1) * n + j, i * n + j});
            }

            if(j > 0) {
                edges.push_back({abs(heights[i][j] - heights[i][j - 1]), i * n + j - 1, i * n + j});
            }
        }
    }

    sort(edges.begin(), edges.end(), [](Edge& a, Edge& b) {
        return a.weight < b.weight;
    });

    for(int i = 0; i < edges.size(); ++i) {
        dsu.merge(edges[i].x, edges[i].y);
        if(dsu.isConnected(0, m * n - 1)) {
            return edges[i].weight;
        }
    }

    return 0;
}

1489. 找到最小生成树里的关键边和伪关键边

在这里插入图片描述

class DisjointSetUnion {
private:
    vector<int> fa;
    vector<int> rank;
public:
    int connectCount;

    DisjointSetUnion(int n)
        : connectCount(n)
    {
        fa.resize(n);
        rank.resize(n, 1);
        iota(fa.begin(), fa.end(), 0);
    }

    int find(int x) {
        return x == fa[x] ? x : fa[x] = find(fa[x]);
    }

    void merge(int a, int b) {
        a = find(a);
        b = find(b);
        if(a != b) {
            if(rank[a] <= rank[b]) {
                fa[a] = b;
            } else { 
                fa[b] = a;
            }

            if(rank[a] == rank[b]) {
                ++rank[b];
            }
            connectCount--;
        }
    }

    bool isConnected(int a, int b) {
        a = find(a);
        b = find(b);
        if(a != b) {
            return false;
        }
        return true;
    }
};

vector<vector<int>> findCriticalAndPseudoCriticalEdges(int n, vector<vector<int>>& edges) {
    vector<vector<int>> result(2);
    DisjointSetUnion dsu(n);

    for(int i = 0; i < edges.size(); ++i) {
        edges[i].push_back(i);
    }

    sort(edges.begin(), edges.end(), [](const vector<int>& a, const vector<int>& b) {
        return a[2] < b[2];
    });

    int value = 0;
    for(int i = 0; i < edges.size(); ++i) {
        if(!dsu.isConnected(edges[i][0], edges[i][1])) {
            dsu.merge(edges[i][0], edges[i][1]);
            value += edges[i][2];
        }
    }

    for(int i = 0; i < edges.size(); ++i) {
        DisjointSetUnion dsuTempKey(n);
        int valueTemp = 0;
        // 查找关键边
        for(int j = 0; j < edges.size(); ++j) {
            if(i != j && !dsuTempKey.isConnected(edges[j][0], edges[j][1])) {
                dsuTempKey.merge(edges[j][0], edges[j][1]);
                valueTemp += edges[j][2];
            }
        }
        if(dsuTempKey.connectCount != 1 || (dsuTempKey.connectCount == 1 && valueTemp > value)) {
            result[0].push_back(edges[i][3]);
            continue;
        }

        // 查找伪关键边
        DisjointSetUnion dsuTempNoKey(n);
        valueTemp = edges[i][2];
        dsuTempNoKey.merge(edges[i][0], edges[i][1]);
        for(int j = 0; j < edges.size(); ++j) {
            if(i != j && !dsuTempNoKey.isConnected(edges[j][0], edges[j][1])) {
                dsuTempNoKey.merge(edges[j][0], edges[j][1]);
                valueTemp += edges[j][2];
            }
        }
        if(valueTemp == value) {
            result[1].push_back(edges[i][3]);
        }
    }

    return result;
}

2.5 最短路径问题

743. 网络延迟问题(Dijkstra算法)

在这里插入图片描述

int networkDelayTime(vector<vector<int>>& times, int n, int k) {
    // 1. 优化Dijkstra算法
    // vector<vector<pair<int, int>>> distMatrix(n);
    // for(int i = 0; i < times.size(); ++i) {
    //     int u = times[i][0] - 1, v = times[i][1] - 1, distance = times[i][2];
    //     distMatrix[u].push_back({v, distance});
    // }

    // vector<int> dist(n, INT_MAX / 2);
    // Dijkstra(distMatrix, k - 1, dist);

    // int result = INT_MIN;
    // for(int i = 0; i < n; ++i) {
    //     result = max(result, dist[i]);
    // }

    // return result == INT_MAX / 2 ? -1 : result;

    // 2. 朴素Dijkstra算法
    // 创建邻接矩阵
    vector<vector<int>> distMatrix(n, vector<int>(n, INT_MAX));

    for(int i = 0; i < times.size(); ++i) {
        distMatrix[times[i][0] - 1][times[i][1] - 1] = times[i][2];
    }
    vector<int> distVec;
    Dijkstra(distMatrix, k - 1, distVec);

    int result = INT_MIN;
    for(int i = 0; i < distVec.size(); ++i) {
        result = max(result, distVec[i]);
    }

    return result == INT_MAX ? -1 : result;
}

787. K站中转内最便宜的航班

在这里插入图片描述

int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
    vector<vector<int>> swapVec(2, vector<int>(n, INT_MAX));
    swapVec[0][src] = 0;
    swapVec[1][src] = 0;
    int pre = 0;

    for(int i = 0; i < k + 1; ++i) {
        int cur = 1 - pre;
        for(int j = 0; j < flights.size(); ++j) {
            if((long long)swapVec[pre][flights[j][0]] + flights[j][2] < swapVec[cur][flights[j][1]]) {
                swapVec[cur][flights[j][1]] = swapVec[pre][flights[j][0]] + flights[j][2];
            }
        }
        pre = 1 - pre;
    }

    if(swapVec[pre][dst] != INT_MAX) {
        return swapVec[pre][dst];
    }
    return -1;
}

882. 细分图中可到达节点

在这里插入图片描述

int reachableNodes(vector<vector<int>>& edges, int maxMoves, int n) {
    vector<vector<pair<int, int>>> distMatrix(n);
    for(int i = 0; i < edges.size(); ++i) {
        int u = edges[i][0], v = edges[i][1], distance = edges[i][2] + 1;
        distMatrix[u].push_back({v, distance});
        distMatrix[v].push_back({u, distance});
    }

    vector<int> dist(n, INT_MAX);

    dijkstra(distMatrix, 0, dist);

    int result = 0;
    for(int i = 0; i < n; ++i) {
        if(dist[i] <= maxMoves) {
            ++result;
        }
    }

    for(int i = 0; i < edges.size(); ++i) {
        int u = edges[i][0], v = edges[i][1], node = edges[i][2];
        int a = 0, b = 0;
        if(dist[u] <= maxMoves) {
            a = maxMoves - dist[u];
        }

        if(dist[v] <= maxMoves) {
            b = maxMoves - dist[v];
        }

        result += min(node, a + b);
    }

    return result;
}

2.6 图的连通性

785. 判断二分图

在这里插入图片描述

bool isBipartite(vector<vector<int>>& graph) {
    vector<int> isColored(graph.size(), 0);

    function<bool(int, int)> dfs = [&](int curNode, int color) {
        if(isColored[curNode] != 0) {
            if(isColored[curNode] != color) {
                return false;
            } else {
                return true;
            }
        }

        bool result = true;
        isColored[curNode] = color;
        color = color == 1 ? 2 : 1;

        for(int i = 0; i < graph[curNode].size(); ++i) {
            result = dfs(graph[curNode][i], color);
            if(result == false) {
                return false;
            }
        }

        return true;
    };

    for(int i = 0; i < graph.size(); ++i) {
        if(isColored[i] == 0) {
            bool result = dfs(i, 1);
            if(result == false) {
                return false;
            }
        }
    }

    return true;
}

886. 可能的二分法

在这里插入图片描述

bool possibleBipartition(int n, vector<vector<int>>& dislikes) {
    vector<int> isColored(n, 0);
    vector<vector<int>> distMatrix(n);
    for(int i = 0; i < dislikes.size(); ++i) {
        distMatrix[dislikes[i][0] - 1].push_back(dislikes[i][1] - 1);
        distMatrix[dislikes[i][1] - 1].push_back(dislikes[i][0] - 1);
    }

    function<bool(int, int)> dfs = [&](int cur, int color) {
        if(isColored[cur] != 0) {
            if(isColored[cur] == color) {
                return true;
            } else {
                return false;
            }
        }

        bool result = true;
        isColored[cur] = color;
        color = color == 1 ? 2 : 1;
        for(int i = 0; i < distMatrix[cur].size(); ++i) {
            result = dfs(distMatrix[cur][i], color);
            if(!result) return false;
        }   

        return true;
    };

    for(int i = 0; i < n; ++i) {
        if(isColored[i] == 0) {
            bool result = dfs(i, 1);
            if(!result) return false;
        }
    }

    return true;
}

959. 由斜杠划分区域*

在这里插入图片描述

int regionsBySlashes(vector<string>& grid) {
    int n = grid.size();
    int nn4 = 4 * n * n;
    DisjointSetUnion dsu(nn4);

    // top-0 left-1 right-2 bottom-3
    for(int i = 0; i < grid.size(); ++i) {
        for(int j = 0; j < n; ++j) {
            if (grid[i][j] == ' ') {
                dsu.merge(4 * (i * n + j) + 0, 4 * (i * n + j) + 1);
                dsu.merge(4 * (i * n + j) + 2, 4 * (i * n + j) + 3);
                dsu.merge(4 * (i * n + j) + 1, 4 * (i * n + j) + 2);
            }
            else if (grid[i][j] == '\\') {
                dsu.merge(4 * (i * n + j) + 0, 4 * (i * n + j) + 2);
                dsu.merge(4 * (i * n + j) + 1, 4 * (i * n + j) + 3);
            }
            else if (grid[i][j] == '/') {
                dsu.merge(4 * (i * n + j) + 0, 4 * (i * n + j) + 1);
                dsu.merge(4 * (i * n + j) + 2, 4 * (i * n + j) + 3);
            }

            if (i > 0) {
                dsu.merge(4 * ((i - 1) * n + j) + 3, 4 * (i * n + j) + 0);
            }
            if (j > 0) {
                dsu.merge(4 * (i * n + j) + 1, 4 * (i * n + j - 1) + 2);
            }
        }
    }

    return dsu.connectCount;
}

3. 滑动窗口

滑动窗口问题有时候需要维持一个单调双端队列【239.滑动窗口最大值

3.1 固定窗口大小的滑动窗口问题

76. 最小覆盖字串

在这里插入图片描述

string minWindow(string s, string t) {
    string result;
    int len = s.length() + 1;
    unordered_map<char, int> tMap;
    // 记录t的所有字符以及出现次数
    for(int i = 0; i < t.length(); ++i) {
        tMap[t[i]]++;
    }

    int l = 0;
    int r;
    for(r = 0; r < s.length(); ++r){
        if(tMap.find(s[r]) == tMap.end()) {
            continue;
        }
        tMap[s[r]]--;
        bool flag = true;
        for(auto iter : tMap) {
            if(iter.second > 0) {
                flag = false;   // 右边已经包括所有字符
            }
        }
        
        if(flag) {
            while(l < r && (tMap.find(s[l]) == tMap.end() || ++tMap[s[l]] <= 0)) {
                ++l;
            }

            int lenTemp = r - l + 1;

            if(lenTemp < len) {
                len = lenTemp;
                result = s.substr(l, len);
            }
            ++l;
        }

    }

    return result;
}

3. 无重复字符的最长子串

在这里插入图片描述

int lengthOfLongestSubstring(string s) {
    unordered_set<char> strSet;
    int result = 0;
    int r = 0, l = 0;
    for(r = 0; r < s.length(); ++r) {
        if(strSet.count(s[r])) {
            while(s[l] != s[r]) {
                strSet.erase(s[l]);
                ++l;
            }
            ++l;
        } else {
            result = max(result, r - l + 1);
            strSet.insert(s[r]);
        }
    }

    return result;
}

567. 字符串的排列

在这里插入图片描述

bool checkInclusion(string s1, string s2) {
    unordered_map<char, int> strMap;
    for(int i = 0; i < s1.length(); ++i) {
        strMap[s1[i]]++;
    }
    int lengthS1 = s1.length();
    int r = lengthS1 - 1;
    while(r < s2.length()) {
        if(strMap.count(s2[r]) == 0) {
            r += lengthS1;
        } else {
            auto temp = strMap;
            bool flag = true;
            for(int i = r - lengthS1 + 1; i <= r; ++i) {
                temp[s2[i]]--;
                if(temp[s2[i]] < 0) {
                    flag = false;
                    break;
                }
            }
            if(flag) return true;
            ++r;
        } 
    }

    return false;
}

438. 找到字符串中所有字母异位词

在这里插入图片描述

vector<int> findAnagrams(string s, string p) {
    int sLen = s.size(), pLen = p.size();

    if (sLen < pLen) {
        return vector<int>();
    }

    vector<int> ans;
    vector<int> sCount(26);
    vector<int> pCount(26);
    for (int i = 0; i < pLen; ++i) {
        ++sCount[s[i] - 'a'];
        ++pCount[p[i] - 'a'];
    }

    if (sCount == pCount) {
        ans.emplace_back(0);
    }

    for (int i = 0; i < sLen - pLen; ++i) {
        --sCount[s[i] - 'a'];
        ++sCount[s[i + pLen] - 'a'];

        if (sCount == pCount) {
            ans.emplace_back(i + 1);
        }
    }

    return ans;
}

1763. 最长的美好子字符串*

718. 最长重复子数组

在这里插入图片描述

int findLength(vector<int>& nums1, vector<int>& nums2) {
    int n = nums1.size();
    int m = nums2.size();
    int result = 0;
    for(int i = 0; i < n; ++i) {
        int len = min(n - i, m);
        int maxLen = maxLength(nums1, nums2, i, 0, len);
        result = max(result, maxLen);
    }

    for(int j = 0; j < m; ++j) {
        int len = min(m - j, n);
        int maxLen = maxLength(nums1, nums2, 0, j, len);
        result = max(result, maxLen);
    }

    return result;
}

int maxLength(vector<int>& a, vector<int>& b, int addA, int addB, int len) {
    int result = 0, k = 0;
    for(int i = 0; i < len; ++i) {
        if(a[addA + i] == b[addB + i]) {
            k++;
        } else {
            k = 0;
        }

        result = max(result, k);
    }

    return result;
}

239. 滑动窗口最大值

在这里插入图片描述

vector<int> maxSlidingWindow(vector<int>& nums, int k) {
    vector<int> result;
    deque<int> maxQueue;
    for(int i = 0; i < nums.size(); ++i) {
        while(!maxQueue.empty() && nums[i] > nums[maxQueue.back()]) {
            maxQueue.pop_back();
        }

        maxQueue.push_back(i);

        while(!maxQueue.empty() && i - maxQueue.front() + 1 > k) {
            maxQueue.pop_front();
        }

        if(i >= k - 1) {
            result.push_back(nums[maxQueue.front()]);
        }
    }

    return result;
}

480. 滑动窗口中位数

在这里插入图片描述

// 该类拥有三个接口
// 1. 插入一个元素
// 2. 删除一个元素
// 3. 求所有元素的中位数
class DualHeap {
public:
    void insert(int num) {
        if(small.empty() || num <= small.top()) {
            small.push(num);
            ++smallSize;
        } else {
            large.push(num);
            ++largeSize;
        }
        makeBalance();
    }
    void erase(int num) {
        ++eraseNumMap[num];
        if(num <= small.top()) {
            --smallSize;
            if(num == small.top()) {
                prune(small);
            }
        } else {
            --largeSize;
            if (num == large.top()) {
                prune(large);
            }
        }
        makeBalance();
    }
    double getMedian() {
        if(largeSize < smallSize) return small.top();
        else return ((double)large.top() + small.top()) / 2.0;
    }

private:
    priority_queue<int> small;                              // 存放较小的一半数,大根堆
    priority_queue<int, vector<int>, greater<int>> large;   // 存放较大的一半数,小根堆
    unordered_map<int, int> eraseNumMap;                    // 记录待删除元素,延迟删除
    int smallSize = 0, largeSize = 0;                               // 小根堆和大根堆的实际元素个数

    // 调整小根堆和大根堆的元素个数,使其满足个数要求
    void makeBalance() {
        if(smallSize > largeSize + 1) {
            // small比large元素多2个
            large.push(small.top());
            small.pop();
            --smallSize;
            ++largeSize;
            prune(small);
        } else if (smallSize < largeSize) {
            // large 比 small 元素多 1 个
            small.push(large.top());
            large.pop();
            ++smallSize;
            --largeSize;
            // large 堆顶元素被移除,需要进行 prune
            prune(large);
        }
    }

    // 判断该堆的根元素是否需要被移除
    template<typename T>
    void prune(T& heap) {
        while(!eraseNumMap.empty() && eraseNumMap.find(heap.top()) != eraseNumMap.end()) {
            eraseNumMap[heap.top()]--;
            if(eraseNumMap[heap.top()] == 0) {
                eraseNumMap.erase(heap.top());
            }
            heap.pop();
        }
    }
};


vector<double> medianSlidingWindow(vector<int>& nums, int k) {
    DualHeap dh;
    vector<double> result;
    for(int i = 0; i < k; ++i) {
        dh.insert(nums[i]);
    }
    result.push_back(dh.getMedian());

    for(int i = k; i < nums.size(); ++i) {
        dh.insert(nums[i]);
        dh.erase(nums[i - k]);
        result.push_back(dh.getMedian());
    }

    return result;
}

128. 最长连续序列

在这里插入图片描述

int longestConsecutive(vector<int>& nums) {
    unordered_map<int, bool> numsMap;
    int result = 0;
    for(auto num : nums) {
        numsMap[num] = false;
    }

    for(auto numsOne : numsMap) {
        if(numsOne.second) {
            continue;
        }

        int pre = numsOne.first - 1;
        int next = numsOne.first + 1;

        while(numsMap.count(pre) && numsMap[pre] == false) {
            numsMap[pre] = true;
            --pre;
        }

        while(numsMap.count(next) && numsMap[next] == false) {
            numsMap[next] = true;
            ++next;
        }

        result = max(result, next - pre - 1);

    }

    return result;
}

3.2 不固定窗口大小的滑动窗口问题

209. 长度最小的子数组

在这里插入图片描述

int minSubArrayLen(int target, vector<int>& nums) {
    int result = nums.size() + 1;
    int curSum = 0;
    int l = 0;
    for(int r = 0; r < nums.size(); ++r) {
        curSum = curSum + nums[r];
        while(curSum >= target) {
            result = min(result, r - l + 1);
            curSum -= nums[l];
            ++l;
        }
    }

    return result == nums.size() + 1 ? 0 : result;
}

943. 最短超级串*

在这里插入图片描述

string shortestSuperstring(vector<string>& words) {
    int n = words.size(), mask = 1 << n;
    // g[i][j] 表示words[i]的后缀与words[j]的前缀的最长覆盖长度
    vector<vector<int>> g(n, vector<int>(n, 0));
    for(int i = 0; i < n; ++i) {
        for(int j = 0; j < n; ++j) {
            int len = min(words[i].length(), words[j].length());
            for(int k = len; k >= 1; --k) {
                if(words[i].substr(words[i].length() - k) == words[j].substr(0, k)) {
                    g[i][j] = k;
                    break;
                }
            }
        }
    }

    vector<vector<int>> dp(mask, vector<int>(n, 0));
    vector<vector<int>> form(mask, vector<int>(n, 0));
    for(int i = 0; i < mask; ++i) {
        for(int j = 0; j < n; ++j) {
            if(((i >> j) & 1) == 0) continue;
            for(int k = 0; k < n; ++k) {
                if((i >> k) & 1 == 1) continue;
                if(dp[i | (1 << k)][k] <= dp[i][j] + g[j][k]) {
                    dp[i | (1 << k)][k] = dp[i][j] + g[j][k];
                    form[i | (1 << k)][k] = j;
                }
            }
        }
    }

    int idx = 0, status = mask - 1, last = -1;
    int max = dp[mask - 1][0];
    for(int i = 1; i < n; ++i) {
        if(dp[mask - 1][i] > max) {
            max = dp[mask - 1][i];
            idx = i;
        }
    }

    string result = "";
    while(status != 0) {
        if(last == -1) result = words[idx];
        else result = words[idx].substr(0, words[idx].length() - g[idx][last]) + result;
        last = idx;
        idx = form[status][idx];
        status ^= (1 << last);
    }

    return result;
}

904. 水果成篮

在这里插入图片描述

int totalFruit(vector<int>& fruits) {
    // 在区间内只能出现两种水果的最大子串
    int result = 0;
    int l = 0, r = 0;
    unordered_map<int, int> fruitsSet;
    for(r = 0; r < fruits.size(); ++r) {
        fruitsSet[fruits[r]]++;
        if(fruitsSet.size() <= 2) {
            result = max(result, r - l + 1);
        } else {
            while(fruitsSet.size() > 2) {
                --fruitsSet[fruits[l]];
                if(fruitsSet[fruits[l]] == 0) {
                    fruitsSet.erase(fruits[l]);
                }
                ++l;
            }
            result = max(result, r - l + 1);
        }
    }

    return result;
}

632. 最小区间*(优先队列)

在这里插入图片描述

class GetMaxMin {
    vector<int> vec;
public:
    int maxValue;
    int minValue;
    int maxIndex;
    int minIndex;
public:
    GetMaxMin(int size) {
        vec.resize(size);
    }

    void getMaxMin() {
        maxIndex = 0;
        minIndex = 0;
        for(int i = 1; i < vec.size(); ++i) {
            if(vec[maxIndex] < vec[i]) {
                maxIndex = i;
            }

            if(vec[minIndex] > vec[i]) {
                minIndex = i;
            }
        }
        maxValue = vec[maxIndex];
        minValue = vec[minIndex];
    }

    void setValue(int index, int value) {
        vec[index] = value;
        if(value > maxValue) {
            maxValue = value;
            maxIndex = index;
        } 
    }
};

vector<int> smallestRange(vector<vector<int>>& nums) {
    // 1. 超时
    // int k =  nums.size();
    // GetMaxMin getM(k);
    // vector<int> index(k, 0);
    // for(int i = 0; i < nums.size(); ++i) {
    //     getM.setValue(i, nums[i][0]);
    // }
    // getM.getMaxMin();

    // int resultMin = getM.minValue;
    // int resultMax = getM.maxValue;
    // long long len = resultMax - resultMin;
    // if(len == 0) return {resultMin, resultMax};

    // while(true) {
    //     int tempIndex = getM.minIndex;
    //     if (index[tempIndex] < nums[tempIndex].size() - 1) {
    //         index[tempIndex]++;
    //         getM.setValue(tempIndex, nums[tempIndex][index[tempIndex]]);
    //         getM.getMaxMin();
    //         if(getM.maxValue - getM.minValue < len) {
    //             resultMin = getM.minValue;
    //             resultMax = getM.maxValue;
    //             len = resultMax - resultMin;
    //             if(len == 0) return {resultMin, resultMax};
    //         }
    //     } else {
    //         break;
    //     }
    // }

    // return {resultMin, resultMax};

    // 2.优先队列加速
    int k = nums.size();
    vector<int> index(k, 0);
    auto cmp = [&](const int a, const int b) {
        return nums[a][index[a]] > nums[b][index[b]];
    };
    priority_queue<int, vector<int>, decltype(cmp)> mPriorityQueue(cmp);
    int maxValue = INT_MIN;

    int resultMaxValue = INT_MAX / 2;
    int resultMinValue = INT_MIN / 2;
    for(int i = 0; i < k; ++i) {
        mPriorityQueue.push(i);
        maxValue = max(maxValue, nums[i][index[i]]);
    }

    while(true) {
        int minIndex = mPriorityQueue.top();
        mPriorityQueue.pop();
        int minValue = nums[minIndex][index[minIndex]];
        if((maxValue - minValue) < (resultMaxValue - resultMinValue)) {
            resultMaxValue = maxValue;
            resultMinValue = minValue;
        }
        if (index[minIndex] < nums[minIndex].size() - 1) {
            index[minIndex]++;
            mPriorityQueue.push(minIndex);
            maxValue = max(maxValue, nums[minIndex][index[minIndex]]);
        } else {
            break;
        }
    }

    return {resultMinValue, resultMaxValue};
}

560. 和为K的子数组

在这里插入图片描述

int subarraySum(vector<int>& nums, int k) {
    // 1.超时
    // int result = 0;
    // int n = nums.size();
    // for(int l = 0; l < n; ++l) {
    //     int curSum = 0;
    //     for(int r = l; r < n; ++r) {
    //         curSum += nums[r];
    //         if(curSum == k) {
    //             ++result;
    //         }
    //     }
    // }

    // return result;

    // 2.前缀和
    int n = nums.size();
    int result = 0;
    unordered_map<int, int> numsMap;
    numsMap[0] = 1;
    int preSum = 0;
    for(int i = 0; i < n; ++i) {
        preSum += nums[i];
        if(numsMap.count(preSum - k)) {
            result += numsMap[preSum - k];
        }
        numsMap[preSum]++;
    }
    return result;
}

395. 至少有K个重复字符的最长子串*(枚举子串中字符种数)

在这里插入图片描述

int longestSubstring(string s, int k) {
    // 1. 分治
    // int result = 0;
    // function<void(int, int)> dfs = [&](int l, int r) {
    //     if(l + k - 1 > r) return;

    //     unordered_map<char, int> cnt;
    //     for(int i = l; i <= r; ++i) {
    //         cnt[s[i]]++;
    //     }

    //     bool flag = true;
    //     for(auto iter : cnt) {
    //         if(iter.second < k) {
    //             flag = false;
    //         }
    //     }

    //     if(flag) {
    //         result = max(result, r - l + 1);
    //         return;
    //     }

    //     int nextR = l;
    //     while(nextR <= r) {
    //         int nextL = nextR;
    //         while(nextR <= r && cnt[s[nextR]] >= k) {
    //             ++nextR;
    //         }
    //         dfs(nextL, nextR - 1);
    //         while(nextR <= r && cnt[s[nextR]] < k) {
    //             ++nextR;
    //         }
    //     }
    // };

    // dfs(0, s.length() - 1);
    // return result;


    // 2. 滑动窗口
    int result = 0;
    for(int i = 1; i <= 26; ++i) {
        unordered_map<char, int> charMap;
        int l = 0, r = 0;
        for(r = 0; r < s.length(); ++r) {
            charMap[s[r]]++;
            while(charMap.size() > i) {
                charMap[s[l]]--;
                if(charMap[s[l]] == 0) {
                    charMap.erase(s[l]);
                }
                ++l;
            }
            bool flag = true;
            for(auto iter : charMap) {
                if(iter.second < k) {
                    flag = false;
                }
            }

            if(flag) {
                result = max(result, r - l + 1);
            }
        }
    }

    return result;
}

862. 和至少为 K 的最短子数组*(双端队列+前缀和)

在这里插入图片描述

int shortestSubarray(vector<int>& nums, int k) {
    int n = nums.size();
    int result = INT_MAX;
    vector<int> preSum(n + 1, 0);
    for(int i = 0; i < nums.size(); ++i) {
        preSum[i + 1] = preSum[i] + nums[i];
    }

    deque<int> mDeque;
    for(int i = 0; i <= n; ++i) {
        int curPreSum = preSum[i];
        while(!mDeque.empty() && curPreSum - preSum[mDeque.front()] >= k) {
            result = min(result, i - mDeque.front());
            mDeque.pop_front();
        }

        while(!mDeque.empty() && curPreSum <= preSum[mDeque.back()]) {
            mDeque.pop_back();
        }

        mDeque.push_back(i);
    }

    return result == INT_MAX ? -1 : result;
}

4. 分治

1763. 最长的美好子字符串

在这里插入图片描述

string longestNiceSubstring(string s) {
    // 1.枚举
    // int maxPos = 0;
    // int maxLen = 0;
    // for(int i = 0; i < s.length(); ++i)  {
    //     for(int j = i; j < s.length(); ++j) {
    //         int upper = 0;
    //         int lower = 0;
    //         for(int k = i; k <= j; ++k) {
    //             if(islower(s[k])) {
    //                 lower |= (1 << (s[k] - 'a'));
    //             } else {
    //                 upper |= (1 << (s[k] - 'A'));
    //             }
    //         }
    //         if(lower == upper && j - i + 1 > maxLen) {
    //             maxPos = i;
    //             maxLen = j - i + 1;
    //         }
    //     }
    // }

    // return s.substr(maxPos, maxLen);


    // 2.分治
    int maxLen = 0;
    int maxPos = 0;
    function<void(int, int)> dfs = [&](int l, int r) {
        if(l >= r) {
            return;
        }

        int upper = 0;
        int lower = 0;
        for(int i = l; i <= r; ++i) {
            if(islower(s[i])) {
                lower |= (1 << (s[i] - 'a')); 
            } else {
                upper |= (1 << (s[i] - 'A'));
            }
        }

        if(lower == upper) {
            if(r - l + 1 > maxLen) {
                maxPos = l;
                maxLen = r - l + 1;
            }
            return;
        }

        int valid = lower & upper;
        int nextR = l;
        while(nextR < s.length()) {
            int nextL = nextR;
            while(nextR < s.length() && valid & (1 << (tolower(s[nextR]) - 'a'))) {
                ++nextR;
            }
            dfs(nextL, nextR - 1);
            ++nextR;
        }
    };

    dfs(0, s.length() - 1);
    return s.substr(maxPos, maxLen);
}

5. 二分查找

二分查找往往都是排除法,根据条件排除掉不满足的那部分区域,只考虑另一半区域【4.寻找两个正序数组的中位数

5.1 普通二分查找

153. 寻找旋转排序数组中的最小值

在这里插入图片描述

int findMin(vector<int>& nums) {
    int left = 0;
    int right = nums.size() - 1;
    while(left < right) {
        int mid = left + (right - left) / 2;
        if(nums[mid] > nums[right]) {
            left = mid + 1;
        } else if(nums[mid] <= nums[right]) {
            right = mid;
        }
    }
    return nums[right];
}

367. 有效的完全平方数

在这里插入图片描述

bool isPerfectSquare(int num) {
    int left = 1;
    int right = num;
    while(left <= right) {
        int mid = left + (right - left) / 2;
        if((long long)mid * mid == num) {
            return true;
        } else if((long long)mid * mid > num) {
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }

    return false;
}

4. 寻找两个正序数组的中位数

在这里插入图片描述

double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
    int m = nums1.size();
    int n = nums2.size();
    int totalLength = m + n;
    if(totalLength % 2 == 1) {
        return helper(nums1, nums2, (totalLength + 1) / 2);
    } else {
        return (helper(nums1, nums2, totalLength / 2) + helper(nums1, nums2, totalLength / 2 + 1)) / 2.0;
    }
}

double helper(vector<int>& nums1, vector<int>& nums2, int index) {
    int m = nums1.size();
    int n = nums2.size();

    int offset1 = 0, offset2 = 0;

    while(true) {
        if(offset1 == m) return nums2[offset2 + index - 1];
        if(offset2 == n) return nums1[offset1 + index - 1];

        if(index == 1) return min(nums1[offset1], nums2[offset2]);
        int newOffset1 = min(offset1 + index / 2 - 1, m - 1);
        int newOffset2 = min(offset2 + index / 2 - 1, n - 1);

        if(nums1[newOffset1] <= nums2[newOffset2]) {
            index = index - (newOffset1 - offset1 + 1);
            offset1 = newOffset1 + 1;
        } else {
            index = index - (newOffset2 - offset2 + 1);
            offset2 = newOffset2 + 1;
        }

    }
}

162. 寻找峰值

在这里插入图片描述

int findPeakElement(vector<int>& nums) {
    int left = 0;
    int right = nums.size() - 1;
    while(left < right) {
        int mid = left + (right - left) / 2;
        if(nums[mid] > nums[mid + 1]) {
            right = mid;
        } else {
            left = mid + 1;
        }
    }

    return left;
}

5.2 查找区间

5.3 旋转排序数组

33. 搜索旋转排序数组

在这里插入图片描述

int search(vector<int>& nums, int target) {
    int left = 0;
    int right = nums.size() - 1;
    while(left <= right) {
        int mid = left + (right - left) / 2;
        if(nums[mid] == target) {
            return mid;
        }

        if(nums[mid] == nums[left]) {
            ++left;
            continue;
        } else if(nums[mid] > nums[left]) {
            if(nums[left] <= target && target < nums[mid]) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        } else {
            if(nums[mid] < target && target <= nums[right]) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
    }

    return -1;
}

154. 寻找旋转排序数组中的最小值 II

在这里插入图片描述

int findMin(vector<int>& nums) {
    int left = 0;
    int right = nums.size() - 1;
    while(left < right) {
        int mid = left + (right - left) / 2;
        if(nums[mid] == nums[right]) {
            --right;
            continue;
        }

        if(nums[mid] > nums[right]) {
            left = mid + 1;
        } else {
            right = mid;
        }

    }

    return nums[right];
}

5.4 最小值最大化

2560. 打家劫舍Ⅳ*

在这里插入图片描述

int minCapability(vector<int>& nums, int k) {
    // int n = nums.size();
    // int result = INT_MAX;
    // unordered_map<int, int> numMap;
    // function<void(int, bool, int, int)> dfs = [&](int curIndex, bool flag, int num, int curMax) {
    //     if(num == 0) {
    //         result = min(result, curMax);
    //         return;
    //     }
    //     if(curIndex == n) {
    //         return;
    //     }

    //     dfs(curIndex + 1, true, num, curMax);
    //     if(flag) dfs(curIndex + 1, false, num - 1, max(curMax, nums[curIndex]));
    // };

    // dfs(0, true, k, 0);
    // return result;

    int lower = *min_element(nums.begin(), nums.end());
    int upper = *max_element(nums.begin(), nums.end());
    int left = lower - 1;
    int right = upper + 1;
    while(left + 1 != right) {
        int mid = left + (right - left) / 2;
        bool visit = true;
        int count = 0;
        for(auto num : nums) {
            if(num <= mid && visit) {
                ++count;
                visit = false;
            } else {
                visit = true;
            }
        }

        if(count >= k) {
            right = mid;
        } else {
            left = mid;
        }
    }
    return right;
}

6. 双指针

6.1 对撞指针问题

11. 盛最多水的容器

在这里插入图片描述

int maxArea(vector<int>& height) {
    int result = 0;
    int left = 0;
    int right = height.size() - 1;
    
    while(left < right) {
        int temp = (right - left) * min(height[left], height[right]);
        result = max(temp, result);
        if(height[left] >= height[right]) {
            --right;
        } else {
            ++left;
        }
    }

    return result;
}

6.2 快慢指针问题

206. 反转链表

在这里插入图片描述

ListNode* reverseList(ListNode* head) {
    // 1. 循环
    // ListNode* pre = nullptr;
    // ListNode* next;
    // while(head) {
    //     next = head->next;
    //     head->next = pre;
    //     pre = head;
    //     head = next;
    // }

    // return pre;


    // 2. 递归
    ListNode* pre = nullptr;
    return helper(head, pre);
}

ListNode* helper(ListNode* head, ListNode* pre) {
    if(!head) return pre;
    ListNode* next = head->next;
    head->next = pre;
    return helper(next, head);
}

142. 环形链表 II

在这里插入图片描述

    ListNode *detectCycle(ListNode *head) {
        ListNode* fastPtr = head;
        ListNode* slowPtr = head;

        while(fastPtr && fastPtr->next) {
            fastPtr = fastPtr->next->next;
            slowPtr = slowPtr->next;
            if(fastPtr == slowPtr) break;
        }

        if(!fastPtr || !fastPtr->next) return nullptr;

        fastPtr = head;
        while(fastPtr != slowPtr) {
            fastPtr = fastPtr->next;
            slowPtr = slowPtr->next;
        }

        return slowPtr;
    }

82. 删除排序链表中的重复元素 II

在这里插入图片描述

ListNode* deleteDuplicates(ListNode* head) {
    ListNode* dummy = new ListNode;
    dummy->next = head;
    ListNode* cur = dummy;
    while(cur->next && cur->next->next) {
        if(cur->next->val == cur->next->next->val) {
            int value = cur->next->val;
            while (cur->next && cur->next->val == value) {
                cur->next = cur->next->next;
            }
        } else {
            cur = cur->next;
        }
    }
    return dummy->next;
}

234. 回文链表*(递归)

在这里插入图片描述

bool recursivelyCheck(ListNode* currentNode) {
    if (currentNode != nullptr) {
        if (!recursivelyCheck(currentNode->next)) {
            return false;
        }
        if (currentNode->val != frontPointer->val) {
            return false;
        }
        frontPointer = frontPointer->next;
    }
    return true;
}

bool isPalindrome(ListNode* head) {
    frontPointer = head;
    return recursivelyCheck(head);
}

457. 环形数组是否存在循环

在这里插入图片描述

bool circularArrayLoop(vector<int>& nums) {
    int n = nums.size();
    auto next = [&](int cur) {
        return ((cur + nums[cur]) % n + n) % n; // 保证返回值在 [0,n) 中
    };

    // 以每个为起点都试一次
    for(int i = 0; i < nums.size(); ++i) {
        if(nums[i] == 0) continue;
        int slow = i;
        int fast = next(i);
        while(nums[slow] * nums[fast] > 0 && nums[slow] * nums[next(fast)] > 0) {
            if(slow == fast) {
                if(slow != next(slow)) {
                    return true;
                } else {
                    break;
                }
            }
            slow = next(slow);
            fast = next(next(fast));
        }

        int add = i;
        while(nums[add] * nums[next(add)] > 0) {
            int tmp = add;
            add = next(add);
            nums[tmp] = 0;
        }
    }

    return false;
}

7. 贪心

7.1 区间类

452. 用最少数量的箭引爆气球

在这里插入图片描述

static bool cmp(const vector<int>& a, const vector<int>& b) {
    return a[1] < b[1];
}

int findMinArrowShots(vector<vector<int>>& points) {
    // 找所有区间的重合点,使得重合点可以覆盖所有区间
    sort(points.begin(), points.end(), cmp);

    int pos = points[0][1];
    int result = 1;

    for(int i = 1; i < points.size(); ++i) {
        if(points[i][0] > pos) {
            pos = points[i][1];
            ++result;
        }
    }

    return result;
}

763. 划分字母区间

在这里插入图片描述

vector<int> partitionLabels(string s) {
    vector<int> result;
    vector<int> lastIndex(26, -1);
    int start = 0, end = 0;

    // 记录各个字符最后出现位置
    for(int i = 0; i < s.length(); ++i) {
        lastIndex[s[i] - 'a'] = i;
    }

    for(int i = 0; i < s.length(); ++i) {
        end = max(end, lastIndex[s[i] - 'a']);
        if(i == end) {
            result.push_back(end - start + 1);
            start = end + 1;
        }
    }

    return result;
}

1353. 最多可以参加的会议数目*

在这里插入图片描述

static int cmp(const vector<int>& a, const vector<int>& b) {
    return a[0] < b[0];
}

int maxEvents(vector<vector<int>>& events) {
    int curDay = 1;
    int result = 0;
    sort(events.begin(), events.end(), cmp);

    // 小顶堆,用于高效维护最小的endDay
    priority_queue<int, vector<int>, greater<int>> pq;
    int i = 0;
    while(i < events.size() || !pq.empty()) {
        // 添加新增的当天能够参加的会议,并将结束时间最早的会议放在头部
        while(i < events.size() && events[i][0] == curDay) {
            pq.push(events[i][1]);
            ++i;
        }

        // 移除已经过期会议
        while(!pq.empty() && pq.top() < curDay) {
            pq.pop();
        }

        // 若还有可以参加的会议
        if(!pq.empty()) {
            pq.pop();
            result++;
        }
        
        curDay++;
    }

    return result;
}

1520. 最多的不重叠子字符串*

在这里插入图片描述

struct Seg {
    int left = -1;
    int right = -1;
    bool operator<(const Seg& rhs) const {
        // 长度作为第二关键字,选择长度较短的
        if(right == rhs.right) {
            return left > rhs.left;
        }
        return right < rhs.right;   // 结束位置作为第一关键字
    }

};
vector<string> maxNumOfSubstrings(string s) {
    vector<string> result;

    // 1.预处理左右端点,找到每个字母在给定字符串中的起始和终止位置
    vector<Seg> seg(26);

    for(int i = 0; i < s.length(); ++i) {
        int char_index = s[i] - 'a';
        if(seg[char_index].left == -1) {
            seg[char_index].left = seg[char_index].right = i;
        } else {
            seg[char_index].right = i;
        }
    }

    // 2.进一步处理,确保每一段内包含的每个字符都是全部字符
    for(int i = 0; i < seg.size(); ++i) {
        if(seg[i].left != -1) {
            for(int j = seg[i].left; j < seg[i].right; ++j) {
                int char_index = s[j] - 'a';
                if(seg[i].left <= seg[char_index].left && seg[i].right >= seg[char_index].right) {
                    continue;
                }
                seg[i].left = min(seg[i].left, seg[char_index].left);
                seg[i].right = max(seg[i].right, seg[char_index].right);
                j = seg[i].left;
            }
        }
    }
    
    // 3.贪心选取
    sort(seg.begin(), seg.end());
    vector<string> ans;
    int end = -1;
    for(auto& segment : seg) {
        int left = segment.left, right = segment.right;
        if(left == - 1) {
            continue;
        }
        if(end == -1 || left > end) {
            end = right;
            result.emplace_back(s.substr(left, right - left + 1));
        }
    }

    return result;
}

738. 单调递增的数字

在这里插入图片描述

int monotoneIncreasingDigits(int n) {
    string num = to_string(n);
    int index = 1;
    while(index < num.length() && num[index] >= num[index - 1]) {
        ++index;
    }

    if(index < num.length()) {
        while(index > 0 && num[index] < num[index - 1]) {
            num[index - 1]--;
            --index;
        }

        ++index;
        while(index < num.length()) {
            num[index++] = '9';
        }
    }

    return stoi(num);
}

7.2 峰谷类

376. 摆动序列

在这里插入图片描述`

int wiggleMaxLength(vector<int>& nums) {
    int result = 0;
    // 1. 优化前
    // vector<int> up(nums.size(), 1);
    // vector<int> down(nums.size(), 1);

    // for(int i = 1; i < nums.size(); ++i) {
    //     if(nums[i] > nums[i - 1]) {
    //         up[i] = up[i - 1];
    //         down[i] = max(up[i - 1] + 1, down[i - 1]);
    //     } else if(nums[i] < nums[i - 1]) {
    //         up[i] = max(up[i - 1], down[i - 1] + 1);
    //         down[i] = down[i - 1];
    //     } else {
    //         up[i] = up[i - 1];
    //         down[i] = down[i - 1];
    //     }
    // }

    // 2. 优化后
    int up = 1;
    int down = 1;
    for(int i = 1; i < nums.size(); ++i) {
        if(nums[i] > nums[i - 1]) {
            down = max(up +1, down);
        }
        else if(nums[i] < nums[i - 1]) {
            up = max(up, down + 1);
        }
    }

    return max(up, down);
}

7.3 钱币类

7.4 任务类

621. 任务调度器*(模拟)

在这里插入图片描述

int leastInterval(vector<char>& tasks, int n) {
    // // 1. 模拟
    // unordered_map<char, int> taskMap;
    // for(int i = 0; i < tasks.size(); ++i) {
    //     taskMap[tasks[i]]++;
    // }
    // int totalType = taskMap.size();

    // vector<pair<int, int>> taskQueue;
    // for(auto task : taskMap) {
    //     taskQueue.push_back({1, task.second});
    // }

    // int time = 0;
    // for(int i = 0; i < tasks.size(); ++i) {
    //     ++time;
    //     int minTime = INT_MAX;
    //     for(int j = 0; j < taskQueue.size(); ++j) {
    //         if(taskQueue[j].second > 0) {
    //             minTime = min(minTime, taskQueue[j].first);
    //         }
    //     }

    //     time = max(time, minTime);
    //     int Best = -1;
    //     for(int j = 0; j < taskQueue.size(); ++j) {
    //         if(time >= taskQueue[j].first && taskQueue[j].second > 0) {
    //             if(Best == -1 || taskQueue[Best].second < taskQueue[j].second) {
    //                 Best = j;
    //             }
    //         }
    //     }

    //     taskQueue[Best].first = time + n + 1;
    //     --taskQueue[Best].second;
    // }
    

    // return time;

    // 2. 贪心
    vector<int> taskMap(26);
    for(int i = 0; i < tasks.size(); ++i) {
        taskMap[tasks[i] - 'A']++;
    }

    sort(taskMap.begin(), taskMap.end());

    int maxCount = taskMap[25];
    int result = (maxCount - 1) * (n + 1) + 1;
    for(int i = 24; i >= 0; --i) {
        if(taskMap[i] == maxCount) {
            result++;
        }
    }

    return max(result, (int)tasks.size());
}

1705. 吃苹果的最大数目*(模拟)

在这里插入图片描述

int eatenApples(vector<int> apples, vector<int> days) {
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> mPriorityQueue;
    int result = 0;
    int i = 0;
    for(i = 0; i < apples.size(); ++i) {
        while(!mPriorityQueue.empty() && mPriorityQueue.top().first < i) {
            mPriorityQueue.pop();
        }

        if(apples[i] != 0) {
            mPriorityQueue.push({i + days[i] - 1, apples[i]});
        }

        if(!mPriorityQueue.empty()) {
            auto [time, count] = mPriorityQueue.top();
            mPriorityQueue.pop();
            count--;
            if(count != 0) {
                mPriorityQueue.push({time, count});
            }
            ++result;
        }
    }

    while(!mPriorityQueue.empty()) {
        while(!mPriorityQueue.empty() &&  mPriorityQueue.top().first < i) {
            mPriorityQueue.pop();
        }
        
        while(!mPriorityQueue.empty() && mPriorityQueue.top().first >= i) {
            auto [time, count] = mPriorityQueue.top();
            mPriorityQueue.pop();
            result += min(count, time - i + 1);
            i += min(count, time - i + 1);
        }
    }

    return result;
}

7.5 矩形类

870. 优势洗牌

在这里插入图片描述

vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) {
    vector<int> result(nums1.size());
    sort(nums1.begin(), nums1.end());

    vector<int> indexNum2(nums2.size(), 0);
    iota(indexNum2.begin(), indexNum2.end(), 0);

    sort(indexNum2.begin(), indexNum2.end(), [&](int a, int b){
        return nums2[a] < nums2[b];
    });

    int left = 0, right = nums2.size() - 1;
    for(int i = 0; i < nums1.size(); ++i) {
        if(nums1[i] > nums2[indexNum2[left]]) {
            result[indexNum2[left++]] = nums1[i];
        } else {
            result[indexNum2[right--]] = nums1[i];
        }
    }

    return result;
}

1262. 可被三整除的最大和*(DP/贪心)

在这里插入图片描述

int maxSumDivThree(vector<int> nums) {
    int n = nums.size();
    // dp[i][j]表示从前i个数中取若干个数与3取余为j的最大和
    vector<vector<int>> dp(n + 1, vector<int>(3, 0));

    for (int i = 0; i < n; ++i) {
        if (nums[i] % 3 == 0) {
            dp[i + 1][0] = dp[i][0] + nums[i];
            dp[i + 1][1] = dp[i][1] == 0 ? 0 : dp[i][1] + nums[i];
            dp[i + 1][2] = dp[i][2] == 0 ? 0 : dp[i][2] + nums[i];
        }
        else if (nums[i] % 3 == 1) {
            dp[i + 1][0] = dp[i][2] == 0 ? 0 : dp[i][2] + nums[i];
            dp[i + 1][1] = dp[i][0] + nums[i];
            dp[i + 1][2] = dp[i][1] == 0 ? 0 : dp[i][1] + nums[i];
        }
        else if (nums[i] % 3 == 2) {
            dp[i + 1][0] = dp[i][1] == 0 ? 0 : dp[i][1] + nums[i];
            dp[i + 1][1] = dp[i][2] == 0 ? 0 : dp[i][2] + nums[i];
            dp[i + 1][2] = dp[i][0] + nums[i];
        }
        
        dp[i + 1][0] = max(dp[i + 1][0], dp[i][0]);
        dp[i + 1][1] = max(dp[i + 1][1], dp[i][1]);
        dp[i + 1][2] = max(dp[i + 1][2], dp[i][2]);
    }

    return dp[n][0];
}

8. 排序

8.1 冒泡排序

void  bubbleSort(vector<int>& nums, int n) {
    for(int i = 0; i < nums.size() - 1; ++i) {
        for(int j = nums.size() - 1; j > i; --j) {
            if(nums[j] < nums[j - 1]) {
                swap(nums[j], nums[j - 1]);
            }
        }
    }
}

8.2 选择排序

void selectSort(vector<int>& nums, int n) {
    for(int i = 0; i < nums.size() - 1; ++i) {
        int minIndex = i;
        for(int j = i + 1; j < nums.size(); ++j) {
            if(nums[minIndex] > nums[j]) {
                minIndex = j;
            }
        }
        swap(nums[i], nums[minIndex]);
    }
}

8.3 插入排序

void insertSort(vector<int>& nums, int n) {
    for(int i = 0; i < nums.size() - 1; ++i) {
        for(int j = i + 1; j > 0 && nums[j] < nums[j - 1]; --j) {
            swap(nums[j], nums[j - 1]);
        }
    }
}

147. 对链表进行插入排序

在这里插入图片描述

ListNode* insertionSortList(ListNode* head) {
    ListNode* dummy = new ListNode(INT_MIN);
    ListNode* node = head;
    while(node) {
        ListNode* cur = dummy;
        while(cur->next && cur->next->val < node->val) {
            cur = cur->next;
        }

        ListNode* temp = node;
        node = node->next;

        if(!(cur->next)) {
            cur->next = temp;
            temp->next = nullptr;
        }
        else {
            temp->next = cur->next;
            cur->next = temp;
        }

    }
    return dummy->next;
}

8.4 快速排序

void quickSort(vector<int>& nums, int l, int r) {
    if(l + 1 >= r) {
        return;
    }
    int left = l, right = r - 1, key = nums[left];
    while(left < right) {
        while(left < right && nums[right] >= key) {
            --right;
        }
        nums[left] = nums[right];
        while(left < right && nums[left] <= key) {
            ++left;
        }
        nums[right] = nums[left];
    }
    nums[left] = key;
    quickSort(nums, l, left);
    quickSort(nums, left + 1, r);
}

973. 最接近原点的 K 个点

在这里插入图片描述

vector<vector<int>> kClosest(vector<vector<int>>& points, int k) {
    vector<vector<int>> result;
    quickSort(points, 0, points.size(), k);
    for(int i = 0; i < k; ++i) {
        result.push_back(points[i]);
    }

    return result;
}

void quickSort(vector<vector<int>>& points, int l, int r, int k) {
    if(l + 1 >= r) return;
    int left = l, right = r - 1;
    auto pointTemp = points[left];
    int pointDistance = getDistance(pointTemp[0], pointTemp[1]);

    while(left < right) {
        while(left < right && getDistance(points[right][0], points[right][1]) >= pointDistance) {
            --right;
        }

        points[left] = points[right];

        while(left < right && getDistance(points[left][0], points[left][1]) <= pointDistance) {
            ++left;
        }

        points[right] = points[left];
    }

    points[left] = pointTemp;
    if(left == k - 1) return;

    quickSort(points, l, left, k);
    quickSort(points, left + 1, r, k);
}

int getDistance(int x, int y) {
    return abs(pow(x, 2)) + abs(pow(y, 2));
}

8.5 归并排序

void mergeSort(vector<int>& nums, int l, int r, vector<int>& temp) {
    if (l + 1 >= r) {
        return;
    }
    // divide
    int m = l + (r - l) / 2;
    mergeSort(nums, l, m, temp);
    mergeSort(nums, m, r, temp);
    int index1 = l;
    int index2 = m;
    int index = l;
    while (index1 < m && index2 < r) {
        if (nums[index1] <= nums[index2]) {
            temp[index++] = nums[index1++];
        }
        else {
            temp[index++] = nums[index2++];
        }
    }
    while (index1 < m) {
        temp[index++] = nums[index1++];
    }
    while (index2 < r) {
        temp[index++] = nums[index2++];
    }
    for (int i = l; i < r; ++i) {
        nums[i] = temp[i];
    }
}

23. 合并K个升序链表

在这里插入图片描述

struct cmp {
    bool operator()(ListNode* a, ListNode* b) {
        return a->val > b->val;
    }
};

ListNode* mergeKLists(vector<ListNode*>& lists) {
    priority_queue<ListNode*, vector<ListNode*>, cmp> priorityQueue;
    for(auto list : lists) {
        if(list) {
            priorityQueue.push(list);
        }

    }

    ListNode* dummy = new ListNode;
    ListNode* cur = dummy;
    while(!priorityQueue.empty()) {
        ListNode* node = priorityQueue.top();
        priorityQueue.pop();
        cur->next = node;
        cur = cur->next;
        if(node->next) {
            priorityQueue.push(node->next);
        }
    }

    return dummy->next;
}

8.6 桶排序

347. 前 K 个高频元素

在这里插入图片描述

vector<int> topKFrequent(vector<int>& nums, int k) {
    unordered_map<int, int> numsMap;
    vector<int> result;
    int maxCount = 0;
    for(int i = 0; i < nums.size(); ++i) {
        numsMap[nums[i]]++;
        maxCount = max(maxCount, numsMap[nums[i]]);
    }

    vector<vector<int>> bucket(maxCount + 1);

    for(auto iter : numsMap) {
        bucket[iter.second].push_back(iter.first);
    }

    for(int i = maxCount; i >= 0; --i) {
        int size = bucket[i].size();
        for(int j = 0; j < size; ++j) {
            result.push_back(bucket[i][j]);
            if(result.size() == k) {
                return result;
            }
        }
    }

    return result;
}

220. 存在重复元素Ⅲ*(桶的构建)

在这里插入图片描述

bool containsNearbyAlmostDuplicate(vector<int>& nums, int indexDiff, int valueDiff) {
    // 滑动窗口+有序集合
    // set<int> window;
    // for(int i = 0; i < nums.size(); ++i) {
    //     auto iter = window.lower_bound(nums[i] - valueDiff);    // 第一个不小于目标元素
    //     if(iter != window.end() && *iter <= nums[i] + valueDiff) {
    //         return true;
    //     }

    //     window.insert(nums[i]);
    //     if(i >= indexDiff) {
    //         window.erase(nums[i - indexDiff]);
    //     }
    // }

    // 桶
    unordered_map<int, int> numsMap;
    for(int i = 0; i < nums.size(); ++i) {
        int id = getID(nums[i], valueDiff + 1);
        if(numsMap.count(id)) {
            return true;
        } else if(numsMap.count(id - 1) && abs(nums[i] - numsMap[id - 1]) <= valueDiff) {
            return true;
        } else if(numsMap.count(id + 1) && abs(nums[i] - numsMap[id + 1]) <= valueDiff) {
            return true;
        }
        numsMap[id] = nums[i];
        if(i >= indexDiff) {
            numsMap.erase(getID(nums[i - indexDiff], valueDiff + 1));
        }
    }

    return false;
}

8.7 堆排序

1648. 销售价值减少的颜色球*(模拟+加速)

在这里插入图片描述

int maxProfit(vector<int>& inventory, int orders) {
    priority_queue<int> priorityQueue;
    long long result = 0;
    for(int i = 0; i < inventory.size(); ++i) {
        priorityQueue.push(inventory[i]);
    }
    auto getProfit = [&](const long long begin, const long long end) {
        long long a = begin + end;
        long long b = end - begin + 1;
        return a * b / 2;
    };

    int m = 1;      // 最多数目球的种类数,初始化为1
    while(orders > 0) {
        long long maxBall = priorityQueue.top();
        priorityQueue.pop();
        if(!priorityQueue.empty() && (maxBall - priorityQueue.top()) * m <= orders) {
            result = (result + getProfit(priorityQueue.top() + 1, maxBall) * m) % ((long long)1e9 + 7);
            orders = orders - (maxBall - priorityQueue.top()) * m;
            m++;
        } else {    // 没有其他球或者最多数目球已经满足需求
            int x = orders / m;     // 最多数目球同时减
            int y = orders % m;
            result = (result + getProfit(maxBall - x + 1, maxBall) * m) % ((long long)1e9 + 7);
            result = (result + (maxBall - x) * y) % ((long long)1e9 + 7);
            orders = 0;
        }
    }

    return result;
}

8.8 计数排序

75. 颜色分类

在这里插入图片描述

void sortColors(vector<int>& nums) {
    int indexOfZero = 0;
    int indexOfOne = 0;
    int indexOfTwo = 0;
    for (int i = 0; i < nums.size(); ++i) {
        if (nums[i] == 0) {
            nums[indexOfTwo++] = 2;
            nums[indexOfOne++] = 1;
            nums[indexOfZero++] = 0;
        }
        else if (nums[i] == 1) {
            nums[indexOfTwo++] = 2;
            nums[indexOfOne++] = 1;
        }
        else if (nums[i] == 2) {
            nums[indexOfTwo++] = 2;
        }
    }
}

3. STL

3.1 链表

3.1.1 移除与插入链表元素

83. 删除排序链表中的重复元素

在这里插入图片描述

ListNode* deleteDuplicates(ListNode* head) {
    if (!head) {
        return head;
    }

    ListNode* cur = head;
    while (cur->next) {
        if (cur->val == cur->next->val) {
            cur->next = cur->next->next;
        }
        else {
            cur = cur->next;
        }
    }
    return head;
}

3.1.2 链表的遍历

前序遍历
auto stk = stack<TreeNode*>();
TreeNode *node = root;
while (node != nullptr || !stk.empty()) {
    while (node != nullptr) {
        v.push_back(node);
        stk.push(node);
        node = node->left;
    }
    node = stk.top(); stk.pop();
    node = node->right;
}

430. 扁平化多级双向链表

在这里插入图片描述

Node* flatten(Node* head) {
    if(!head) return nullptr;
    Node* pre = nullptr;
    function<void(Node*)> dfs = [&](Node* node) {
        if(!node) return;
        if(pre) node->prev = pre;
        pre = node;
        if(node->child) {
            dfs(node->child);
        }
        if(node->next) {
            dfs(node->next);
        }
        node->child = nullptr;
    };

    dfs(head);
    
    while(pre->prev) {
        pre->prev->next = pre;
        pre = pre->prev;
    }

    return pre;
}

3.1.3 链表的旋转和反转

61. 旋转链表

在这里插入图片描述

ListNode* rotateRight(ListNode* head, int k) {
    if(!head) return nullptr;
    if(!head->next) return head;

    ListNode* temp = head;
    int length = 1;
    while(temp->next)
    {
        temp = temp->next;
        ++length;
    }

    k = k % length;

    ListNode* node1 = head;
    ListNode* node2 = head;

    for(int i = 0; i < k; ++i)
    {
        while(node1->next->next)
        {
            node1 = node1->next;
            node2 = node2->next;
        }
        node2 = node2->next;

        node2->next = head;
        head = node2;
        node1->next = nullptr;
        node1 = head;
    }

    return head;
}

3.2 栈和队列

3.2.1 利用栈进行模拟

32. 最长的有效括号

在这里插入图片描述

int longestValidParentheses(string s) {
    // 利用栈进行模拟
    int result = 0;
    stack<int> mStack;  // 应该在栈底填入第一个不满足配对条件的下标
    mStack.push(-1);
    for(int i = 0; i < s.length(); ++i) {
        if(s[i] == '(') {
            mStack.push(i);
        } else {
            mStack.pop();
            if(mStack.empty())      // 如果弹出以后为空,说明没有匹配上
            {   
                mStack.push(i);     
            }
            // 如果弹出以后不为空,说明匹配上了,更新长度到上面一个没匹配上的位置
            else
            {
                int temp = mStack.top();
                int length = i - temp;
                result = max(result, length);
            }
        }
    }

    return result;
}

3.3 数组

3.3.1 数组的旋转

396. 旋转函数

在这里插入图片描述

在这里插入代码片

3.3.2 统计数组中的元素

697. 数组的度

在这里插入图片描述

int findShortestSubArray(vector<int>& nums) {
    // 1.双指针
    // 先求数组的度
    // unordered_map<int, int> numCount;
    // int degree = 0;
    // for(int i = 0; i < nums.size(); ++i)
    // {
    //     numCount[nums[i]]++;
    //     degree = max(degree, numCount[nums[i]]);
    // }

    // int res = nums.size();
    
    // // 利用双指针遍历所有情况计算长度
    // int begin = 0, end = 0, curDegree = 0;
    // unordered_map<int, int> curNumCount;
    // while(end < nums.size())
    // {
    //     curNumCount[nums[end]]++;
    //     curDegree = max(curDegree, curNumCount[nums[end]]);
    //     while(curDegree == degree)
    //     {
    //         res = min(res, end - begin + 1);
    //         if(curNumCount[nums[begin]] == curDegree)
    //         {
    //             --curDegree;
    //         }
    //         --curNumCount[nums[begin]];
    //         ++begin;
    //     }
    //     ++end;
    // }

    // return res;

    // 2. 统计每个数出现的起始和终止位置
    unordered_map<int, vector<int>> numCount;
    for(int i = 0; i < nums.size(); ++i)
    {
        if(numCount.find(nums[i]) != numCount.end())
        {
            numCount[nums[i]][0]++;
            numCount[nums[i]][2] = i;
        }
        else
        {
            numCount[nums[i]] = {1, i, i};
        }
    }

    int maxNum = 0;
    int minLen = nums.size();
    for(auto [_, vec] : numCount) 
    {
        if(maxNum < vec[0])
        {
            maxNum = vec[0];
            minLen = vec[2] - vec[1] + 1;
        }
        else if(maxNum == vec[0])
        {
            minLen = min(minLen, vec[2] - vec[1] + 1);
        }
    }

    return minLen;
}
41. 缺失的第一个正数

在这里插入图片描述

int firstMissingPositive(vector<int>& nums) {
    for(int i = 0; i < nums.size(); ++i)
    {
        while(nums[i] > 0 && nums[i] <= nums.size() && nums[i] != i + 1)
        {
            if(nums[i] == nums[nums[i] - 1]) break;
            swap(nums[i], nums[nums[i] - 1]);
        }
    }

    for(int i = 0; i < nums.size(); ++i)
    {
        if(nums[i] != i + 1)
        {
            return i + 1;
        }
    }

    return nums.size() + 1;
}
628. 三个数的最大乘积

在这里插入图片描述

int maximumProduct(vector<int>& nums) {
    sort(nums.begin(), nums.end());
    int n = nums.size();
    return max(nums[0] * nums[1] * nums[n - 1], nums[n - 3] * nums[n - 2] * nums[n - 1]);
}

3.3.3 特定顺序遍历二维数组

54. 螺旋矩阵

在这里插入图片描述

vector<int> spiralOrder(vector<vector<int>>& matrix) {
    int m = matrix.size();
    int n = matrix[0].size();
    vector<int> res;
    for(int i = 0; i <= min((m - 1) / 2, (n - 1) / 2); ++i)
    {
        for(int j = i; j <= n - i - 1; ++j)
        {
            res.push_back(matrix[i][j]);
        }

        for(int j = i + 1; j <= m - i - 1; ++j)
        {
            res.push_back(matrix[j][n - i - 1]);
        }
        
        if(m - i - 2 < i || n - i - 2 < i) break;

        for(int j = n - i - 2; j >= i; --j)
        {
            res.push_back(matrix[m - i - 1][j]);
        }

        for(int j = m - i - 2; j > i; --j)
        {
            res.push_back(matrix[j][i]);
        }
    }

    return res;
}

算法集合

1. 动态规划算出字符串每个子串是否为回文

vector<vector<bool>> calculate(string str) {
	int n = str.length();
	vector<vector<bool>> dp(n, vector<bool>(n, true));
	
	for (int i = n - 1; i >= 0; --i) {
	   for (int j = i + 1; j < n; ++j) {
	       dp[i][j] = (str[i] == str[j]) && dp[i + 1][j - 1];
	   }
	}
}

2. 并查集

 class DisjointSetUnion {
 private:
     vector<int> fa;
     vector<int> rank;

 public:
     int connectCount;
     DisjointSetUnion(int n) 
         : connectCount(n)
     {
         fa.resize(n);
         rank.resize(n, 1);
         iota(fa.begin(), fa.end(), 0);
     }

     int find(int x) {
         return x == fa[x] ? x : fa[x] = find(fa[x]);
     }

     void merge(int a, int b) {
         a = find(a);
         b = find(b);
         if(a != b) {
             if(rank[a] <= rank[b]) {
                 fa[a] = b;
             } else {
                 fa[b] = a;
             }

             if(rank[a] == rank[b]) {
                 ++rank[b];
             }
             --connectCount;
         }
     }

     bool isConnected(int a, int b) {
         a = find(a);
         b = find(b);
         if(a != b) {
             return false;
         }

         return true;
     }
 };

3. 快速幂

const long long m = 1e9 + 7;
long long quickpow(long long a,long long b){
	long long sum = 1;
	while(b) {
		if(b & 1) // 与运算,可判断奇偶,详细见注释
			sum = sum * a % m; // 取模运算
		a = a * a % m;
		b >>= 1;  // 位运算,右移,相当于除以2
	}
	return sum;
}

4. 位运算

  1. 集合论到位运算

4.1 返回二进制中1的个数

__builtin_popcount();

5. 模运算

(a + b) % m = (a % m + b % m) % m
(a * b) % m = ((a % m) * (b % m )) % m

6. 将十进制数转换为二进制数

string strNum = bitset<32>(n).to_string()

7. Dijkstra算法

7.1 朴素Dijkstra算法

void Dijkstra(vector<vector<int>>& distMatrix, int start, vector<int>& distVec) {
    int n = distMatrix.size();
    vector<bool> flag(n, false);
    distVec = distMatrix[start];
    distVec[start] = 0;
    flag[start] = true;
    for(int i = 1; i < n; ++i) {
        int minValue = INT_MAX;
        int minIndex = -1;
        for(int j = 0; j < n; ++j) {
            if(!flag[j] && distVec[j] < minValue) {
                minValue = distVec[j];
                minIndex = j;
            }
        }
        if(minIndex == -1) return;
        flag[minIndex] = true;
        for(int j = 0; j < n; ++j) {
            if(!flag[j] && distMatrix[minIndex][j] < INT_MAX) {
                int newDist = minValue + distMatrix[minIndex][j];
                if(newDist < distVec[j]) {
                    distVec[j] = newDist;
                }
            }
        }
    }
}

void test() {
	vector<vector<int>> distMatrix(n, vector<int>(n, INT_MAX));
	// ...
	vector<int> distVec;
    Dijkstra(distMatrix, k - 1, distVec);
}

7.2 优化Dijkstra算法

// distMatric:邻接表,到各个节点的距离
// start:起始位置
// dist:返回离各个节点的最近距离
void Dijkstra(vector<vector<pair<int, int>>>& distMatrix, int start, vector<int>& dist) {
    dist[start] = 0;
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
    pq.push({0, start});
    while(!pq.empty()) {
        auto [d, node] = pq.top();
        pq.pop();
        if(d > dist[node]) continue;	// 表示已经处理过了
        for(auto relativeNode : distMatrix[node]) {
            auto [nodeTemp, dTemp] = relativeNode;
            int newDistance = dist[node] + dTemp;
            if(newDistance < dist[nodeTemp]) {
                dist[nodeTemp] = newDistance;
                pq.push({newDistance, nodeTemp});
            }
        }
    }
}

void test() {
	// 1.构建邻接表 distMatrix
	vector<int> dist(n, INT_MAX / 2);
	Dijkstra(distMatrix, k - 1, dist);
}

8. bellman ford算法

算法的第k步就是求出的从给定起点到所有节点经过最多k步的最短路

9. 字符运算

#include <ctype.h>
tolower();	// 将大写字符转换为小写字符
isalpha(); 	// 用来判断一个字符是否为字母
isalnum(); 	// 用来判断一个字符是否为数字或者字母,也就是说判断一个字符是否属于a~ z||A~ Z||0~9。
isdigit();	// 用来检测一个字符是否是十进制数字0-9
islower();	// 用来判断一个字符是否为小写字母,也就是是否属于a~z。
isupper();	// 和islower相反,用来判断一个字符是否为大写字母。

10. 填充数组

iota(indexNum2.begin(), indexNum2.end(), 0);

11. INTMAX判断

sum>INT_MAX/10||sum==INT_MAX/10&&r>7

4. 技巧

  1. 完全平方数有奇数个约数,其他的约数个数为偶数个

5. 解题思路

  1. 正难则反
  2. 问题转换
  3. 观察子问题是否与主问题相似(动态规划 / 深度优先)
  4. 数组问题可以先排序
  5. 数字可以分别考虑各个位【233.数字1的个数
  6. 有序数组可以二分查找
  7. 转换为位运算
  8. 自底向上不行就自顶向下
  9. 有一些题目最后会形成一种循环,因此可以记录各个状态【957.N天后的牢房
  10. 组合问题可以使用数组记录并求和【1128.等价多米诺骨牌对的数量
  11. 乱序问题可以先排列【567.字符串的排列
  12. 不要忘了枚举
  13. 可以使用位运算来存放已经出现的大小写字符【1763.最长的美好子字符串
  14. 求中位数可以分两个堆,利用堆顶元素求解【480.滑动窗口中位数】,也可以转换为求第n/2大的元素【4.寻找两个正序数组的中位数
  15. 数组排序只需要对下标进行排序
  16. 对于求子数组局部和的,可以采用前缀和的方式加速【560.和为K的子数组
  17. 做题时如果不能找到符合的情况,可以先剔除掉不符合的情况【862.和至少为K的最短子数组
  18. 找规律题目可以通过打印观察
  19. 数组类问题可以先来一次正序遍历,再来一次倒序遍历【135.分发糖果
  20. DP问题有多种情况可以另外开辟一个维度记录这些情况【1262.可被三整除的最大和
  21. 被num整除:将数据分为0、1…num-1,然后再组合【1262.可被三整除的最大和
  22. 充分利用数据的特点【41. 缺失的第一个正数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值