leetcode 1289. 下降路径最小和 II

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leetcode 1289. 下降路径最小和 II相关的知识,希望对你有一定的参考价值。

在这里插入图片描述


动态规划朴素解法

  • 定含义: dp[i][j]表示当前(i,j)位置的最小下降路径和
  • 找状态转移方程:

在这里插入图片描述

  • 很显然,有上面的图可以得到状态转移方程:
  • for (int k = 0; k < r; k++)
  • {
  • if(j!=k)
  • dp[i][j] = min(dp[i - 1][k] + arr[i][j], dp[i][j]);
  • }
  • 这里注意:需要把dp数组初始元素都设置为最大值

代码展示:

class Solution {
public:
	int minFallingPathSum(vector<vector<int>>& arr) 
	{
		if (arr.empty()) return 0;
		int r = arr.size();
		vector<vector<int>> dp(r,vector<int>(r,INT_MAX));
		for (int i = 0; i < r; i++)//第一行初始化
			dp[0][i] = arr[0][i];
		for (int i = 1; i < r; i++)
		{
			for (int j = 0; j < r; j++)
			{
				for (int k = 0; k < r; k++)
				{
					if(j!=k)
					dp[i][j] = min(dp[i - 1][k] + arr[i][j], dp[i][j]);
				}
			}
		}
		//在最后一行中找出最小值
		int Min = INT_MAX;
		for (int i = 0; i < r; i++)
		{
			Min = min(dp[r - 1][i], Min);
		}
		return Min;
	}
};

在这里插入图片描述


动态规划进阶解法

我们来分析一下上述解法有哪些可优化的点:

  • DP 状态转移部分,共有 n * n 个状态需要转移

  • 每次转移的时候,枚举上一行的所有列

我们要确保所有的方案都枚举到,得到的才是全局最优解。

因此 DP 部分,我们是无法优化的。

那就只剩下枚举上一行的所有列这个部分可以优化了。

其实细想就可以发现,当我们在计算某行的状态值的时候,只会用到「上一行」的两个值:最小值和次小值。

  • 举个🌰,当我们已经处理完第 i-1i−1 行的状态值。
  • 假设第 i-1行状态中的最小值对应的列下标是 i1次小值对应的列下标是 i2
  • 那么当我们处理第 i 行时,显然有:
  • 处理第i 行中列下标为 i1的状态值时,由于不能选择「正上方」的数字,用到的是次小值。转移方程为:f[i][j]=f[i-1][i2]+arr[i][j]
  • 处理第 i行其他列下标的状态值时,这时候用到的是最小值。转移方程为:f[i][j]=f[i-1][i1]+arr[i][j]
    在这里插入图片描述

因此我们可以使用 i1 保存上一行的最小值对应的列下标,用 i2 保存次小值对应的列下标。

而无需每次转移都枚举上一行的所有列。

代码如下:

class Solution {
public:
	int minFallingPathSum(vector<vector<int>>& arr) 
	{
		if (arr.empty()) return 0;
		int r = arr.size();
		vector<vector<int>> dp(r,vector<int>(r,INT_MAX));
		int i1=-1, i2=-1;//最小值和次小值

		//给dp数组第一行进行初始化
		for (int i = 0; i < r; i++)
		{
			dp[0][i] = arr[0][i];
			//找出当前行的最小值和次小值
			if (arr[0][i]<(i1==-1?INT_MAX:dp[0][i1]))
			{
				i2 = i1;//更新i2
				i1 = i;//更新i1
			}
			else if (arr[0][i] <(i2==-1?INT_MAX:dp[0][i2]))
			{
				i2 = i;
			}
		}
		for (int i = 1; i < r; i++)
		{
			// 当前转移第 i 行,使用临时变量保存转移过程中的「最小值列下标」&「次小值列下标」
			int ti1 = -1, ti2 = -1;

			for (int j = 0; j < r; j++)
			{
				dp[i][j] = INT_MAX;
				int val = arr[i][j];

				//如果最小值与当前列不冲突,选择上一行最小值,否则选择上一行次小值
				if (j != i1)
					dp[i][j] = dp[i - 1][i1] + val;
				else
					dp[i][j] = dp[i - 1][i2] + val;


				//寻找当前行的最小值和次小值下标
				if (dp[i][j] < (ti1 == -1? INT_MAX : dp[i][ti1]))
				{
					ti2 = ti1;
					ti1 = j;
				}
				else if (dp[i][j] < (ti2 == -1 ? INT_MAX : dp[i][ti2]))
				{
					ti2 = j;
				}
			}
			// 使用临时变量更新 i1 和 i2
			i1 = ti1; i2 = ti2;
		}
		int Min = INT_MAX;
		for (int i = 0; i < r; i++)
		{
			Min = min(dp[r - 1][i], Min);
		}
		return Min;
	}
};

