适合 10-12 岁学生的互动讲解版:先学会排序 + 枚举,再认识双指针是后续优化。
这道题里,你给的做法更适合当课堂主线。因为 数据范围只有 n ≤ 1000,所以 O(n²) 完全能过,而且思路更直观。
主线先讲“排序后固定左端点,再往右试”。学生真的听懂以后,再补一句“双指针可以更快”,节奏会更稳。
排序后,价值接近的宝箱会排在一起。这样我们更容易判断:哪些宝箱能一起拿。
排序后,如果一段区间两端满足 a[j] - a[i] ≤ k,那么中间所有数也都合法。又因为每个价值都是正整数,所以中间的宝箱当然也应该一起拿走。
所以这题等价于:找一个连续区间,让区间最大值减最小值不超过 k,并且区间和最大。
样例输入:5 1 和 1 2 3 1 2。排序后变成 1 1 2 2 3。
点击下面的按钮,一步一步看程序怎么做。
准备开始:先固定左端点,再往右尝试。
当前判断:a[j] - a[i] ≤ k ?
当前区间和:0
a[j] - a[i] > k,后面的数还要不要继续看?
n ≤ 1000,O(n²) 能不能过?
| 左端点 | 能选的区间 | 区间和 |
|---|---|---|
i = 1 | 1 1 2 2 | 6 |
i = 2 | 1 2 2 | 5 |
i = 3 | 2 2 3 | 7 |
i = 4 | 2 3 | 5 |
i = 5 | 3 | 3 |
最后答案是 7。 也就是拿走 2, 2, 3。
#include <bits/stdc++.h>
using namespace std;
const int N = 1005;
int a[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a + 1, a + n + 1);
int ans = 0;
for (int i = 1; i <= n; i++) {
int sum = 0;
for (int j = i; j <= n; j++) {
if (a[j] - a[i] <= k) {
sum += a[j];
} else {
break;
}
}
ans = max(ans, sum);
}
cout << ans << '\n';
return 0;
}
因为它每一步都看得见:排序、固定左端点、往右试、累加总和、更新答案。学生可以直接对着样例手推代码。
当学生已经熟悉“连续区间”和“越往后越大”这两个事实后,再讲双指针会很顺。否则他们容易只记代码,不理解为什么这样移动指针。
前面的枚举法,每换一个左端点,都要重新往右试一遍。双指针会更省事:左右指针都只往右走,不回头。
课堂顺序还是不变:先学会枚举,再认识优化。
排序后,用一个窗口 [L, R] 表示当前选中的连续区间。
R 往右走,把新宝箱加进来。a[R] - a[L] > k,说明区间不合法。L 往右挪,同时把左边移出的值从总和里减掉。每个位置最多只会被 R 扫到一次,也最多只会被 L 移出一次,所以扫描部分是线性的。
#include <bits/stdc++.h>
using namespace std;
const int N = 1005;
int a[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a + 1, a + n + 1);
int ans = 0;
int sum = 0;
int L = 1;
for (int R = 1; R <= n; R++) {
sum += a[R];
while (a[R] - a[L] > k) {
sum -= a[L];
L++;
}
ans = max(ans, sum);
}
cout << ans << '\n';
return 0;
}
第一句:R 每次右移,把新宝箱加入窗口。
第二句:如果区间不合法,就移动 L,直到重新合法。
第三句:每次窗口合法时,用 sum 更新 ans。
| 做法 | 思路难度 | 复杂度 | 建议 |
|---|---|---|---|
| 排序 + 枚举 | 更直观 | O(n²) |
第一次讲这题时用 |
| 排序 + 双指针 | 稍抽象 | O(n log n) 总体O(n) 扫描部分 |
当作后半段优化 |