题目
问题描述
小蓝有一个序列 a[1],a[2],…,a[n]。
给定一个正整数 k,请问对于每一个 1 到 n 之间的序号 i,a[i−k],a[i−k+1],…,a[i+k] 这2k+1 个数中的最小值是多少?
当某个下标超过 1 到 n 的范围时,数不存在,求最小值时只取存在的那些值。
输入格式
输入的第一行包含一整数 n。
第二行包含 n 个整数,分别表示 a[1],a[2],…,a[n]。
第三行包含一个整数 k 。
输出格式
输出一行,包含 n 个整数,分别表示对于每个序号求得的最小值。
样例输入
5
5 2 7 4 3
1
样例输出
2 2 2 3 3
评测用例规模与约定
对于 30% 的评测用例,1<=n<=1000,1<=a[i]<=1000。
对于 50% 的评测用例,1<=n<=10000,1<=a[i]<=10000。
对于所有评测用例,1<=n<=1000000,1<=a[i]<=1000000。
运行限制
语言 | 最大运行时间 | 最大运行内存 |
---|---|---|
C++ | 1s | 256M |
C | 1s | 256M |
Java | 3s | 256M |
Python3 | 12s | 512M |
PyPy3 | 12s | 512M |
解题
分析题目
- 给定一个正整数 k,请问对于每一个 1 到 n 之间的序号 i,a[i−k],a[i−k+1],…,a[i+k] 这2k+1 个数中的最小值是多少?
- 在序号为 i 附近找最小值时,数据范围:序号i 位置上,左边 k 个数据,右边 k 个数据,及 序号 i 本身的数据 a[i]
- 当某个下标超过 1 到 n 的范围时,数不存在,求最小值时只取存在的那些值。
- 说明有寻找过程可能会出现数组下标越界的情况
解题
- 因此,寻找办法可以使用如下思路:
- 从序号 i 开始,假设最小值为 a[i],先向左寻找,并依次和 最小值比较,当超出数组下标时,退出遍历;再向右寻找,寻找方式同上。
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] ints = new int[n];
int i;
for(i = 0; i < n; i++){
ints[i] = sc.nextInt();
}
int k = sc.nextInt();
int[] mins = new int[n];
for(i = 0; i < n; i++){
int min = ints[i];
for(int j = i - 1; j >= i - k; j--) {
if(j < 0) {
break;
}
min = Math.min(min, ints[j]);
}
for(int j = i + 1; j <= i + k; j++) {
if(j >= n) {
break;
}
min = Math.min(min, ints[j]);
}
mins[i] = min;
}
for(i = 0; i < n; i++){
System.out.print(mins[i] + " ");
}
}
- 提交测试:运行超时
优化
- 在循环遍历时,在判断数组是否越界时花费了很多的时间
- 解决办法:
- 在遍历之前提前判断是否会出现数组越界情况。
- 如何判断是否会出现数组越界情况?
- 左边: i - k 是否 小于 0
- 右边: i +k 是否 大于或等于 n
- 当不会出现数组越界时,遍历范围在 i - k 到 i + k 之间
- 当出现数组越界时,遍历范围在由越界点决定:
- 当数组在左边越界,则数组下标最小为 0
- 当数组在右边越界,则数组下标最大为 n
- 解决办法:
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] ints = new int[n];
int i;
for(i = 0; i < n; i++){
ints[i] = sc.nextInt();
}
int k = sc.nextInt();
int[] mins = new int[n];
for(i = 0; i < n; i++){
int min = ints[i];
int left = i - k, right = i + k;
if(left < 0) {
left = 0;
}
if(right >= n) {
right = n - 1;
}
for(int j = left; j <= right; j++) {
min = Math.min(min, ints[j]);
}
mins[i] = min;
}
for(i = 0; i < n; i++){
System.out.print(mins[i] + " ");
}
}
- 运行测试:运行超时
再优化
- 通过分析可以发现:在这个寻找过程中,还是有很多重复的操作
- 比如:在 a[1] a[2] a[3] a[4] a[5] 中, 如果知道a[2] 附近的最小值,那么在寻找a[3] 附近最小值时,可以参考a[2] 附近的最小值
- 具体实现如下:
- 因为 a[2] 是在 a[1] a[2] a[3] 中取得的最小值,而 a[3] 是在 a[2] a[3] a[4] 中取的的最小值
- 通过分析可以发现,a[3] 的最小值是和 a[2] 的最小值是有关联的
- 关联如下:
- 假设已知 a[2] 的最小值且最小值不是a[1],那么 a[3]min = Math.min(a[2]min,a[4])
- 假设 a[2]的最小值是a[1],那么a[3]min = Math.min(a[2],a[3],a[4])
- 具体实现如下:
- 根据分析,抽象出关联公式,如下所示:
- 当 a[i - 1]min != a[i - 1 - k] 时,a[i]min = Math.min(a[i - 1]min,a[i + k])
- 当a[i - 1]min == a[i - 1 - k] 时,a[i]min = Math.min(a[i - k], … ,a[i + k])
上代码
package 蓝桥杯题库题解;
import java.util.Scanner;
public class 附近最小 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] ints = new int[n];
int i;
for(i = 0; i < n; i++){
ints[i] = sc.nextInt();
}
int k = sc.nextInt();
int[] mins = new int[n];
int index = 0; // 记录 ints[i] 的最小值的下标
// 初始化ins[0]的最小值
mins[0] = ints[0];
for(i = 1; i <= k && i < n; i++) {
// 注意这里需要考虑相等的情况,这样可以使得最小值尽可能靠前,保证数据的准确性
if(mins[0] >= ints[i]) {
index = i;
mins[0] = ints[i];
}
}
for(i = 1; i < n; i++){
int left = i - k, right = i + k;
if(left < 0) {
left = 0;
}
if(right >= n) {
right = n - 1;
}
if(index != i - 1 - k) {
// 当 a[i - 1]min != a[i - 1 - k] 时,a[i]min = Math.min(a[i - 1]min,a[i + k])
if(mins[i - 1] >= ints[right]) {
mins[i] = ints[right];
index = right;
continue; }
mins[i] = mins[i - 1];
}
else {
// 当a[i - 1]min == a[i - 1 - k] 时,a[i]min = Math.min(a[i - k], ... ,a[i + k])
int[] t = findMin(left, right, ints);
mins[i] = t[0];
index = t[1];
}
}
for(i = 0; i < n; i++){
System.out.print(mins[i] + " ");
}
}
// 在给定的区间[left,right]寻找最小值,返回值:a[0] --> 最小值 a[1] --> 最小值的下标
public static int[] findMin(int left, int right, int[] ints) {
int min = ints[left];
int index = left;
for(int j = left + 1; j <= right; j++) {
if(min >= ints[j]) {
index = j;
min = ints[j];
}
}
return new int[] {min, index};
}
}