Triangle自底向上解法

本文介绍了一种采用自底向上动态规划的方法解决三角形最短路径问题的算法实现。通过逐层更新结果数组,最终求得从三角形顶部到底部的最短路径总和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述:

   Given a triangle, find the minimum path sum from top to bottom.
   Each step you may move to adjacent numbers on the row below.
   (给定一个三角形,从上到下找到最短路径的总和。每一步,你要移到下一行与该数相邻的数。)
   For example, given the following triangle
   [
       [2],
      [3,4],
     [6,5,7],
    [4,1,8,3]
   ]
   The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).

思路:和第一次Triangle自顶向下DP不同,这次使用了自底向上DP的方法,因为数据量在一直缩小,所以不需要去考虑边界值的问题,更加简洁。

          还有就是只使用一个数组来存放结果,例如上例,第三行的第一个数6,经过判断应该和其下一行的右边的1来相加,此时result[0]为6+1=7,

          接下来判断第三行的第二个数5,他要和和result[1]和result[2]中的最小值相加,所以即便result[0]已经改变,但对后面没有影响,其余结点皆如此。

public class Triangle_bottomTotop {
	public static int minimumTotal(List<List<Integer>> triangle) 
	{
		//因为triangle.get(i).get(j)不是变量不能直接赋值
		//所以要找一个数组来代替
		//这个数组的长度只需要设置为最后一行的长度即可因为是从下往上推,所以越往上只可能越小
		int result[] = new int[triangle.size()];
		for(int i=0;i<triangle.get(triangle.size()-1).size();i++)
		{
			result[i] = triangle.get(triangle.size()-1).get(i);
		}
        for(int i = triangle.size()-2;i >= 0;i--)
        {
        	for(int j = 0;j<triangle.get(i).size();j++)
        	{
        		//每个点的值就是其下一行相邻的两个点的最小值加上该点的值
        		//一直用一个数组来存放结果会大大减少空间上的开销
        		result[j] = triangle.get(i).get(j)+Math.min(result[j],result[j+1]);
         	}
        }
        return result[0];
    }
	public static void main(String[] args) {
		List<List<Integer>> Triangle = new ArrayList();
		ArrayList List1 = new ArrayList();
		ArrayList List2 = new ArrayList();
		ArrayList List3 = new ArrayList();
		ArrayList List4 = new ArrayList();
		List1.add(2);
		List2.add(3);
		List2.add(4);
		List3.add(6);
		List3.add(5);
		List3.add(7);
		List4.add(4);
		List4.add(1);
		List4.add(8);
		List4.add(3);
		Triangle.add(List1);
		Triangle.add(List2);
		Triangle.add(List3);
		Triangle.add(List4);
		System.out.println(minimumTotal(Triangle));
	}
}


