算法-18最大值减去最小值小于或等于num的子数组数量
描述
给定数组 arr 和整数 num,共返回有多少个子数组满足如下情况:
max(arr[i...j]) - min(arr[i...j]) <= num
max(arr[i...j])表示子数组arr[i...j]中的最大值,min[arr[i...j])表示子数组arr[i...j]中的最小值。
输入描述:
第一行输入两个整数 n 和 m,代表 n*m 的矩阵
接下来输入一个 n*m 的矩阵
接下来输入一个 n*m 的矩阵
输出描述:
输出给定数组中满足条件的子数组个数
示例1
输入:
5 2 1 2 3 4 5
输出:
12
思路
首先介绍普通的解法,找到arr的所有子数组,一共有O(N^2) 个,然后对每一个子数组做遍历找到其中的最小值和最大值,这个过程的时间复杂度为O(N),然后看看这个子数组是否满足条件,统计所有满足的子数组数量即可,普通解法容易实现,但是时间复杂度为O(N^3),本次不再详述,最优解可以做到时间复杂度为O(N),额外空间复杂度为O(N),在阅读下面的分析过程之前,请读者先阅读“生成窗口最大值数组”问题,本题所使用到的双端队列结构与解决“生成窗口最大值数组”问题中的双端队列结构含义基本一致。
生成两个双端队列qmax和qmin。当子数组为arr时,qmax维护了窗口子数组arr的最大值更新的结构,qmin维护了窗口子数组arr的最小值更新的结构。当子数组arr[i..j]向右扩一个位置变成arr[i..j+1]时,qmax和qmin结构更新代价的平均时间复杂度为O(1),并且可以在O(1)的时间内得到arr[i..j+1]的最大值和最小值,当子数组arr[i..j]向左缩一个位置变成arr[i+1..j]时,qmax和qmim结构更新代价的平均时间复杂度为O(1),并且在O(1)的时间内得到arr[i+1..j]的最大值和最小值。
通过分析题目满足的条件,可以得到如下两个结论。
● 如果子数组arr[i..j]满足条件,即 max(arr[i..j]) - min(arr[i..j] <= num,那么 arr[i..j]中的每一个子数组,即arr[k..l](i <= k <=l <= j)都满足条件。我们以子数组arr[i..j-1]为例说明,arr[i..j-1]最大值只可能小于或等于arr[i..j]的最大值,arr[i..j-1]最小值只可能大于或等于arr[i..j]的最小值,所以arr[i..j-1]必然满足条件。同理,arr[i..j]中的每一个子数组都满足条件。
● 如果子数组arr[i..j]不满足条件,那么所有包含arr[i..j]的子数组,即arr[k..l] (k <= i <= j <= l)都不满足条件。证明过程同第一个结论。
根据双端队列qmax和qmin的结构性质,以及如上两个结论,设计整个过程如下:
1.生成两个双端队列qmax和qmin。含义如上文所说,生成两个整型交量 i 和 j ,表示子数组的范围,即arr[i..j]生成整型变量res,表示所有满足条件的子数组数量。
2.令 j 不断向右移动(j++),表示arr[i..j]一直向右扩大,并不断更新qmax和qmin结构,保证qmax和qmin始终维持动态窗口最大值和最小值的更新结构。一旦出现arr[i..j]不满足条件的情况,j 向右扩的过程停止,此时arr[i..j-1]、arr[i..j-2]、arr[i..j-2]...arr[i..i]一定都是满足条件的。也就是说所有必须以arr[i+1]作为第一个元素的了数组,满足条件的数量为 j - i个,于是令 res += j-i。
3.当进行完步骤2,令 i 向右移动一个位置,并对 qmax 和 qmin 做出相应的更新,qmax 和 qmin 从原来的 arr[i..j] 窗口变成arr[i+1..j]窗口的最大值和最小值的更新结构。然后重复步骤2,也就是求所有必须以arr[i+1]作为第一个元素的子数组中,满足条件的数量有多少个。
4.根据步骤2和步骤3,依次求出:必须以arr[0]开头的子数组,满足条件的数量有多少个;必须以arr[1]开头的子数组,满足条件的数量有多少个;必须以arr[2]开头的子数组,满足条件的数量有多少个,全部累加起来就是答案。
上述过程中,所有的下标值最多进qmax和qminー次,出qmax和qmin一次,i 和 j 值也不断增加,并且从来不减小。所以整个过程的时复杂度为O(N)。
生成两个双端队列qmax和qmin。当子数组为arr时,qmax维护了窗口子数组arr的最大值更新的结构,qmin维护了窗口子数组arr的最小值更新的结构。当子数组arr[i..j]向右扩一个位置变成arr[i..j+1]时,qmax和qmin结构更新代价的平均时间复杂度为O(1),并且可以在O(1)的时间内得到arr[i..j+1]的最大值和最小值,当子数组arr[i..j]向左缩一个位置变成arr[i+1..j]时,qmax和qmim结构更新代价的平均时间复杂度为O(1),并且在O(1)的时间内得到arr[i+1..j]的最大值和最小值。
通过分析题目满足的条件,可以得到如下两个结论。
● 如果子数组arr[i..j]满足条件,即 max(arr[i..j]) - min(arr[i..j] <= num,那么 arr[i..j]中的每一个子数组,即arr[k..l](i <= k <=l <= j)都满足条件。我们以子数组arr[i..j-1]为例说明,arr[i..j-1]最大值只可能小于或等于arr[i..j]的最大值,arr[i..j-1]最小值只可能大于或等于arr[i..j]的最小值,所以arr[i..j-1]必然满足条件。同理,arr[i..j]中的每一个子数组都满足条件。
● 如果子数组arr[i..j]不满足条件,那么所有包含arr[i..j]的子数组,即arr[k..l] (k <= i <= j <= l)都不满足条件。证明过程同第一个结论。
根据双端队列qmax和qmin的结构性质,以及如上两个结论,设计整个过程如下:
1.生成两个双端队列qmax和qmin。含义如上文所说,生成两个整型交量 i 和 j ,表示子数组的范围,即arr[i..j]生成整型变量res,表示所有满足条件的子数组数量。
2.令 j 不断向右移动(j++),表示arr[i..j]一直向右扩大,并不断更新qmax和qmin结构,保证qmax和qmin始终维持动态窗口最大值和最小值的更新结构。一旦出现arr[i..j]不满足条件的情况,j 向右扩的过程停止,此时arr[i..j-1]、arr[i..j-2]、arr[i..j-2]...arr[i..i]一定都是满足条件的。也就是说所有必须以arr[i+1]作为第一个元素的了数组,满足条件的数量为 j - i个,于是令 res += j-i。
3.当进行完步骤2,令 i 向右移动一个位置,并对 qmax 和 qmin 做出相应的更新,qmax 和 qmin 从原来的 arr[i..j] 窗口变成arr[i+1..j]窗口的最大值和最小值的更新结构。然后重复步骤2,也就是求所有必须以arr[i+1]作为第一个元素的子数组中,满足条件的数量有多少个。
4.根据步骤2和步骤3,依次求出:必须以arr[0]开头的子数组,满足条件的数量有多少个;必须以arr[1]开头的子数组,满足条件的数量有多少个;必须以arr[2]开头的子数组,满足条件的数量有多少个,全部累加起来就是答案。
上述过程中,所有的下标值最多进qmax和qminー次,出qmax和qmin一次,i 和 j 值也不断增加,并且从来不减小。所以整个过程的时复杂度为O(N)。
代码如下:
import java.util.LinkedList; import java.util.Scanner; public class Main{ public static int getNum(int[] arr,int num) { if(arr==null || arr.length == 0) return 0; LinkedList<Integer> qmin = new LinkedList<>(); LinkedList<Integer> qmax = new LinkedList<>(); int i=0,j=0,res=0; while(i<arr.length) { while(j<arr.length) { if(qmin.isEmpty()||qmin.peekLast()!=j){ while(!qmin.isEmpty()&& arr[qmin.peekLast()]>=arr[j]) { qmin.pollLast(); } qmin.addLast(j); while(!qmax.isEmpty()&& arr[qmax.peekLast()]<=arr[j]){ qmax.pollLast(); } qmax.addLast(j); } if(arr[qmax.getFirst()] - arr[qmin.getFirst()]>num) break; j++; } res +=j - i; if(qmin.peekFirst()==i) qmin.pollFirst(); if(qmax.peekFirst()==i) qmax.pollFirst(); i++; } return res; } public static void main(String args[]) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); int num = sc.nextInt(); int[] arr = new int[n]; for(int i=0;i<n;i++) { arr[i] = sc.nextInt(); } int res = getNum(arr,num); System.out.println(res); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix