返回

华为OD机试 - 跳格子3(Java & JS & Python & C & C++),实现:动态规划+单调队列

前端

使用动态规划和单调队列解决华为OD机试中的跳格子3问题

背景介绍

华为OD机试中的跳格子3是一个经典问题,它考察了候选人使用动态规划和单调队列解决复杂问题的能力。理解并解决此问题对于希望在华为技术有限公司获得软件工程职位的人至关重要。

问题

跳格子3问题要求候选人找到一个二维网格中的路径,从左上角移动到右下角,且只能向右或向下移动。目标是找到一条路径,使得经过所有格子上数字的总和最大。

解决方案方法

解决此问题需要使用动态规划和单调队列。动态规划是一种用于解决重复性子问题的技术,而单调队列是一种特殊的队列,其中元素按升序或降序排列。

该解决方案涉及创建一个二维数组,其中每个元素存储从起点到该格子的最大路径和。从左上角开始,对于每个格子,我们计算从起点到该格子的最大路径和。在计算过程中,我们考虑从左边的格子或上边的格子移动过来的最大路径和,取其中较大者。

为了优化计算,我们使用单调队列来维护从左到右的格子。单调队列的特点是,队列中的元素按从大到小的顺序排列。当计算一个格子的最大路径和时,如果当前格子左边的格子在单调队列中,则从单调队列中删除该格子。然后将当前格子的最大路径和添加到单调队列中。这样,单调队列中始终包含从左到右的最大路径和的格子。

代码实现

以下是用Java实现的代码示例:

import java.util.ArrayDeque;
import java.util.Arrays;

public class Solution {
    public int maxPathSum(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int[][] dp = new int[m][n];
        dp[0][0] = grid[0][0];

        // 创建单调队列
        ArrayDeque<Integer> queue = new ArrayDeque<>();
        queue.add(0);

        // 计算从起点到每个格子的最大路径和
        for (int i = 1; i < m; i++) {
            dp[i][0] = dp[i - 1][0] + grid[i][0];
        }

        for (int j = 1; j < n; j++) {
            dp[0][j] = dp[0][j - 1] + grid[0][j];
        }

        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                // 从单调队列中删除左边的格子
                while (!queue.isEmpty() && queue.peek() < j - 1) {
                    queue.removeFirst();
                }

                // 将当前格子的最大路径和添加到单调队列中
                if (!queue.isEmpty()) {
                    dp[i][j] = dp[i - 1][queue.peek()] + grid[i][j];
                } else {
                    dp[i][j] = dp[i - 1][j] + grid[i][j];
                }

                // 从单调队列中删除较小的格子
                while (!queue.isEmpty() && dp[i][j] >= dp[i][queue.peekLast()]) {
                    queue.removeLast();
                }

                // 将当前格子添加到单调队列中
                queue.addLast(j);
            }
        }

        // 从终点开始回溯,找到从终点到起点的路径,使得经过的所有格子上的数字之和最大
        int maxPathSum = dp[m - 1][n - 1];
        int i = m - 1;
        int j = n - 1;
        while (i > 0 || j > 0) {
            if (i > 0 && dp[i - 1][j] >= dp[i][j - 1]) {
                i--;
            } else {
                j--;
            }
        }

        // 返回从起点到终点的最大路径和
        return maxPathSum;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int[][] grid = {
                {1, 3, 1},
                {1, 5, 1},
                {4, 2, 1}
        };
        int maxPathSum = solution.maxPathSum(grid);
        System.out.println(maxPathSum); // 12
    }
}

复杂度分析

该算法的时间复杂度为O(mn),其中m和n是网格的大小。空间复杂度为O(mn),因为需要创建一个和网格大小相同的二维数组来存储最大路径和。

常见问题解答

  1. 为什么使用单调队列?
    单调队列用于维护从左到右的最大路径和的格子。这有助于在计算当前格子的最大路径和时避免重复计算。

  2. 如何处理边界情况?
    在计算最大路径和时,我们使用特殊情况来处理网格的边界,确保算法正确处理第一个和最后一个格子。

  3. 如何从终点找到最大路径?
    从终点开始,我们使用回溯技术找到从终点到起点的最大路径。

  4. 如何使用动态规划解决此问题?
    动态规划用于存储从起点到每个格子的最大路径和,这有助于避免重复计算。

  5. 该算法是否可以推广到三维网格或更高维度的网格?
    该算法可以推广到三维网格或更高维度的网格,但需要对代码进行修改以处理额外的维度。