算法经典题型解析(C++)
前言
本篇文稿为站长阶段性学习笔记,建议读者精通C++包含STL并深入学习数据结构的前提下阅读。
滑动检索/双指针/前缀和
一般和数据结构中的队列相似,符合先进先出规则,区别为需不断更新处理数据
核心:载入💾更新💽弹出🖨️
- 载入💾:下标为
i的元素进入窗口,更新相关统计量。当
窗口载入<窗口长度时即i<k−1,需不断重复执行该步骤填满窗口 - 更新💽:更新答案,检索并处理
在窗数据。一般是更新最大值/最小值/平均值。 - 弹出🖨️:下标为
i−k+1的元素离开窗口,更新相关统计量。
注意事项
1 | double m_age = -1000; // 注意初始化特殊情况 |
例题: 半径为 k 的子数组平均值
给你一个下标从 0 开始的数组 nums ,数组中有 n 个整数,另给你一个整数 k 。
半径为 k 的子数组平均值 是指:nums 中一个以下标 i 为 中心 且 半径 为 k 的子数组中所有元素的平均值,即下标在 i - k 和 i + k 范围(含 i - k 和 i + k)内所有元素的平均值。如果在下标 i 前或> 后不足 k 个元素,那么 半径为 k 的子数组平均值 是 -1 。
构建并返回一个长度为 n 的数组 avgs ,其中 avgs[i] 是以下标 i 为中心的子数组的 半径为 k 的子数组平均值 。
x 个元素的 平均值 是 x 个元素相加之和除以 x ,此时使用截断式 整数除法 ,即需要去掉结果的小数部分。例如,四个元素 2、3、1 和 5 的平均值是 (2 + 3 + 1 + 5) / 4 = 11 / 4 = 2.75,截断后得到 2 。
1 | class Solution { |
二分查找/最值算法
核心:定区🧵收束🪡边界判断🖼️
- 定区🧵:
通常设置三个变量:二分中间值mid、左边界left,右边界right - 收束🪡:不断求取二分值覆盖边界以压缩区间
以中间值mid来将其赋值给左或右区间作边界 - 边界判断🖼️:通常可选闭、开、半开闭区间,其影响
区间定义、收束条件、终止判断
闭区间:边界可与数据覆盖,直接判断收束,重叠即终止
开区间:边界包围数据,判断收束需加减,包围仅一数据终止
1 | int //区间定义 |
1 | // 闭区间写法 |
注意事项
mid是否能将区间均分不重要,应关注区间如何收束1
2
3
4
5
6// 明确循环不变量:
nums[left] //闭区间加减
nums[right] //开区间不便
nums[mid] //由区间类型定义
// 常用二分收束
int mid = left + (right - left) / 2;
例题: 统计公平数对的数目
给你一个下标从 0 开始、长度为 n 的整数数组 nums ,和两个整数 lower 和 upper ,返回 公平数对的数目 。
如果 (i, j) 数对满足以下情况,则认为它是一个 公平数对 :
0 <= i < j < n,且
lower <= nums[i] + nums[j] <= upper例如,输入:nums = [0,1,7,4,4,5], lower = 3, upper = 6 ;输出:6 ;
解释:共计 6 个公平数对:(0,3)、(0,4)、(0,5)、(1,3)、(1,4) 和 (1,5) 。
1 | class Solution { |
递归搜索树/DFS/BFS💎
核心:寻根🔍纵深🥏递归终止🎯
- 寻找根节点,分析问题,来确定根节点如何开始。
- 树型数据结构深度,通常与问题中的数据数目相互挂钩。
- 选择合适的递归终止的判断条件,并记录结果,以减少运算时间。
1
2
3
4
5int fun(int x)
{
//逻辑代码区,递归终止条件
return fun(a) + fun(b) + ...;//树型递归
}
注意事项
不应关注楼梯阶数,应思考每一步的选择,根据选择判断阶数是否达标
在输入数据的问题规模<10^5,两者速度差不多,当问题规模>10^5,cin和cout速度将会慢一倍或更多。1
2
3//输入输出流选择
scanf("%d", &n);
cin >> n;//不推荐
例题:
一个楼梯共有 n级台阶,每次可以走一级或者两级,问从第0级台阶走到第n级台阶一共有多少种方案。
1≤n≤15,输入格式:共一行,包含一个整数 n;输出格式:共一行,包含一个整数,表示方案数。
1 |
|
动态规划(DP)/记忆化搜索💎
核心:暴力🥊记忆🎰动态规划📊
- 暴力递归:
会随着数据量的增大时间复杂度呈指数型上升,最终会有超时的风险。 - 记忆化搜索:
是在暴力递归的基础上对中间过程进行过计算的结果进行保存,以便在下一次递归调用的时候可以直接读取,一般经过记忆化优化,在数据量很大的情况下,效果很明显。 - 动态规划:
动态规划常用来解决方案数和最优解两类问题,当前状态往往都是有前一步子问题的状态推导而来,存在递推公式。1
2
3//记忆化搜索,通常借助哈希表实现对重复数据的检索
Map<Integer, Integer> map = new HashMap<>();
//语法详见《算法基础导读》
例题: 数组返回特定和
给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
暴力搜索dfs+回溯会超时,可以考虑使用dfs+备忘录的模式,一般是优化dfs,也就是记忆化搜索。
1 | class Solution { |
求方案个数,还有一种思路就是利用动态规划的思想,定义dp数组表示 当目标值为n的时候数组中的组合方案数,存在递推公式:f(n) = sum( f(n-i) ) 0<i<=n且需要存在物品重量等于i,则可以循环物品,针对每一个小于等于n的物品,计算f(n-i)值累积求和.
1 | class Solution { |
注意事项
1 | for (int a : x) //快速遍历,a:接收容器,x:遍历数组地址 |
贪心💎
核心:尝试✏️退回⌛最优解⏱️
- 先分析问题,将复杂问题简单化,结合暴力枚举,逐一尝试
- 通常需要根据题意创建两个或多个变量计数,以便可退回至最优解法
- 记录每次枚举是否最优,来共同组成最后答案
例题: 返回最大子序列
给你一个下标从 0 开始的字符串 text 和另一个下标从 0 开始且长度为 2 的字符串 pattern ,两者都只包含小写英文字母。
你可以在 text 中任意位置插入一个字符,这个插入的字符必须是pattern[0]或者pattern[1]。注意,这个字符可以插入在 text 开头或者结尾的位置。
请你返回插入一个字符后,text 中最多包含多少个等于 pattern 的 子序列 。
子序列 指的是将一个字符串删除若干个字符后(也可以不删除),剩余字符保持原本顺序得到的字符串。
没有特判 x=y 的情况,要先更新答案,再更新 cnt_X,这可以保证更新答案时 cnt_X 表示的是当前字母左边的 x 的出现次数,cnt_X 尚未计入当前字母。
1 | class Solution { |
特别鸣谢
思路参考:茶灵山艾府



