蓝桥杯真题 --- 附近最小

题目

问题描述

小蓝有一个序列 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++1s256M
C1s256M
Java3s256M
Python312s512M
PyPy312s512M

解题

分析题目

  1. 给定一个正整数 k,请问对于每一个 1 到 n 之间的序号 i,a[i−k],a[i−k+1],…,a[i+k] 这2k+1 个数中的最小值是多少?
    • 在序号为 i 附近找最小值时,数据范围:序号i 位置上,左边 k 个数据,右边 k 个数据,及 序号 i 本身的数据 a[i]
  2. 当某个下标超过 1 到 n 的范围时,数不存在,求最小值时只取存在的那些值。
    • 说明有寻找过程可能会出现数组下标越界的情况

解题

  1. 因此,寻找办法可以使用如下思路:
    • 从序号 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};  
	    }  
	}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值