题目复习
滑动窗口
模版总结
- 一定是条件满足时,更新最终结果
- 首先最外部的循环
while (j < len) - 接着内部有一个while循环,需要在里面压缩i
- 条件成立进while:最小窗口;更新值;更新条件;
i++ - 条件不成立进while:最大窗口;更新条件;
i++
- 条件成立进while:最小窗口;更新值;更新条件;
- 接着走出while循环
j++
破题思路:窗口应该满足什么条件 or 不满足什么条件!!!
904.水果成篮,结合题解,深入理解:
- 最大滑动窗口 - 模版
1
2
3
4
5
6while j < len(nums):
// 判断[i, j]是否满足条件
while 不满足条件:
i += 1 //(最保守的压缩i,一旦满足条件了就退出压缩i的过程,使得滑窗尽可能的大)
// 不断更新结果(注意在while外更新!)
j += 1 - 最小滑动窗口 - 模版
1
2
3
4
5
6while j < len(nums):
// 判断[i, j]是否满足条件
while 满足条件:
// 不断更新结果(注意在while内更新!)
i += 1 //(最大程度的压缩i,使得滑窗尽可能的小)
j += 1 - 确实,有了模版后,遇到滑窗问题,思路更加清晰了
- 破题思路:窗口应该满足什么条件 or 不满足什么条件!!!
- 最大窗口:2024. 考试的最大困扰度
- 最小窗口:1234. 替换子串得到平衡字符串
- 这个题目还是比较难,需要好好思考转换一下思路!!!
- 窗口应该满足:包含的多的字符的个数应该
>=需要替换的个数
- 最大滑动窗口 - 模版
-
- 需要动脑思考一下,察觉为使用滑动窗口
- 而且在做减法的时候有一个溢出问题:啊啊啊啊啊!!!
字符串哈希
字符串哈希:使用hash映射,减小索引的存储消耗!!!参考
- 选取一个大于字符集大小的质数作为
base - 将字符映射为小于base的数:
int fx = map(x) "abccd" = map(a)*base^0 + map(b)*base^1 + map(c)*base^2 + map(c)*base^3 + map(d)*base^4- 参考
- 选取一个大于字符集大小的质数作为
-
- 这个题目比较典型,再复习一下cpp中的数据类型及范围
- 尝试使用
long long但是超出了范围 - 该用
string直接作为哈希key,但是时间性能较差 - 尝试使用
double,因为其采用浮点表示法,范围更大但是精度差点;有些案例过不去 - 使用
unsigned long long,这是能表示的最大整数范围了,通过了 - 没必要使用map,改为使用unorder_map,时间性能有提升
-
- 对二维坐标点进行hash
- 题目规定坐标值的范围在[0, 40000]
- 因此选取质数40001作为hash的底
- 直接选
pair<int, int>作为key
- 对二维坐标点进行hash
-
- 通过取相反数,将最小子序和转化为最大字序和问题
- 前缀和法
- 贪心 + dp 法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31class Solution {
public:
int maxScore(vector<int>& cardPoints) {
auto maxSubArray = [](vector<int>& nums) {
//实现1:前缀和
// int res = INT_MIN, pre_sum = 0, min_pre_sum = 0;
// for (auto& x : nums) {
// pre_sum += x;
// res = max(res, pre_sum - min_pre_sum);
// min_pre_sum = min(min_pre_sum, pre_sum);
// }
// return res;
//实现2:贪心 + dp
//s = 0考虑了k = 0时的情况
int res = INT_MIN, s = 0;
for (auto& x : nums) {
if (s <= 0) s = 0;
s += x;
res = max(res, s);
}
return res;
};
//累加总和为s
int s = accumulate(cardPoints.begin(), cardPoints.end(), 0);
//将数组的每个元素乘以-1, 用来求连续子数组的最大和
transform(cardPoints.begin(), cardPoints.end(), cardPoints.begin(), [](int x) { return -x; });
//答案 = 总和 - 连续子数组的最小和
return s - (-maxSubArray(cardPoints));
}
};
回文串
- 最长回文子串
- 加深理解:如果字串长度为奇数,则只能为
aba类型;但是当字串长度为偶数时,可以为aa,也可以为abaaba!!! - 自己想到了使用dp数组,记忆化遍历,以减小重复遍历。但是时间表现还是很差❗️❗️
- 注意到回文串的中间部分一定是由连续且相同的字符串组成,因此可以定位到中间字符,再左右扩展查询,参考❗️
- 这样中间部分根据奇偶,可以统一处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37class Solution {
public String longestPalindrome(String s) {
if (s == null || s.length() == 0) {
return "";
}
// 保存起始位置,测试了用数组似乎能比全局变量稍快一点
int[] range = new int[2];
char[] str = s.toCharArray();
for (int i = 0; i < s.length(); i++) {
// 把回文看成中间的部分全是同一字符,左右部分相对称
// 找到下一个与当前字符不同的字符
i = findLongest(str, i, range);
}
return s.substring(range[0], range[1] + 1);
}
public static int findLongest(char[] str, int low, int[] range) {
// 查找中间部分
int high = low;
while (high < str.length - 1 && str[high + 1] == str[low]) {
high++;
}
// 定位中间部分的最后一个字符
int ans = high;
// 从中间向左右扩散
while (low > 0 && high < str.length - 1 && str[low - 1] == str[high + 1]) {
low--;
high++;
}
// 记录最大长度
if (high - low > range[1] - range[0]) {
range[0] = low;
range[1] = high;
}
return ans;
}
}
- 这样中间部分根据奇偶,可以统一处理
- 加深理解:如果字串长度为奇数,则只能为
记忆化搜索
常用技巧
- 得先想出递归的完整方案
- 将递归的参数引入记忆化搜索中
- 记忆化搜索key压缩技术
经典问题
- 3154. 到达第 K 级台阶的方案数
- 想到了递归方案 -> 但是边界条件的处理不适合转化为记忆化
- 想到的递归没有带返回值
- 但是没有想出如何进行递归话搜索
- 边界条件处理的逻辑
- 还是子问题的逻辑没有想好
- 想到了递归方案 -> 但是边界条件的处理不适合转化为记忆化
动态规划
解题参考
从寻找子问题的思路出发
找到dfs各参数
子序列 DP 的思考套路
- 子序列 + 不考虑相邻元素:选或不选。代表题目:494. 目标和(0-1 背包)
- 到达该stage时,该stage对应的结果为下面两种情况的叠加
- 选择该stage代表的内容
- 不选择该stage代表的内容
- 自己想到了全量的递推做法
- 可以采用数学推导:target确定时,前面为
+的和也就确定了,因此题目转化为找物品塞满背包的方法有多少种
- 到达该stage时,该stage对应的结果为下面两种情况的叠加
- 子序列 + 考虑相邻元素:枚举选哪个。代表题目:300. 最长递增子序列
- 到达某个stage时,在前面哪个状态的基础上转移到该状态是最好的?需要枚举选择状态最好的
- 300. 最长递增子序列
- 2901. 最长相邻不相等子序列 II
- 下面这个题目只想到了子序列的回溯做法,但是超时了
- 这两个题目很像,思路很像。第二个题目只是将条件严格递增简单的替换为一个比较复杂的条件;其余思路都是一样的
- 记录节点路径的方法值得仔细回味
- 子序列 + 不考虑相邻元素:选或不选。代表题目:494. 目标和(0-1 背包)
经典问题
-
- 得好好总结一下,怎么动🧠!!!
- 得动动笔啊啊啊啊啊
- 二维前缀+维护列最小
- 维护行列最小值
-
- double, long long等数据类型的区别
-
- 需要动态规划,但是没有想起来
- 计算递推时要严谨一些,好好看看下面对递归的步步推导与解释❗️
- 动态规划有「选或不选」和「枚举选哪个」这两种基本思考方式。在做题时,可根据题目要求,选择适合题目的一种来思考
-
- 这个困难题对于动归非常典型
- 它的递推公式真的很难想到
- 典型的先想到递归/回溯做法,再反推出动规的做法
- 题解:这个题解是比较清楚的
-
- 和上面这个挺像的
- dfs + 记忆化递推
- 想到把前面and的结果传到递归函数中去比较难☹️
- 记忆化递推的记忆化key想的还不到位
- 选或不选!枚举选哪个!
-
- 动态规划递推公式难推导啊!!
- 从推导子问题开始
- 题解
- 这个题解中,想到使用种类数作为
dp[i][j]中的i是有些难度的!!! - 需要较多的
数学思考
- 这个题解中,想到使用种类数作为
-
- 暴力动态规划
匹配 + 全排列
具有明显的匹配意味,使用next_permutation()对其中一个求取全排列,并与另一个进行组合,记录最值
-
- 这题想到这一点还是需要一些思考的:主要注意到均值为1,为0的格子和不为0的格子-1后的和是一致的
-
- 含有重复元素的排列问题
- 非常经典的回溯解法,有一个“去重”的处理值得体会
- 使用used数组进行访问记录,所以遍历当前层时总是从0开始
单调栈
- 402. 移掉 K 位数字
- 题解
- 使用vector模拟栈
杂项
-
- 全排列任务,相同类型任务之间得间隔space,问完成任务的最短时间!
- 需要动脑袋思考一下
- 当时怎么想出来这种纯数学的计算的?->似乎没有证明其正确性
- “贪心数学”
-
- 任务顺序给定,相同任务之间得间隔space,求完成任务的最短时间
- 简单模拟即可
- 自己想到了DP,在DP的基础上优化即可
- 一维数组变为一个变量
- 总结一下这两个题目
-
- 与2972的思路一样
-
- 前缀和,需要动笔简单算一下
-
- 需要思考想到为前缀和
- 简单计算区间公式
-
- 使用并查集
-
- 并查集
-
- 参考题解
- 用哈希表存储每个端点值对应连续区间的长度
- 若数已在哈希表中:跳过不做处理
- 若是新数加入:
- 取出其左右相邻数已有的连续区间长度 left 和 right
- 计算当前数的区间长度为:
cur_length = left + right + 1 - 根据 cur_length 更新最大长度 max_length 的值
- 更新区间两端点的长度值
1 | |
对一个数组求子序列的两种回溯方法差异
不使用for
使用for
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37class Solution {
public:
int result = 0;
void traversal(
vector<int>& nums, int start, int left, int m_min, int last)
{
if (left <= 0)
{
result += m_min;
result %= (1000000000 + 7);
return;
}
for (int i = start; i < nums.size(); i++)
{
if (i + left > nums.size()) break;
int n_min = m_min;
if (last >= 0)
{
n_min = min(m_min, nums[i] - nums[last]);
}
// 注意这里每一次递归都表示选当前元素
traversal(nums, i+1, left-1, n_min, i);
// 针对每个i,到这里都表示不选当前元素
}
}
int sumOfPowers(vector<int>& nums, int k) {
int n = nums.size();
sort(nums.begin(), nums.end());
traversal(nums, 0, k, INT32_MAX, -1);
return result;
}
};