📜  门|门CS 2013 |第 65 题(1)

📅  最后修改于: 2023-12-03 15:12:47.615000             🧑  作者: Mango

题目描述

在一个 $m \times n$ 的方格图中,每一个方格上都有一些苹果。你可以从左上角出发,每次向右或者向下移动一个格子,直到到达右下角。你可以多次经过同一个方格,但是每次经过一个方格,它的代价就会增加。请找出一条代价最小的路径。

输入格式

第一行包含两个整数 $m, n$,表示方格图的大小。

接下来 $m$ 行,每行包含 $n$ 个整数,表示对应位置上的苹果数目。

输出格式

输出一个整数,表示代价最小的路径的代价。

数据范围

$1 \leq m,n \leq 200$

输入样例:

3 3
1 3 1
1 5 1
4 2 1

输出样例:

7
算法1

(动态规划) $O(mn)$

动态规划

题目中明确给出了要找到代价最小的路径,同时有很多的重叠部分子问题,因此考虑使用动态规划进行求解。根据题目以及DP惯用的思路,我们可以通过分析最后一步,将整个问题分解为子问题。

在此处插入图片描述

我们可以定义状态 $\text{f}(i,j)$ 表示从左上角 $(1,1)$ 到点 $(i,j)$ 的最小代价。可以得到一个由子问题组成的递推式:

$$ \text{f(i,j)}=\min(\text{f(i,j-1)},\text{f(i-1,j)})+(\text{w(i,j)}) $$

其中 w(i,j) 表示坐标 (i,j) 上的权重。

注意最开始一层和一列的状态,需要先处理好。 最后的代价最小的路径即为状态 $\text{f}(m,n)$。

时间复杂度

每一个状态计算的复杂度为 $O(1)$,总共需要计算 $O(mn)$ 个状态,因此总时间复杂度为 $O(mn)$。

代码实现
def minCost(self, grid: List[List[int]]) -> int:
    m, n = len(grid), len(grid[0])
    f = [[float('inf')] * (n + 1) for _ in range(m + 1)]
    f[0][1], f[1][0] = 0, 0

    for i in range(1, m + 1):
        for j in range(1, n + 1):
            f[i][j] = min(f[i - 1][j], f[i][j - 1]) + grid[i - 1][j - 1]

    return f[-1][-1]
算法2

(堆优化Dijkstra) $O(mn\log(mn))$

算法思想

使用 Dijkstra 算法求解。我们将左上角看作图上的源点,右下角看作汇点,各个点之间权重为它们上下左右相邻的权重。 对于图上的任意两个点,我们可以很容易的估计从左上角到它们的最小代价。 这样,在优先队列中,每次取出代价最小的点,在它相邻的点中更新从起点到每个相邻点的最小代价,继续计算。

算法流程
  1. 初始化两个数组 dist[] 和 seen[],dist[] 数组用于存储从左上角顶点到当前顶点的最短距离,seen[] 数组记录节点是否被遍历。

  2. 用堆操作维护 dist[] 数据,堆中存储的元素是一个二元组 (dist, x),代表节点 x 和其已经松弛的边的最短距离。

  3. 初始化 dist[0]=0,其余为正无穷。然后将源点加入堆中,每次取出堆顶元素 (d, x),遍历该节点的所有邻居,如果从源点到邻居的距离 d 加上当前节点到邻居的路径长度 w(x,y) 小于之前记录的路径,则更新 dist[y],并将 (dist[y], y) 加入堆中。

  4. 重复步骤 3 直到出现右下角节点出队,最短路径即为 dist[n-1]。

复杂度分析
  • 时间复杂度 $O(mn \log(mn))$ 每个格子会被处理一次,因此需要左右进行 $O(mn)$ 次堆操作,一次操作的时间最坏情况下为 $O(\log(mn))$
  • 空间复杂度 $O(mn)$ 需要开 $mn$ 的数组 seen 和 dist,同时需要开一个堆。
代码实现
import heapq

def minCost(self, grid: List[List[int]]) -> int:
    m, n = len(grid), len(grid[0])
    dist = [[0x7f7f7f7f] * n for _ in range(m)]
    dist[0][0] = grid[0][0]
    heap = [(dist[0][0], 0, 0)]  # (distance, row, col)
    seen = set()
    directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]

    while heap:
        d, r, c = heapq.heappop(heap)

        if r == m - 1 and c == n - 1:
            return d

        if (r, c) in seen:
            continue

        seen.add((r, c))

        for dx, dy in directions:
            x, y = r + dx, c + dy
            if 0 <= x < m and 0 <= y < n and (x, y) not in seen:
                new_dist = d + grid[x][y]
                if new_dist < dist[x][y]:
                    dist[x][y] = new_dist
                    heapq.heappush(heap, (dist[x][y], x, y))

    return -1