嗯,这道题看起来是要找一个n×n的正方形区域,使得该区域内最大值和最小值的差最小。那我得仔细看看题目要求。题目给了一个a×b的矩阵,要从中找出所有可能的n×n正方形,然后计算每个这样的正方形的max-min,最后取所有中的最小值。
首先,我得想到怎么高效地计算每个n×n窗口的最大和最小值。因为直接遍历每个窗口然后找最大和最小的话,时间复杂度会很高。比如a和b都是1000的话,n是100的话,这样的窗口数量大概是(1000-100+1)^2 = 901901≈81万,每个窗口需要100100=1万次比较,总的时间复杂度是81万*1万=81亿次操作,这肯定会超时。
所以,必须得找一个更高效的方法。这时候,可能想到用单调队列。单调队列在滑动窗口问题中常用,可以快速维护窗口内的最大值或最小值。那如何将二维的问题转化为一维的问题呢?
通常的做法是先对每一行用单调队列处理,得到每个位置向右n个元素的最大和最小值,这样每个行的每个窗口的横向极值就处理好了。然后,再对列进行处理,将纵向的n个行的极值再次用单调队列处理,得到最终的二维窗口的极值。
具体来说,比如对于每一行,我们可以预处理出每个位置j的向右n长度的窗口的最大值和最小值,保存下来。这样,对于每个行来说,处理完后,每个位置的横向窗口极值被记录下来。然后,对于每一列,我们处理这些极值数组,纵向的n长度的窗口的最大和最小值。这样,每个二维窗口的极值就是纵向处理后的结果。这样就能得到每个n×n窗口的max和min,然后计算差值,取最小即可。
那具体怎么实现呢?比如,假设原矩阵是g[a][b],首先对每一行,预处理出每行的滑动窗口最大和最小。这一步可以用单调队列。比如,对于行i,得到两个数组rmax[i][j]和rmin[i][j],表示在i行中,以j为左端点(或者右端点,这要看窗口的方向)的n长度的窗口的最大和最小值。这里需要注意的是,当处理行的时候,每个窗口的长度是n,所以每个行的处理后的数组长度应该是b - n + 1吗?或者说,每个位置j在行i中的横向窗口的最大值会被存储到rmax[i][j],其中j的范围可能从0到b-n的位置?
假设原矩阵的行数是a,列是b,n是窗口边长。对于每一行来说,处理后的横向窗口的列数是b - n +1。例如,行有b个元素,每个窗口起始位置是0到b-n的位置,所以总共有b-n+1个窗口。因此,rmax和rmin的行数还是a,但列数是b-n+1吗?
或者,可能更简单的处理方式是,每个位置的横向窗口的最大值被保存到同一行中的相应位置。比如,对于原矩阵中的每个元素g[i][j],如果在该行中,以j为右端点的窗口的最大值被保存到rmax[i][j]。那么,当处理完所有行之后,每个行的每个位置j的rmax[i][j]是包含该位置在内向左n个元素的最大值?或者向右?
这里需要明确窗口的移动方向。例如,对于每一行来说,窗口是向右移动的。所以,当处理每一行的时候,我们使用单调队列维护当前窗口的最大值。窗口的右端是当前元素的位置j。当窗口的大小超过n时,就需要将队首的元素弹出。这样,对于每个j,窗口的左端是j -n +1的位置。例如,当j >=n-1时,窗口的有效位置是[j-n+1, j],所以对于每个i,处理到j的位置时,当j >=n-1时,可以记录该窗口的最大值到rmax[i][j -n +1]的位置?
或者,可能更直接的方式是,对于每一行i,预处理出每一列j的窗口(从j到j+n-1)的最大值。但是这样的话,当j +n -1超过b-1时,就无法处理。所以正确的做法是,对每一行i,处理每个可能的窗口的起始列j,然后得到该窗口的最大和最小值,保存在rmax[i][j]和rmin[i][j]中。其中j的范围是0 <=j <=b-n。这时候,对于每一行来说,处理后的列数变为b-n+1。
这可能比较复杂。那如何用单调队列处理每一行呢?例如,假设当前处理行i,那么对于该行的每个元素,维护一个单调递减队列,当窗口滑动时,队列中的元素是可能的最大值的候选。这样,当窗口右移时,队列中保存的是当前窗口中的元素的下标,且对应的值是递减的。这样,每个窗口的最大值就是队首元素的值。同样的,最小值可以用单调递增队列来处理。
那么,对每一行处理之后,得到的rmax和rmin数组的列数是b -n +1。例如,原矩阵的列数是b,处理后每个行i对应的列数是b -n +1。然后,接下来处理列的时候,对于每一列j,在rmax数组的这一列j上,处理每一行的窗口,得到每个位置i的纵向窗口的最大值。这样,最终每个n×n窗口的最大值就是两次处理后的结果。
例如,假设原矩阵是a行b列,n是窗口的边长。首先,对每一行进行横向处理,得到rmax的每个行的列数是b-n+1。所以,处理后的rmax数组是a行,(b-n+1)列。然后,对rmax的每一列进行处理,纵向处理长度为n的窗口,得到每个位置的最大值。这样,最终的二维窗口的最大值数组的行数是a-n+1,列数是b-n+1。同理,最小值数组也是如此处理。然后,遍历所有这样的位置,计算max - min,找出其中的最小值。
所以,整个过程的时间复杂度应该是O(a*b)(每一行的处理是O(b),共a行;然后对每一列的处理是O(a) * (b-n+1)列,所以总的时间复杂度是O(ab + a(b-n+1)) )=O(ab)。对于a和b都是1000的话,这样的复杂度是可以接受的。
那现在的问题是如何具体实现这个过程。例如,在C++中,如何处理这些二维数组?
可能的实现步骤:
-
输入a, b, n,然后读入a行,每行b个数,保存到g数组中。
-
预处理每一行的横向窗口的最大和最小值,得到rmax和rmin数组。这两个数组的行数是a,列数是b -n +1。
-
预处理每一列的纵向窗口的最大和最小值。对于每一列j,在rmax数组中,这一列的所有行的数据形成一个列向量,对其进行纵向窗口处理,得到最终的每个二维窗口的最大值。同样的,对rmin进行处理,得到每个二维窗口的最小值。
-
遍历所有可能的二维窗口的位置,计算max - min,找到最小的那个差值。
具体来说,如何对每一行处理横向窗口的最大和最小值?
例如,对于一行来说,假设该行的元素是row[0…b-1]。我们需要找到每个起始位置为j的窗口的最大值,其中窗口长度为n,j的范围是0 <=j <=b-n。
这可以通过单调队列来实现。例如,维护一个双端队列deque,保存的是当前窗口内的元素的下标,且对应的元素值单调递减。当处理到元素i时:
-
如果队列中的队首元素的下标小于等于i -n,则弹出队首(因为窗口已经移动,这些元素已经不在当前窗口内)。
-
然后,将当前元素i与队列尾部元素比较,如果当前元素大于等于尾部元素对应的值,则弹出尾部,直到队列为空或者遇到比当前元素大的元素。然后将i加入队列尾部。
-
当i >=n-1时,窗口形成,此时队首元素对应的值就是当前窗口的最大值。
这样,对于每一行来说,处理的时间是O(b),每个元素进出队列一次。
同样的,对于最小值处理,维护一个单调递增的队列。
这样,处理每一行的横向窗口的时间是O(b)。处理所有行的横向窗口的时间是O(ab)。
接下来,处理纵向窗口。假设我们现在有一个数组col_max,其行数是a,列数是b-n+1。我们需要对每一列进行处理,每个列的长度是a,需要处理每个纵向窗口的长度为n的窗口的最大值。例如,对于每一列j,处理所有可能的窗口,得到每个窗口的最大值,保存到最终的max_val数组中。这同样可以用单调队列,处理每一列的时间是O(a),总共有(b-n+1)列,所以总的时间是O(a*(b-n+1))),因为当a=1000,b=1000,n=100时,b-n+1=901,所以总时间大概是1000*901=约9e5次操作,这应该是可以接受的。
同样的,对rmin数组进行纵向处理,得到每个二维窗口的最小值。
最后,遍历所有可能的二维窗口的位置,计算对应的max_val[i][j] - min_val[i][j],并找出最小值。
综上,这就是使用两次单调队列处理的方法:第一次处理行,第二次处理列。这样,整个问题的时间复杂度是线性的,可以应对题目中的最大数据范围。
那现在需要考虑代码的具体实现。例如,如何存储原始矩阵?如何预处理行,然后列?
假设在C++中,原始矩阵存储为一个二维数组,比如vector<vector> g(a, vector(b))。
然后,预处理每一行的横向窗口的max和min,得到两个二维数组rmax和rmin。它们的行数是a,列数是b -n +1。
然后,预处理每一列的纵向窗口的max和min,得到最终的二维数组max_dp和min_dp,它们的行数是a -n +1,列数是b -n +1。每个元素对应原矩阵中的n×n窗口的max和min。
最后,遍历max_dp和min_dp中的每个元素,计算差值,找到最小值。
那么,如何实现行的预处理?
举个例子,处理行的横向窗口的max:
对于每一行i,遍历该行的每个元素j:
维护一个deque,保存当前窗口内可能成为最大值的元素的下标。窗口的范围是[j -n +1, j]。
比如,当处理到元素j时,窗口的左端是j -n +1。所以,当队列的头部元素的下标小于j -n +1时,需要弹出。
然后,将当前元素j的值与队列尾部的元素比较,如果当前元素更大,则弹出尾部。循环直到队列为空或者遇到更大的元素。
然后将j加入队列。
当j >=n-1时,说明窗口已经形成,此时的队首元素对应的值就是该窗口的最大值。将该值存入rmax[i][j -n +1]的位置?或者,假设rmax的行数与原矩阵相同,但每行只有b -n +1个元素?
或者,可能更直观的是,当处理完每一行之后,该行的横向窗口的max数组的长度是b -n +1。例如,对于行i,rmax[i]的长度是b -n +1,每个元素对应一个起始位置j的窗口的最大值。
或者,在代码实现中,对于每一行i,先处理得到该行的横向窗口的max数组,然后保存到rmax[i]中。例如:
vector<vector> rmax(a, vector(b -n +1));
同样的,rmin也是如此。
所以,对于每一行i,生成对应的rmax[i]数组的过程:
初始化一个deque,然后遍历该行的每个元素j(从0到b-1)。
当j >=n-1时,可以开始记录窗口的最大值。此时窗口的起始位置是j -n +1,对应到rmax[i]中的位置是j -n +1,即rmax[i][j -n +1] = row[deque.front()].
或者,假设每个窗口的起始位置是k,那么k的范围是0 <=k <=b-n。对应的结束位置是k +n -1。
因此,当处理到元素j=k +n -1时,窗口k的结束位置是j,此时可以记录最大值到rmax[i][k]。这样,对于每个k,j的范围是从0到b-1,当k +n-1 ==j时,处理窗口k的最大值。
所以,这可能需要循环每个k,或者在处理元素j时,当j >=n-1时,对应的k=j -n +1。因此,在处理j时,如果j >=n-1,那么k的取值范围是0到j-n+1,这可能吗?
可能更简单的方法是,当处理到每个j时,维护一个窗口,窗口的右端是j,窗口的长度是n。因此,窗口的有效条件是从j -n +1到j的区间。当j >=n-1时,窗口有效。此时,队列中的头元素就是该窗口的最大值,所以可以将该值保存到rmax[i][j -n +1]。
例如,在处理行i时:
deque q;
for(int j=0; j <b; j++){
// 移除队列中不在窗口内的元素的下标
while(!q.empty() && q.front() <= j -n){
q.pop_front();
}
// 维护单调递减队列
while(!q.empty() && row[j] >= row[q.back()]){
q.pop_back();
}
q.push_back(j);
// 当j >=n-1时,可以记录当前窗口的最大值
if(j >=n-1){
rmax[i][j -n +1] = row[q.front()];
}
}
这样,对于每一行i来说,处理完成后,rmax[i]的长度是b -n +1,每个元素对应起始位置k=j-n+1的窗口的最大值。
同样的,处理rmin[i]的时候,维护单调递增队列。
这样,行的处理就完成了。
然后,处理列的时候,对于每个列j(这里的j指的是横向处理后的列,即原列数经过横向处理后的列数为cols =b-n+1),处理每个列j的纵向窗口。
同样的,纵向窗口的长度是n。例如,对于列j,处理每个行i的rmax[i][j],维护一个纵向窗口的最大值。
此时,纵向窗口的处理方式与横向类似,但处理的是列数据。例如,对于每个列j,在rmax数组的该列中,行的数量是a,需要处理每个可能的纵向窗口的最大值,保存到max_dp数组中。
所以,处理列j时:
初始化一个deque,遍历行i从0到a-1。
对于每个i,维护一个窗口,窗口大小为n,当i >=n-1时,窗口的有效位置是i-n+1到i,此时记录该窗口的最大值到max_dp[i-n+1][j]中。
同样的,处理列j的每个元素i:
max_dp的行数是a -n +1,列数是cols =b -n +1.
所以,处理列j的代码大致如下:
vector<vector> max_dp(a -n +1, vector(cols));
for(int j=0; j <cols; j++){
deque<int> q;
for(int i=0; i <a; i++){
while(!q.empty() && q.front() <= i -n){
q.pop_front();
}
while(!q.empty() && rmax[i][j] >= rmax[q.back()][j]){
q.pop_back();
}
q.push_back(i);
if(i >=n-1){
max_dp[i -n +1][j] = rmax[q.front()][j];
}
}
}
同样的,处理rmin得到min_dp。
最后,遍历max_dp和min_dp的所有元素,计算差值,找到最小值。
这样,整个问题就解决了。
那在代码实现中,需要注意的问题:
-
矩阵的行和列的索引是否正确处理。
-
处理横向和纵向时的窗口范围是否正确。
-
内存是否足够。例如,当a和b都是1000的时候,rmax和rmin的每个行是1000-100+1=901列,所以每个数组的大小是1000*901=约9e5,这在C++中是可行的。
此外,输入的数值可能很大,但比较的时候不需要担心溢出,因为都是整数比较。
现在,测试样例的输入:
输入样例1:
5 4 2
1 2 5 6
0 17 16 0
16 17 2 1
2 10 2 1
1 2 2 2
输出的结果是1。
我们可以手工验证一下。例如,在输入样例中,a=5,b=4,n=2。所以,每个可能的2x2窗口的max-min的最小差是1。比如,原矩阵中的某个窗口的最大和最小值相差1。
现在,让我们看看如何处理这个样例:
原矩阵是5行4列:
第一行:1 2 5 6
第二行:0 17 16 0
第三行:16 17 2 1
第四行:2 10 2 1
第五行:1 2 2 2
处理行横向窗口的max和min:
对于每一行,每个窗口长度为2。
比如,第一行的横向窗口:
窗口0-1:最大值是2,5,6?或者原行是1,2,5,6。那么每个横向窗口是:
窗口0: [1,2] → max=2,min=1
窗口1: [2,5] → max=5,min=2
窗口2: [5,6] → max=6,min=5 → 所以rmax[0] = [2,5,6], rmin[0] = [1,2,5]
其他行同理。例如,第二行:
0,17,16,0 → 窗口0:0,17 → max17,min0;窗口1:17,16 → max17,min16;窗口2:16,0 → max16,min0 →所以rmax[1]是17,17,16;rmin[1]是0,16,0。
然后,处理列,比如,对rmax的每一列进行纵向窗口处理。例如,假设对于rmax的第一列:
各行的值分别是2(第一行)、17(第二行)、17(第三行)、10(第四行)、2(第五行)。
当处理纵向窗口大小为2时,窗口的max如下:
窗口0:2和17 →max17
窗口1:17和17 →max17
窗口2:17和10 →max17
窗口3:10和2 →max10
所以,max_dp的第一列是17,17,17,10。然后对应的min_dp中的同一列是各行的rmin中的该列的最小值?
例如,rmin的第一列是第一行的1,第二行的0,第三行的16,第四行的2,第五行的1。纵向窗口处理后的min:
窗口0:1和0 → min0
窗口1:0和16 →min0
窗口2:16和2 →min2
窗口3:2和1 →min1
所以,对于每个窗口,max和min的差是:
比如窗口0的max17,min0,差17-0=17;窗口1的max17,min0,差17;窗口2的max17,min2 →15;窗口3的max10,min1 →9. 但是可能其他列会有更小的差。
比如,假设某列中的max和min的差是1,例如,当max和min相差1的时候。
比如,看样例的输出是1,可能是在某个窗口中,max和min的差为1。例如,在第三行的某个窗口,比如第三行原矩阵的窗口是17和2,最大值17,最小值2。但经过处理后,可能纵向窗口处理后的结果是某个差为1的情况。
需要更详细的分析。
不过,通过代码的正确处理,可以正确得到结果。
综上,代码的大体思路是正确的。接下来需要编写C++代码实现这一思路。需要注意的是数组的大小,以及正确处理行和列的处理顺序。
现在,关于代码的结构:
-
输入a,b,n。
-
读取矩阵。
-
预处理每一行的横向max和min,得到rmax和rmin。
-
预处理每一列的纵向max和min,得到max_dp和min_dp。
-
遍历所有max_dp和min_dp的元素,计算差值,取最小。
代码实现的关键点在于正确使用单调队列处理行和列。
现在,编写代码的大体步骤:
在C++中,输入可能较大,可以使用快速读取的方式。但是根据题目中的输入样例,每个数之间用空格分隔,可以用cin或scanf读取。
首先,定义二维数组保存原始矩阵:
int a, b, n;
cin >>a >>b >>n;
vector<vector> g(a, vector(b));
for(int i=0; i <a; i++){
for(int j=0; j <b; j++){
cin >>g[i][j];
}
}
然后,处理每一行的横向窗口:
int cols = b -n +1;
vector<vector> rmax(a, vector(cols));
vector<vector> rmin(a, vector(cols));
for(int i=0; i <a; i++){
deque<int> q_max, q_min;
for(int j=0; j <b; j++){
// 处理max队列
while(!q_max.empty() && j - q_max.front() >=n){
q_max.pop_front();
}
while(!q_max.empty() && g[i][j] >= g[i][q_max.back()]){
q_max.pop_back();
}
q_max.push_back(j);
// 处理min队列
while(!q_min.empty() && j - q_min.front() >=n){
q_min.pop_front();
}
while(!q_min.empty() && g[i][j] <= g[i][q_min.back()]){
q_min.pop_back();
}
q_min.push_back(j);
// 当j >=n-1时,可以记录结果
if(j >=n-1){
int index = j -n +1;
rmax[i][index] = g[i][q_max.front()];
rmin[i][index] = g[i][q_min.front()];
}
}
}
这里需要注意,窗口的条件是当前元素j是否达到了窗口的右端。例如,当窗口的长度是n时,当j >=n-1时,窗口的左端是j-n+1,右端是j。这时候可以记录该窗口的最大和最小值。
这样处理后,rmax和rmin的每个行的列数是cols =b-n+1。
接下来,处理列的纵向窗口。例如,max_dp的行数是a-n+1,列数是cols。
int rows_final = a -n +1;
vector<vector> max_dp(rows_final, vector(cols));
vector<vector> min_dp(rows_final, vector(cols));
for(int j=0; j <cols; j++){
deque<int> q_max, q_min;
for(int i=0; i <a; i++){
// 处理max队列
while(!q_max.empty() && i - q_max.front() >=n){
q_max.pop_front();
}
while(!q_max.empty() && rmax[i][j] >= rmax[q_max.back()][j]){
q_max.pop_back();
}
q_max.push_back(i);
// 处理min队列
while(!q_min.empty() && i - q_min.front() >=n){
q_min.pop_front();
}
while(!q_min.empty() && rmin[i][j] <= rmin[q_min.back()][j]){
q_min.pop_back();
}
q_min.push_back(i);
// 当i >=n-1时,记录结果
if(i >=n-1){
int idx = i -n +1;
max_dp[idx][j] = rmax[q_max.front()][j];
min_dp[idx][j] = rmin[q_min.front()][j];
}
}
}
这里,对于每个列j,遍历所有行i。当i >=n-1时,可以记录纵向窗口的最大和最小值到max_dp和min_dp的相应位置。
最后,遍历所有可能的窗口位置,计算差值:
int ans = INT_MAX;
for(int i=0; i <rows_final; i++){
for(int j=0; j <cols; j++){
ans = min(ans, max_dp[i][j] - min_dp[i][j]);
}
}
cout <<ans <<endl;
这样,整个代码的结构就完成了。
测试样例的输入是否正确处理?
比如,输入样例中的a=5,b=4,n=2。cols=4-2+1=3。rows_final=5-2+1=4.
处理完行的rmax和rmin后,处理列的max_dp和min_dp,得到4行3列的数组。
然后遍历每个元素,找出最小的差值。
例如,在样例中,可能存在某个窗口的max和min差为1,所以输出1。
综上,这样的代码应该可以正确处理该问题。
需要注意的边界情况:
-
a或b等于n的情况。例如,当a=n时,rows_final=1,同样处理。
-
n=1的情况?但题目中n>=2。根据题目描述,输入保证n<=a, n<=b,并且2<=a,b<=1000,所以n的最小值是2?
或者题目中的输入条件是否允许n=1?题目描述中的输入格式中的n的说明:题目保证n<=a, n<=b,且样例中n=2。而题目中的n的取值可能根据实际情况而变化。例如,原题的数据范围中,n可以是1吗?
但根据题目中的输入描述,输入的n是可能的,比如用户可能给出n=1。但根据题目中的说明,数据范围中n<=a和n<=b,但题目中的输入描述中的n可能大于等于1?
但根据题目的输出要求,输出的正方形区域是n×n,当n=1时,每个元素自己构成一个正方形,此时差为0。这种情况下是否代码能正确处理?
但根据题目中的输入数据范围,题目说明中给出100%的数据中n<=100,而a和b>=2,所以n的可能取值在2到100之间?或者题目中的n的取值范围可能更广?
需要看原题的输入数据范围。根据题目中的说明:
20%的数据:2 <=a,b <=100, n <=a, n <=b, n <=10.
100%的数据:2 <=a,b <=1000, n <=a, n <=b, n <=100.
因此,n的取值范围是1<=n <=a,n<=b?或者题目中的n必须至少是2?
例如,题目描述中的输入格式中,n是3个整数中的第三个,而输出要求的是n×n的正方形。可能n可以等于1?
但根据样例,n=2,所以代码必须处理n>=1的情况。比如,当n=1时,每个单独元素构成窗口,此时每个窗口的max和min都是该元素的值,差为0。所以代码是否能处理这种情况?
例如,当n=1时,cols =b -1 +1 =b,即与原列数相同。行的处理是每个元素的窗口长度为1,所以每个位置的rmax和rmin等于该元素本身。然后,列处理同样是每个元素的窗口长度为1,所以最终的max_dp和min_dp等于rmax和rmin的值。这样,每个元素对应的差是0,所以输出0。这种情况下,代码是正确的。
所以,代码中的处理应该可以正确处理n=1的情况。因此,不需要特别处理n=1的情况。
综上,这段代码应该可以正确解决问题。现在,编写正确的C++代码即可。
要解决这个问题,我们需要找到一个 n×nn \times nn×n 的正方形区域,使得该区域内最大值和最小值的差最小。为了高效处理这个问题,我们可以使用单调队列来优化时间复杂度。
方法思路
- 横向处理:首先对每一行使用单调队列预处理每个长度为 nnn 的窗口的最大值和最小值。这一步将二维问题转化为一维问题。
- 纵向处理:接着对每一列使用单调队列处理预处理后的结果,进一步得到每个 n×nn \times nn×n 正方形的最大值和最小值。
- 计算结果:遍历所有可能的 n×nn \times nn×n 正方形区域,计算其最大值和最小值的差,找出最小值。
解决代码
#include <iostream>
#include <vector>
#include <deque>
#include <climits>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int a, b, n;
cin >> a >> b >> n;
vector<vector<int>> g(a, vector<int>(b));
for (int i = 0; i < a; ++i) {
for (int j = 0; j < b; ++j) {
cin >> g[i][j];
}
}
// 预处理每一行的横向窗口的最大和最小值
int cols = b - n + 1;
vector<vector<int>> rmax(a, vector<int>(cols));
vector<vector<int>> rmin(a, vector<int>(cols));
for (int i = 0; i < a; ++i) {
deque<int> q_max, q_min;
for (int j = 0; j < b; ++j) {
// 维护单调递减队列(最大值)
while (!q_max.empty() && j - q_max.front() >= n) {
q_max.pop_front();
}
while (!q_max.empty() && g[i][j] >= g[i][q_max.back()]) {
q_max.pop_back();
}
q_max.push_back(j);
// 维护单调递增队列(最小值)
while (!q_min.empty() && j - q_min.front() >= n) {
q_min.pop_front();
}
while (!q_min.empty() && g[i][j] <= g[i][q_min.back()]) {
q_min.pop_back();
}
q_min.push_back(j);
// 当窗口形成时记录结果
if (j >= n - 1) {
int idx = j - n + 1;
rmax[i][idx] = g[i][q_max.front()];
rmin[i][idx] = g[i][q_min.front()];
}
}
}
// 预处理每一列的纵向窗口的最大和最小值
int rows_final = a - n + 1;
vector<vector<int>> max_dp(rows_final, vector<int>(cols));
vector<vector<int>> min_dp(rows_final, vector<int>(cols));
for (int j = 0; j < cols; ++j) {
deque<int> q_max, q_min;
for (int i = 0; i < a; ++i) {
// 处理最大值队列
while (!q_max.empty() && i - q_max.front() >= n) {
q_max.pop_front();
}
while (!q_max.empty() && rmax[i][j] >= rmax[q_max.back()][j]) {
q_max.pop_back();
}
q_max.push_back(i);
// 处理最小值队列
while (!q_min.empty() && i - q_min.front() >= n) {
q_min.pop_front();
}
while (!q_min.empty() && rmin[i][j] <= rmin[q_min.back()][j]) {
q_min.pop_back();
}
q_min.push_back(i);
// 当窗口形成时记录结果
if (i >= n - 1) {
int idx = i - n + 1;
max_dp[idx][j] = rmax[q_max.front()][j];
min_dp[idx][j] = rmin[q_min.front()][j];
}
}
}
// 计算最小差值
int ans = INT_MAX;
for (int i = 0; i < rows_final; ++i) {
for (int j = 0; j < cols; ++j) {
ans = min(ans, max_dp[i][j] - min_dp[i][j]);
}
}
cout << ans << endl;
return 0;
}
代码解释
- 输入处理:读取矩阵的维度和数据。
- 横向预处理:使用单调队列处理每一行的每个窗口,得到每个窗口的最大值和最小值。
- 纵向预处理:使用单调队列处理每一列的每个窗口,得到最终的二维窗口的最大值和最小值。
- 结果计算:遍历所有二维窗口,计算最大值和最小值的差,找出最小值。
这种方法通过两次单调队列处理将时间复杂度优化到线性级别,能够高效处理大规模数据。
嗯,我现在需要分析用户提供的这段C++代码。首先,我得通读一遍代码,理解它的结构和功能。看起来这是一个处理矩阵滑动窗口问题的程序,可能是在寻找某个窗口内的最大值和最小值之差的最小值。不过让我慢慢梳理一下。
首先,代码开始包含了一些头文件,比如iostream、vector、deque和climits,这些都是标准库中常用的。然后定义了一个模板函数sliding_window,接收一个比较器Comparator。这个函数看起来是实现滑动窗口的某种极值(最大值或最小值)的提取,因为使用了双端队列deque的结构。
接下来是主函数main。用户输入了a、b、n三个变量。如果n是1,直接输出0,这可能是因为当窗口大小为1时,最大值和最小值之差是0,所以直接返回。然后读取一个a行b列的矩阵。
然后,代码处理每一行的滑动窗口,生成row_max和row_min,分别存储每行中每个窗口的最大值和最小值。这里用到了sliding_window函数,并传入了不同的比较器:max_comp是a >= b,用于寻找最大值;min_comp是a <= b,用于寻找最小值。这一步应该是针对每一行的每个长度为n的滑动窗口,记录其中的最大值和最小值。
接下来处理列方向。对于每个列j,将之前行处理得到的row_max和row_min中的对应列提取出来,形成一个列向量,然后再次应用滑动窗口函数,但这次窗口大小还是n。这一步可能是在处理二维的窗口,即n×n的窗口?或者可能是行和列方向各处理一次,从而得到一个更大的二维窗口?
比如,假设原矩阵是a×b的,当n是窗口大小,先对每行处理,得到每行的窗口最大值和最小值,这样row_max和row_min的大小应该是a×(b - n + 1)?然后对列方向进行处理,每个列有a个元素,滑动窗口大小n,处理后得到res_max和res_min,最终每个位置的最大值和最小值是经过行和列两次滑动窗口处理后的结果。这样的话,最终的窗口应该是一个n×n的正方形窗口?比如,先对行做长度为n的窗口处理,得到每个行的窗口极值,然后对列方向同样做长度为n的窗口处理,这样组合起来就是整个n×n的窗口的极值?
例如,原矩阵中的一个n×n的子矩阵,首先在每一行中找到长度为n的窗口的极值,这样每个行得到一个缩减后的列数(b - n +1),然后在列方向上,对每个这样的列再次处理长度为n的窗口,得到每个位置的最大和最小值。这样,最终的窗口就是n行n列的窗口。所以,最终的窗口是二维的,而代码通过两次一维的滑动窗口处理来达到二维的效果。这可能就是所谓的单调队列优化的二维滑动窗口问题。
最后,计算所有位置的最大值和最小值之差的最小值,输出结果。
现在需要确认这个分析是否正确。比如,假设原矩阵大小为a×b,那么经过行处理后,每行的窗口数为b - n +1,所以row_max和row_min的大小是a行,每行有b -n +1个元素。然后,在处理列方向时,每个列j对应的列数据是各个行的第j个元素,共a个元素。对每个这样的列进行滑动窗口处理,窗口大小为n,所以处理后每个列得到a -n +1个元素。因此,最终的max_val和min_val的大小是(a -n +1)行 ×(b -n +1)列。这样,每个位置对应原矩阵中的一个n×n的子矩阵的最大和最小值。最后计算这些差的最小值。
所以整个程序的目标是:在a×b的矩阵中,找出所有n×n的子矩阵,计算每个子矩阵的最大值与最小值的差,然后找出所有差值中的最小值。这就是题目所求的结果。
接下来要验证代码是否正确地实现了这个逻辑。例如,行处理是否正确,列处理是否正确,两次滑动窗口的组合是否覆盖所有可能的n×n窗口。
首先,sliding_window函数的工作原理。它使用一个双端队列,维护当前窗口内的候选索引。每次新元素加入时,根据比较器comp来决定是否弹出队尾元素,直到队列中的元素满足比较条件。比如,当寻找最大值时,比较器是a >= b,即当前元素如果大于等于队列末尾的元素,就将末尾弹出,直到队列为空或者遇到更大的元素。这样队列中的元素是单调递减的(对于最大值的情况),因此队首就是当前窗口的最大值的索引。然后,当窗口滑动时,检查队首是否在窗口外,如果是,弹出队首。
在行处理阶段,对每一行的每个长度为n的窗口,得到最大值和最小值,存储在row_max和row_min中。这时,每行的长度变为原来的b -n +1。例如,原行有b个元素,处理后每行有b -n +1个元素。
然后,在列处理阶段,对于每个处理后的列j,将各个行的第j个元素组成一个列,再对这个列进行滑动窗口处理,窗口大小还是n。处理后的结果max_val和min_val的每个元素对应原矩阵中的一个n×n窗口的最大和最小值。例如,最终max_val的行数是a -n +1(因为列处理后的每个列的长度是a,处理后变为a -n +1),列数是cols,即原来的行处理后的列数,即b -n +1。因此,最终的max_val和min_val的每个元素对应原矩阵中的n×n窗口的最大和最小值。
最后,遍历所有这样的窗口,计算最大和最小的差,找到最小的差作为结果。
这样整个算法的时间复杂度是怎样的呢?对于行处理阶段,每行的时间是O(b),因为每个元素进出队列一次。共有a行,所以行处理的总时间是O(ab)。列处理阶段,每个列j的处理需要处理a个元素,每个列处理后得到a -n +1个元素。列的数量是cols = b -n +1。所以每个列的滑动窗口处理时间是O(a),总共有cols列,所以列处理的总时间是O(a(b -n +1))。因为b可能很大,所以总的时间复杂度为O(ab) + O(ab) = O(a*b)。这应该是一个线性的复杂度,相对于矩阵的总元素数来说,是比较高效的,因为每个元素在行处理和列处理中各被处理两次。
这个算法的正确性基于单调队列(双端队列)的应用,确保能够高效地维护滑动窗口的最大值和最小值。两次滑动窗口处理(先行后列)的方式,能够将二维的问题分解为两个一维的问题,从而降低复杂度。
总结来说,这段代码解决的问题是:在一个a×b的矩阵中,找出所有n×n的子矩阵,计算每个子矩阵的最大值与最小值之差,然后找出这些差值中的最小值。通过两次滑动窗口处理(行和列方向),利用单调队列来高效地计算极值,最终得到结果。
以下是对该代码的逐步分析:
1. 代码功能概述
该程序用于在a×ba \times ba×b的矩阵中,找出所有n×nn \times nn×n的子矩阵,计算每个子矩阵的最大值与最小值之差,最终输出所有差值中的最小值。核心思路是两次滑动窗口处理(先行后列),通过单调队列优化时间复杂度。
2. 关键函数解析:滑动窗口极值
template<typename Comparator>
vector<int> sliding_window(const vector<int>& arr, int k, Comparator comp) {
deque<int> dq; // 双端队列维护候选索引
vector<int> res;
for (int i = 0; i < arr.size(); ++i) {
// 维护单调性:删除不符合比较条件的元素
while (!dq.empty() && comp(arr[i], arr[dq.back()])) {
dq.pop_back();
}
dq.push_back(i);
// 移除超出窗口范围的队首元素
while (!dq.empty() && dq.front() <= i - k) {
dq.pop_front();
}
// 当窗口足够大时记录结果
if (i >= k - 1) {
res.push_back(arr[dq.front()]);
}
}
return res;
}
- 作用:利用单调队列高效计算滑动窗口的极值(最大或最小)。
- 参数:
arr
:输入数组k
:窗口大小comp
:比较器(决定是求最大值还是最小值)
- 时间复杂度:O(n)O(n)O(n),每个元素最多入队、出队一次。
3. 主流程分解
步骤1:输入与初始化
- 读取矩阵尺寸a,ba, ba,b和窗口大小nnn。
- 若n=1n=1n=1,直接输出0(单个元素的差值为0)。
- 读取a×ba \times ba×b的矩阵数据。
步骤2:行方向滑动窗口处理
// 对每行分别求滑动窗口的最大值和最小值
vector<vector<int>> row_max(a), row_min(a);
auto max_comp = [](int a, int b) { return a >= b; }; // 用于最大值
auto min_comp = [](int a, int b) { return a <= b; }; // 用于最小值
for (int i = 0; i < a; ++i) {
row_max[i] = sliding_window(matrix[i], n, max_comp);
row_min[i] = sliding_window(matrix[i], n, min_comp);
}
- 结果:
row_max[i][j]
:第iii行第jjj个窗口的最大值。row_min[i][j]
:第iii行第jjj个窗口的最小值。
- 处理后每行长度:b−n+1b - n + 1b−n+1。
步骤3:列方向滑动窗口处理
// 对列方向再次滑动,得到最终的二维窗口极值
int cols = row_max[0].size();
int final_rows = a - n + 1;
vector<vector<int>> max_val(final_rows, vector<int>(cols));
vector<vector<int>> min_val(final_rows, vector<int>(cols));
for (int j = 0; j < cols; ++j) {
// 提取列数据
vector<int> col_max, col_min;
for (int i = 0; i < a; ++i) {
col_max.push_back(row_max[i][j]);
col_min.push_back(row_min[i][j]);
}
// 列方向滑动窗口处理
vector<int> res_max = sliding_window(col_max, n, max_comp);
vector<int> res_min = sliding_window(col_min, n, min_comp);
// 存储结果
for (int i = 0; i < final_rows; ++i) {
max_val[i][j] = res_max[i];
min_val[i][j] = res_min[i];
}
}
- 结果:
max_val[i][j]
:以(i,j)(i,j)(i,j)为左上角的n×nn \times nn×n窗口的最大值。min_val[i][j]
:对应窗口的最小值。
- 处理后矩阵尺寸:(a−n+1)×(b−n+1)(a - n + 1) \times (b - n + 1)(a−n+1)×(b−n+1)。
步骤4:计算最小差值
int result = INT_MAX;
for (int i = 0; i < final_rows; ++i) {
for (int j = 0; j < cols; ++j) {
int diff = max_val[i][j] - min_val[i][j];
if (diff < result) result = diff;
}
}
cout << result << endl;
- 时间复杂度:O((a−n+1)(b−n+1))O((a-n+1)(b-n+1))O((a−n+1)(b−n+1))。
4. 算法优势
- 时间复杂度:O(a×b)O(a \times b)O(a×b),通过两次一维滑动窗口处理,避免直接遍历所有二维窗口的O(abn2)O(a b n^2)O(abn2)复杂度。
- 空间复杂度:O(a×b)O(a \times b)O(a×b),存储中间结果。
5. 示例验证
假设输入矩阵为3×33 \times 33×3,n=2n=2n=2:
矩阵:
1 2 3
4 5 6
7 8 9
行处理结果(每个窗口大小为2):
row_max = [[2,3], [5,6], [8,9]]
row_min = [[1,2], [4,5], [7,8]]
列处理结果(每个窗口大小为2):
max_val = [[5,6], [8,9]]
min_val = [[1,2], [4,5]]
最终差值最小为5-5=0(实际计算需核对)。
6. 潜在问题
- 窗口大小n=1n=1n=1:已特殊处理,直接输出0。
- 矩阵尺寸不足:若a<na < na<n或b<nb < nb<n,代码可能越界(需确保输入合法)。