在这里插入图片描述


动态规划终极优化版本

  • 此外,我们还可以对空间复杂度进行优化。由于 f[i][j] 只会从 f[i - 1][i1] 或 f[i -1][i2] 转移而来那么我们并不用将第 i - 1 行的所有状态存储下来,而是可以浓缩成三个变量
  • first_sum 表示这一行的最小值;
  • first_pos 表示这一行最小值对应的下标;
  • second_sum 表示这一行的次小值。

状态转移方程修改为:

  • f[i][j] = first_sum + arr[i][j] 其中 j != first_pos
  • f[i][j] = second_sum + arr[i][j] 其中 j == first_pos
  • 通过这三个变量计算出第 i 行的所有状态之后,我们也不用将它们存储下来,同样可以浓缩成三个变量,为第 i + 1行的动态规划提供转移基础。由于在计算第 i + 1 行的状态时,不需要第 i - 1 行的任何信息,因此第 i - 1行浓缩成的三个变量此时可以被丢弃。这样以来,我们就将空间复杂度从 O(N^2) 降低至了 O(1)。

代码演示:

class Solution {
public:
	int minFallingPathSum(vector<vector<int>>& arr) 
	{
		if (arr.empty()) return 0;
		int r = arr.size();
		int first_sum = 0;//当前行最小值---这里最小值和次小值一开始都要初始化为0,因为计算第一行时累加起点从0开始
		int first_pos = -1;//当前行最小值对应的下标---是为了每一次判断上一行最小值与当前列是否发生冲突
		int second_sum = 0;//当前行次小值
       //计算每一行的最小值和次小值
		for (int i = 0; i < r; i++)
		{
			//下面三个临时变量用来计算对应的三个值,然后赋值外面对应的三个
			int fs = INT_MAX, fp = -1, ss = INT_MIN;//当前行最小值,最小值下标,当前行次小值
			for (int j = 0; j < r; j++)
			{
				//计算当前行从第一列开始每一个位置元素的最小结果
				//如果上一行最小值与当前列发生冲突,那么选上一行次小值加上去
				int cur_sum = (first_pos != j ? first_sum : second_sum) + arr[i][j];
				//计算当前行最小值,最小值下标,当前行次小值
				if (cur_sum < fs)
				{
					ss = fs;
					fs = cur_sum;
					fp = j;
				}
				else if (cur_sum < ss)
				{
					ss = cur_sum;
				}
			}
			//临时变量赋值给外面对应的三个
			first_pos = fp;
			first_sum = fs;
			second_sum = ss;
		}
		//返回最后一行保存的最小值
		return first_sum;
	}
};

在这里插入图片描述

复杂度分析

  • 时间复杂度:O(N^2)O(N 2 ),其中 NN 是方阵 arr 的边长。

  • 空间复杂度:O(1)O(1)。


总结

这道题其实还可以用记忆化递归的方法求解,这里不作出展示了,留给读者自己思考

以上是关于leetcode 1289. 下降路径最小和 II的主要内容,如果未能解决你的问题,请参考以下文章

[Leetcode]931.下降路径最小和

leetcode 931. 下降路径最小和

20210831每日总结

Leetcode WC-108-03 931-下降路径最小和

leetcode刷题(125)——931. 下降路径最小和

LeetCode 5129. 下降路径最小和 II Minimum Falling Path Sum II