<think>我们有一个数字三角形,要求从顶部到底部的路径,使得路径上数字之和最大。每一步可以向左下或右下走。动态规划是解决此问题的经典方法。我们可以从下往上递推,或者从上往下递推。但考虑到边界条件,从下往上更为简便(避免边界判断)。思路:1.输入数字三角形。2.初始化dp数组,对于最底层的元素,dp值就是它们本身(因为从底层开始,路径只有自己)。3.从倒数第二层开始向上遍历,对于每个位置(i,j),其dp值等于它下一层的左右两个位置的最大值加上它自身的值。即:dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+triangle[i][j]4.最终,dp[0][0](或dp[1][1]取决于存储方式)就是最大路径和。注意:题目中的数字三角形行数最大为100,但我们的数组可以开大一些(如MAXN=610)。参考引用[1]和[3]的代码,它们采用了从下往上的方法。引用[4]和[5]采用了从上往下的方法,但需要处理边界(因为最左边的元素只能从右上方来?实际上从上往下时,最左边只能从正上方来,但题目允许向左下和右下,所以对于(i,j)来说,它可以来自(i-1,j-1)或(i-1,j))。不过引用[4]的代码初始化了负无穷来处理边界,这样在计算时就不会取到不存在的值。这里我们采用从下往上的方法(更简单,无需处理边界,因为每个位置都有两个子节点)。代码步骤:1.读取行数n。2.读取数字三角形,注意通常我们使用1-indexed或0-indexed。这里引用[1]使用了1-indexed(从1开始存储),这样第i行有i个元素(从1到i)。3.初始化dp数组:将最后一行赋值给dp数组的最后一行。4.从倒数第二行(n-1行)开始向上循环,更新dp数组。5.输出dp[1][1](如果从1开始)或dp[0][0](如果从0开始)。但是,引用[1]的代码输出的是dp[1][1](因为其存储从第一行第一列开始)。我们也可以使用0-indexed。为了清晰,我们采用0-indexed(行从0到n-1,列从0到当前行号)。注意:在输入时,第i行(0-indexed)有i+1个数字。代码实现(0-indexed):1.输入n。2.输入三角形,行i从0到n-1,列j从0到i(共i+1个数)。3.初始化dp数组:dp[n-1][j]=triangle[n-1][j](j=0..n-1)4.从i=n-2(倒数第二行)开始,递减到0;对于每一行i,遍历j从0到i:dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+triangle[i][j]5.输出dp[0][0]下面我们给出C++代码,并注意数组边界。注意:我们不需要处理边界条件,因为每个位置(i,j)在下一行都有两个相邻位置(除非是最后一行,但我们已经初始化了最后一行)。我们将参考引用[1]和[3]的代码,但使用0-indexed。代码:*/#include<iostream>#include<algorithm>usingnamespacestd;constintMAXN=610;//最大行数inttriangle[MAXN][MAXN];//存储数字三角形intdp[MAXN][MAXN];//存储最大路径和intmain(){intn;cin>>n;//输入数字三角形for(inti=0;i<n;i++){for(intj=0;j<=i;j++){cin>>triangle[i][j];}}//初始化dp数组的最后一行for(intj=0;j<n;j++){dp[n-1][j]=triangle[n-1][j];}//从倒数第二行开始向上递推for(inti=n-2;i>=0;i--){for(intj=0;j<=i;j++){dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+triangle[i][j];}}//输出最大路径和cout<<dp[0][0]<<endl;return0;}/*这个程序的时间复杂度是$O(n^2)$,空间复杂度也是$O(n^2)$。我们也可以考虑空间优化,因为dp数组在更新时只用到下一行的数据,所以可以用一维数组来存储,但这里为了清晰,使用二维数组。相关问题:1.如果要求输出最大路径(而不仅仅是路径和),应该如何修改程序?2.如果数字三角形中的数字可以为负数,这个算法是否仍然有效?为什么?3.使用动态规划求解数字三角形最大路径和时,从上往下和从下往上两种方法在时间复杂度上有何区别?在边界处理上有何不同?4.如何将上述代码的空间复杂度优化为$O(n)$?注意:上述代码在输入数据为引用[2]中的示例时(5行的三角形),应该输出30。*/</think>### 数字三角形最大路径和(动态规划解法) 数字三角形问题要求从顶部到底部寻找一条路径,使得路径上数字之和最大。每一步只能向左下或右下移动。使用动态规划解决此问题的核心思路如下: #### 算法思路 1. **状态定义** 设 $dp[i][j]$ 表示从顶点到达位置 $(i, j)$ 的最大路径和。 2. **状态转移方程** - **从上往下递推**(需处理边界): $$ dp[i][j] = \begin{cases} dp[i-1][0] + triangle[i][0] & \text{if } j=0 \\ dp[i-1][j-1] + triangle[i][j] & \text{if } j=i \\ \max(dp[i-1][j-1], dp[i-1][j]) + triangle[i][j] & \text{otherwise} \end{cases} $$ - **从下往上递推**(更简洁,无需边界判断): $$ dp[i][j] = \max(dp[i+1][j], dp[i+1][j+1]) + triangle[i][j] $$ 3. **初始化** - 从上往下:$dp[0][0] = triangle[0][0]$ - 从下往上:$dp[n-1][j] = triangle[n-1][j]$(最后一行) 4. **结果获取** - 从上往下:$\max(dp[n-1][j])$(遍历最后一行) - 从下往上:$dp[0][0]$(直接取顶点值) #### 代码实现(从下往上递推) ```cpp #include <iostream> #include <algorithm> using namespace std; const int MAXN = 610; // 最大行数 int main() { int n; cin >> n; int triangle[MAXN][MAXN], dp[MAXN][MAXN]; // 输入数字三角形 for (int i = 0; i < n; i++) for (int j = 0; j <= i; j++) cin >> triangle[i][j]; // 初始化最后一行 for (int j = 0; j < n; j++) dp[n-1][j] = triangle[n-1][j]; // 从倒数第二行向上递推 for (int i = n-2; i >= 0; i--) for (int j = 0; j <= i; j++) dp[i][j] = max(dp[i+1][j], dp[i+1][j+1]) + triangle[i][j]; // 输出最大路径和 cout << dp[0][0] << endl; return 0; } ``` #### 代码说明 1. **时间复杂度**:$O(n^2)$($n$ 为行数) 2. **空间复杂度**:$O(n^2)$ 3. **优势**:从下往上递推避免边界判断,代码更简洁[^3][^1]。 4. **示例**: 输入: ``` 5 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 ``` 输出:`30`(路径 `7→3→8→7→5`) #### 空间优化方法 可将空间复杂度降至 $O(n)$: ```cpp int dp[MAXN] = {0}; // 一维数组 for (int j = 0; j < n; j++) dp[j] = triangle[n-1][j]; // 初始化 for (int i = n-2; i >= 0; i--) for (int j = 0; j <= i; j++) dp[j] = max(dp[j], dp[j+1]) + triangle[i][j]; // 覆盖前一行 ``` 此时结果在 `dp[0]` 中[^3][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值