力扣官方解法,依然是熟悉的哈希+前缀和,老己快点理解这个点吧。。。
- 核心逻辑 :我们需要找之前的前缀和的模 ,使得
当前前缀和的模 - 之前前缀和的模 ≡ x (mod p)(即移除的子数组的和的模为x)。变形得:之前前缀和的模 ≡ (当前前缀和的模 - x) (mod p)。加p再取模是为了避免负数(比如y-x为负时,结果仍为正的模)。
cpp
class Solution {
public:
int minSubarray(vector<int>& nums, int p) {
int x = 0;
//计算数组总和对p取模的结果
for (auto num : nums) {
x = (x + num) % p;
}
//总体可被整除
if (x == 0) {
return 0;
}
//初始化哈希表和变量
//index哈希表用于记录前缀和的模和对应的最后出现的索引
unordered_map<int, int> index;
//y:当前前缀和的模(前缀和是数组开头到当前位置的和)
//res:记录需要移除的最短子数组长度,初始化为数组长度(最大可能的移除长度)
int y = 0, res = nums.size();
for (int i = 0; i < nums.size(); i++) {
//将当前前缀和的模y对应的索引i存入哈希表(覆盖旧值,保证记录的是最后位置)
index[y] = i;
//计算新的前缀和(加上当前元素nums[i]),并对p取模,更新y。
y = (y + nums[i]) % p;
//如果哈希表中存在这个 "之前的模",说明从index[目标模]+1到i的子数组的和的模是x。
if (index.count((y - x + p) % p) > 0) {
//计算该子数组的长度(i - 目标模的索引 + 1),并更新res为最小值。
res = min(res, i - index[(y - x + p) % p] + 1);
}
}
//如果res仍等于数组长度,说明需要移除整个数组(题目不允许),返回 - 1。
//否则返回最短子数组的长度res。
return res == nums.size() ? -1 : res;
}
};
示例验证(以示例 1 为例)
示例 1:nums = [3,1,4,2], p=6
- 数组总和的模
x = (3+1+4+2) %6 = 10%6=4。 - 遍历过程:
i=0:index[0]=0→y=(0+3)%6=3→ 目标模(3-4+6)%6=5(哈希表无)。i=1:index[3]=1→y=(3+1)%6=4→ 目标模(4-4+6)%6=0(哈希表有,索引 0)→ 长度1-0+1=2→res=2。i=2:index[4]=2→y=(4+4)%6=2→ 目标模(2-4+6)%6=4(哈希表有,索引 2)→ 长度2-2+1=1→res=1。i=3:index[2]=3→y=(2+2)%6=4→ 目标模(4-4+6)%6=0(哈希表有,索引 0)→ 长度3-0+1=4→res仍为 1。
- 最终返回
1,符合示例 1 的结果。
auto:
在 C++ 中,auto 是一个类型说明符 ,它的核心作用是让编译器自动推断变量的类型,而不需要你显式地写出来。
1. 最基本的用法
当你声明一个变量并立即初始化时,auto 可以根据初始化的值来推断变量的类型。
示例:
cpp
// 传统写法,需要显式指定类型
int x = 10;
double y = 3.14;
std::string str = "Hello";
// 使用 auto,编译器自动推断类型
auto a = 10; // 编译器推断 a 的类型是 int
auto b = 3.14; // 编译器推断 b 的类型是 double
auto c = "Hello"; // 编译器推断 c 的类型是 const char* (在 C++17 及以后,可以是 std::string_view)
auto d = str; // 编译器推断 d 的类型是 std::string
好处:
- 代码更简洁 :当变量类型很长或很复杂时,
auto能极大地简化代码。 - 避免类型错误 :手动写类型容易出错,
auto由编译器推断,更准确。
2. 与迭代器结合(非常常用)
在遍历 STL 容器(如 vector, map, list 等)时,迭代器的类型通常很长。auto 在这里非常有用。
示例:
cpp
#include <vector>
#include <iostream>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
// 传统写法
for (std::vector<int>::iterator it = nums.begin(); it != nums.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 使用 auto 写法,简洁很多
for (auto it = nums.begin(); it != nums.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// C++11 及以后,配合范围 for 循环,更简洁
for (auto num : nums) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
3. auto dfs = ...
cpp
auto dfs = [&](this auto&& dfs, int x, int fa) -> long long { ... };
这里的 auto 有两个作用:
-
推断
dfs变量的类型:dfs变量被赋值为一个lambda 表达式。Lambda 表达式的类型是编译器生成的一个独特的、匿名的函数对象类型。这个类型非常复杂,无法手动写出。- 因此,必须使用
auto来让编译器自动推断dfs的类型。
-
推断 lambda 捕获的
dfs的类型(用于递归):- 这是一个递归的 lambda 表达式 。为了让 lambda 内部能够调用自身,你需要通过
this auto&& dfs的方式来捕获它自己。 - 这里的
auto同样是让编译器推断被捕获的dfs的类型,从而允许递归调用。这是 C++14 引入的特性。
- 这是一个递归的 lambda 表达式 。为了让 lambda 内部能够调用自身,你需要通过
简单来说,在 auto dfs = [&](...) { ... } 这行代码中:
auto告诉编译器:"dfs的类型由等号右边的表达式决定,你自己去推断吧。"- 等号右边是一个 lambda 表达式,所以
dfs的类型就是这个 lambda 的类型。 - 由于 lambda 内部需要调用自己,所以捕获列表
[&]和this auto&& dfs一起工作,让这个递归成为可能。
总结
auto 的核心就是类型推断 。它让代码更简洁、更安全,尤其是在处理复杂类型或迭代器时。在现代 C++ 编程中,auto 被广泛使用。