题目:
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
示例,返回11,路径为2-3-5-1,不要求返回路径,辅助空间不限(越小越好)
解:
说一下我自己的解题过程,最开始想到的是递归,递归也可以解出来,但是无用的计算太多了,所以最终使用动态规划进行计算。
递归:
/// 寻找三角形最短路径
/// - Parameters:
/// - triangle: 二维数组
/// - i: 当前起始i,最开始应该传0
/// - j: 当前起始j,最开始应该传0
/// - currentSum: 当前位置之前的最小值
/// - Returns: 最小值
func findChild(_ triangle: [[Int]], _ i : Int, _ j : Int, _ currentSum : Int) -> Int {
let sums = currentSum + triangle[i][j];
let n = triangle.count;
if i == n - 1 {
//叶子
return sums;
}
let sum1 = findChild(triangle, i + 1, j, sums);
let sum2 = findChild(triangle, i + 1, j + 1, sums);
return sum1 < sum2 ? sum1 : sum2;
}
递归方法的复杂度会跟二维数组的复杂度 呈现指数增长。所以不可能用该方法。
动态规划:
func minimumTotal(_ triangle: [[Int]]) -> Int {
let n = triangle.count;
if n == 1 {
return triangle[0][0];
}
if n == 0 {
return 0;
}
//数组也可以换成字典(hashMap)[Int: Int]()
var arr = [Int]();
for _ in (0...(n - 1)) {
arr.append(0);
}
arr[0] = triangle[0][0];
for i in (1...(n - 1)) {
for j in (0...i).reversed() {
/*
这里倒序的原因是,不覆盖j。可能说这比较绕,可以自己找个简单的二维数组推一遍
假设不倒序的话,会发生什么,怎么避免?处理后和倒序比有什么变化?
不倒序的话:会在计算j位置的时候覆盖j,再计算加1后的(j + 1)位置时,使用的j位置的值不正确
如何避免:可以再建一个n的数组(或者使用n*n的二维数组),来记录i,j位置值。
处理后和倒序相比:处理后,使用的空间增加n(或n*(n-1)),相对来说增加了辅助空间
*/
if j == 0 {
arr[j] = arr[j] + triangle[i][j];
} else if j == i {
arr[j] = arr[j - 1] + triangle[i][j];
} else {
arr[j] = (arr[j] < arr[j - 1] ? arr[j] : arr[j - 1]) + triangle[i][j];
}
}
}
var num : Int = arr[0];
for i in (1...(n - 1)) {
let curNum = arr[i];
if curNum < num {
num = curNum;
}
}
return num;
}
个人认为,动态规划最重要的是寻找规律,找到规律后,即可实现。
简单介绍一下,上面的。
可以建立一个n*n的二维数组,利用i,j位置记录到达i,j位置的最短路径。
上面的代码,利用一个n的数组,来记录到达最底层i位置的最短路径。
测试代码:
func testMinimumTotal() {
let aa = [
[2],
[3,4],
[6,5,7],
[4,1,8,3]
];
var a = findChild(aa, 0, 0, 0);
var a1 = minimumTotal(aa);
}