动态规划的一个简单列子

该博客介绍了如何使用动态规划解决一个矩阵寻路问题,从左上角出发,每次只能向右或向下移动,目标是找到路径和最小的路径。通过分析得出状态转移方程dp[n][m] = min(dp[n-1][m], dp[n][m-1]),以此求解最小路径和。" 27971435,2276703,Unity 3D游戏开发:Label控件详解,"['Unity', '游戏开发', 'GUI系统', '用户界面', '编程']

问题:
有一个矩阵map,它每个格子有一个权值。从左上角的格子开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中最小的路径和。
* 给定一个矩阵map及它的行数n和列数m,请返回最小路径和

分析:
设dp[n][m]为走到n*m位置的路径长度,那么显而易见dp[n][m] = min(dp[n-1][m],dp[n][m-1]);

代码:

public class Dynamaic {
    public static void main(String[] args) {
        int[][]  map={
  
  {
  
  2,6,9},{
  
  3,7,1},{
  
  9,2,7}};

        
<think>好的,我现在要解决用户的问题:如何计算给定n行m列矩阵的所有x行y列子矩阵的最小值。首先,我需要明确用户的需求。用户提到的是所有子矩阵的最小值,而子矩阵的大小是固定的x行y列。所以需要遍历矩阵中所有可能的x×y大小的子矩阵,并找出每个子矩阵的最小值。 首先,我得考虑暴力解法。暴力法就是遍历每个可能的子矩阵,然后在每个子矩阵中遍历所有元素找最小值。但这样的时间复杂度太高了,尤其是当n和m较大时,比如n和m都是1e3级别的话,时间复杂度会是O(nmxy),这显然不可行。所以必须寻找更高效的算法。 接下来,我想到可能需要使用滑动窗口或者类似二维RMQ(Range Minimum Query)的方法。不过,RMQ预处理的时间可能较高,但查询快。或者,有没有类似二维单调队列的方法?比如在行和列两个方向上进行单调队列的处理,类似二维的滑动窗口最小值。 回忆一下一维的情况,求滑动窗口最小值可以用单调队列在O(n)的时间复杂度内完成。那么二维的情况是否可以分解为行和列的处理?例如,先对每一行处理,得到每一行中长度为y的滑动窗口最小值,然后再对得到的中间矩阵的每一列处理长度为x的滑动窗口最小值。这样,两次处理后的结果就是每个x×y子矩阵的最小值。 具体来说,步骤如下: 1. 对原矩阵的每一行,使用单调队列计算该行中所有长度为y的窗口的最小值,得到一个中间矩阵,其中每个元素是该行对应窗口的最小值。 2. 对这个中间矩阵的每一列,同样使用单调队列计算长度为x的窗口的最小值,得到最终的结果矩阵,每个元素对应原矩阵中x×y子矩阵的最小值。 这样处理的话,时间复杂度应该是O(nm),因为每一行的处理是O(m),每一列的处理是O(n),而总共有n行和m列,所以整体复杂度是O(nm)。这应该比暴力法好很多,尤其是在大数据量的情况下。 接下来需要考虑如何实现二维的滑动窗口处理。首先,针对每一行,进行一维的滑动窗口最小值计算。然后对中间结果的每一列进行同样的处理。需要注意的是,中间矩阵的列数会减少,因为每个行的窗口滑动后,列数变为m - y + 1。因此,在第二步处理列的时候,每一列的长度是n,窗口大小是x,所以处理后的列数会是n - x + 1。因此,最终的结果矩阵的大小应该是(n - x + 1) × (m - y + 1),对应所有可能的x×y子矩阵的最小值。 那如何具体实现呢?比如,用Python的话,可能需要为每一行生成滑动窗口的最小值数组,然后将这些数组转置,再对每一列(即转置后的行)进行滑动窗口处理,再转置回来得到结果。 关于单调队列的实现,需要维护一个双端队列,保存当前窗口内的候选最小值的索引。当新元素进入窗口时,从队列尾部移除比当前元素大的元素,然后将当前元素的索引加入队列。同时,检查队列头部的索引是否已经超出窗口范围,如果是则移除。这样,队列头部始终是当前窗口的最小值。 对于二维的情况,需要分别处理行和列。例如,先处理行方向,得到每一行的滑动窗口最小值,然后将这些结果转置,处理列方向的滑动窗口最小值。这相当于将二维问题分解为两个一维问题。 举个例子,假设原矩阵是3行5列,x=2,y=2。那么处理每一行的滑动窗口后,中间矩阵的大小是3×4,因为每行有5-2+1=4个窗口。然后处理中间矩阵的每一列(此时列数是4),每个列有3个元素,窗口大小为x=2,所以每列处理后得到2个元素。最终的结果矩阵是2行4列,对应每个2×2的子矩阵的最小值。 另外,需要处理可能的边界情况,比如x或y等于1的情况,这时候滑动窗口处理可能退化为普通的遍历。 接下来,需要验证这个方法的正确性。例如,考虑一个简单的测试案例: 原矩阵: 1 2 3 4 5 6 7 8 9 x=2,y=2。处理行方向的窗口,每个行得到两个最小值: 行处理后: min(1,2)=1,min(2,3)=2 min(4,5)=4,min(5,6)=5 min(7,8)=7,min(8,9)=8 中间矩阵: 1 2 4 5 7 8 转置后变为: 1 4 7 2 5 8 处理列方向的窗口(窗口大小为x=2),每列得到两个最小值: 对于第一列(1,4,7),窗口大小为2,得到min(1,4)=1,min(4,7)=4 对于第二列(2,5,8),得到min(2,5)=2,min(5,8)=5 转置后的结果矩阵是: 1 4 2 5 转置回来得到最终结果: 1 2 4 5 但原矩阵中的2x2子矩阵的最小值应该是: 左上角的子矩阵最小是1,右上角的子矩阵是2,左下角的是4右下角的是5。这和结果一致。所以这个方法是正确的。 接下来,需要考虑如何在代码中实现这个算法。例如,在Python中,可以编写一个处理一维滑动窗口的函数,然后应用两次。 另外,需要注意矩阵的行列转换问题,在中间步骤可能需要转置矩阵,以便复用行处理的函数来处理列。 关于时间复杂度,假设矩阵是n行m列,那么行处理的时间是O(n*(m - y + 1)),而列处理的时间是O((m - y + 1)*(n - x + 1)),但具体实现可能不同。但总体来说是线性时间,相对于暴力法来说效率提升明显。 用户提供的引用中有提到二维前缀和的问题,比如引用[2]中的矩阵矩阵求和问题,使用前缀和数组来快速计算。但这里的问题是最小值,无法直接用前缀和,但可能需要类似的结构。但滑动窗口方法可能更高效。 另外,引用[1]中提到用户之前尝试将一维最大子段和扩展到二维的情况,这可能和当前问题有类似之处,但这里是求最小值,而用户之前的问题可能涉及动态规划或其他方法。不过当前的问题的解法可能更适合滑动窗口。 综上,该问题的解决方案是使用两次滑动窗口,一次处理行,一次处理列,从而在O(nm)的时间复杂度内解决问题。</think>### 矩阵所有x行y列子矩阵最小值的高效算法 #### 方法概述 该问题可以通过**两次单调队列滑动窗口**算法高效解决,时间复杂度为$O(nm)$。核心思路是将二维问题分解为两次一维滑动窗口处理: 1. **行方向处理**:对每一行计算所有长度为y的滑动窗口最小值 2. **列方向处理**:对处理后的中间矩阵的每一列计算长度为x的滑动窗口最小值 $$ \text{最终结果矩阵} = \text{列处理}(\text{行处理}(\text{原矩阵})^T)^T $$ #### 算法步骤 1. **行滑动窗口处理** ```python def row_min(matrix, y): result = [] for row in matrix: q = deque() row_mins = [] for i, num in enumerate(row): while q and row[q[-1]] >= num: q.pop() q.append(i) if q[0] <= i - y: q.popleft() if i >= y - 1: row_mins.append(row[q[0]]) result.append(row_mins) return result ``` 2. **列滑动窗口处理**(通过转置复用行处理函数) ```python def matrix_min(matrix, x, y): # 行处理 row_processed = row_min(matrix, y) # 转置矩阵 transposed = list(zip(*row_processed)) # 列处理(转置后变为行处理) col_processed = row_min(transposed, x) # 再次转置得到最终结果 return list(zip(*col_processed)) ``` #### 复杂度分析 - **时间复杂度**:$O(nm)$ 每行处理耗时$O(m)$,共$n$行;转置后每列处理耗时$O(n)$,共$m-y+1$列 - **空间复杂度**:$O(nm)$ 存储中间结果矩阵和最终结果矩阵 #### 应用场景 该方法适用于需要快速获取固定尺寸子矩阵极值的场景,例如: 1. 图像处理中的滤波操作 2. 数据压缩中的块特征提取 3. 数值计算中的局部极值分析[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值