Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.
OR
Given a triangle array, return the minimum path sum from top to bottom.
For each step, you may move to an adjacent number of the row below. More formally, if you are on index i on the current row, you may move to either index i or index i + 1 on the next row.
Input: triangle =[[2],[3,4],[6,5,7],[4,1,8,3]]Output: 11Explanation: The triangle looks like:2̲
3̲ 465̲ 741̲ 83The minimum path sum from top to bottom is2+3+5+1=11(underlined above).
Follow up: Could you do this using only O(n) extra space, where n is the total number of rows in the triangle?
The first mistake people make with this question is they fail to understand that you cannot simply traverse down the triangle taking the lowest number (greedy approach).
At each recursion call, we need to make the decision to traverse down the left or right path. Because of this, it is possible to reach any given node using two different paths. Looking at the recursion tree, we can see a duplicate calculation taking place for node with value 5 in the 3rd row.
classSolution {
private Map<String, Integer> memo =new HashMap<>();
publicintminimumTotal(List<List<Integer>> triangle) {
return dfs(triangle, 0, 0);
}
publicintdfs(List<List<Integer>> triangle, int rowNum, int index) {
// base case, we are at the last rowif (rowNum == triangle.size() - 1) {
return triangle.get(rowNum).get(index);
}
// use a unique key for memoization String key = rowNum +","+ index;
// check if result is already in memoif (memo.containsKey(key)) {
return memo.get(key);
}
int currentValue = triangle.get(rowNum).get(index);
int minSumLeft = dfs(triangle, rowNum + 1, index);
int minSumRight = dfs(triangle, rowNum + 1, index + 1);
// store result in memoint result = currentValue + Math.min(minSumLeft, minSumRight);
memo.put(key, result);
return result;
}
}
classSolution:
def __init__(self):
self.memo = {}
defminimumTotal(self, triangle: List[List[int]]) -> int:
return self.dfs(triangle, 0, 0)
defdfs(self, triangle: List[List[int]], row: int, idx: int) -> int:
# base case, we are at the last rowif row == len(triangle) -1:
return triangle[row][idx]
# use a unique key for memoization key = (row, idx)
# check if result is already in memoif key in self.memo:
return self.memo[key]
current_value = triangle[row][idx]
min_sum_left = self.dfs(triangle, row +1, idx)
min_sum_right = self.dfs(triangle, row +1, idx +1)
# store result in memo result = current_value + min(min_sum_left, min_sum_right)
self.memo[key] = result
return result
⏰ Time complexity: O(n^2) – Each subproblem result is computed only once and stored in the memo.
🧺 Space complexity: O(n^2) – The memoization table stores results for each subproblem, which in the worst case is proportional to the number of elements in the triangle.
The table has meaningful values in the lower half, and at each level, one of the columns gets fixed moving bottom-up. Thus, we can use the bottom level array as the dp table and update the columns bottom-up, reducing the space complexity from O(n^2) to O(n).