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