【2024年华为OD机试】 (C卷,200分)- 园区参观路径(JavaScript&Java & Python&C/C++)

一、问题描述

题目解析

题目描述

园区某部门举办了Family Day,邀请员工及其家属参加。将公司园区视为一个矩形,起始园区设置在左上角,终点园区设置在右下角。家属参观园区时,只能向右和向下园区前进,求从起始园区到终点园区会有多少条不同的参观路径。

输入描述
  • 第一行为园区的长和宽;
  • 后面每一行表示该园区是否可以参观,0表示可以参观,1表示不能参观。
输出描述

输出为不同的路径数量。

用例
  • 输入:
    3 3
    0 0 0
    0 1 0
    0 0 0
    
  • 输出:
    2
    
  • 说明:无
解题思路

本题可以使用深度优先搜索(DFS)或动态规划(DP)来解题。

深度优先搜索(DFS)

DFS的每一条递归分支都对应一条路径。如果某个递归分支可以走到终点位置,那么说明该递归分支对应的路径可达终点。递归进入下一个位置的条件是:

  1. 下一个位置在上一个位置的下边或右边;
  2. 下一个位置不越界;
  3. 下一个位置可以参观(矩阵元素值为0)。

由于本题限定只能从当前位置向下或者向右进入下一个位置,因此不用担心走回头路的问题,即不用建立visited表记录走过的位置。

注意:如果地图矩阵数量级过大,基于递归实现的DFS可能会发生StackOverFlow异常,因此更推荐使用基于栈结构实现的DFS。此外,DFS在大数量级情况下可能会超时。

动态规划(DP)

更优的解法是利用动态规划。我们可以定义一个dp二维数组,dp[i][j]的含义是:从坐标(0,0)到达坐标(i, j)的路径数。

由于只能向下或者向右运动,因此到达一个坐标点,可能来自其上方,也可能来自其左方。因此:

dp[i][j] = dp[i-1][j] + dp[i][j-1]

即:

  • 如果到达(i-1,j)的路径有dp[i-1][j]条,那么到达(i,j)的路径也有dp[i-1][j]条;
  • 同理,到达(i, j-1)的路径有dp[i][j-1]条,那么到达(i,j)的路径也有dp[i][j-1]条。

初始化

  • dp[0][0] 初始化时,需要注意(0,0)坐标位置是否可以参观。如果不可以参观,则到达(0,0)的路径为0条,否则为1条。

注意事项

  • 在套用公式时,注意索引越界问题。

通过动态规划的方法,可以有效地计算出从起始园区到终点园区的不同路径数量。

步骤详解

要解决这个网格路径计数的问题,可以采用以下几种方法:动态规划、基于递归的深度优先搜索(DFS)和基于栈的DFS。每种方法都有其独特的实现方式和适用场景。

方法一:动态规划
  1. 输入处理

    • 首先,读取网格的行数n和列数m
    • 然后,读取网格的每个位置的值,存储在二维数组matrix中,其中0表示可通行,1表示障碍物。
  2. 初始化动态规划表

    • 创建一个与网格大小相同的二维数组dp,用于存储到达每个位置的路径数量。
    • 起点位置dp[0][0]初始化为1,表示只有一种方式到达起点(即不移动)。
  3. 填充动态规划表

    • 遍历整个网格,对于每个位置(i, j),如果不是障碍物,就将来自上方和左方的路径数相加,得到当前位置的路径数。
    • 具体来说:
      • 如果可以从上方到达当前位置,则dp[i][j] += dp[i-1][j]
      • 如果可以从左方到达当前位置,则dp[i][j] += dp[i][j-1]
  4. 结果输出

    • 最终,dp[n-1][m-1]即为从起点到终点的所有可能路径的数量。

时间复杂度

  • 该算法的时间复杂度为O(n*m),因为每个位置只被访问一次。

空间复杂度

  • 空间复杂度为O(n*m),用于存储动态规划表dp
方法二:基于递归的深度优先搜索(DFS)
  1. 输入处理

    • 与动态规划相同,读取网格的尺寸和每个位置的值。
  2. 定义移动偏移量

    • 定义移动的方向,通常是向下offsets = ((1, 0), (0, 1)),表示从当前位置向下和向右移动。
  3. 初始化路径数量

    • 使用全局变量ans来记录到达终点的路径数量。
  4. 递归函数dfs(x, y)

    • 终止条件
      • 如果当前位置是网格的终点(n-1, m-1),则增加路径数量ans,并返回。
    • 遍历移动方向
      • 对于每个移动方向,计算新的位置(newX, newY)
      • 检查新位置是否越界,是否是障碍物。如果不是,则递归调用dfs(newX, newY)
  5. 调用递归函数

    • 从起点位置(0,0)开始递归搜索,前提是起点是可通行的。
  6. 结果输出

    • 递归结束后,输出路径数量ans

时间复杂度

  • 该算法的时间复杂度为O(2^(n+m)),在最坏情况下,每一步有两种选择(向下或向右),类似于计算斐波那契数列的递归方式,效率较低。

空间复杂度

  • 由于使用递归,空间复杂度主要取决于递归调用的深度,理论上为O(n+m),而实际的空间消耗可能较高,尤其是在路径较长时容易导致栈溢出。
方法三:基于栈的深度优先搜索(DFS)
  1. 输入处理

    • 与动态规划相同,读取网格的尺寸和每个位置的值。
  2. 定义移动偏移量

    • 与递归DFS相同,定义移动方向offsets = ((1, 0), (0, 1))
  3. 初始化路径数量

    • 使用全局变量ans记录到达终点的路径数量。
  4. 栈初始化

    • 创建一个栈stack,用于显式模拟递归调用的过程。
    • 如果起点是可通行的,将起点位置编码为整数(例如,pos = x*m + y)并压入栈中。
  5. 栈式DFS循环

    • 在栈不为空时,持续处理栈中的元素。
    • 取出栈顶位置,解码为行号x和列号y
    • 终止条件
      • 如果当前位置是终点,增加路径数量ans,并继续处理。
    • 遍历移动方向
      • 对于每个移动方向,计算新的位置(newX, newY)
      • 检查新位置是否越界,是否是障碍物。如果不是,将新位置编码为整数并压入栈中。
  6. 结果输出

    • 栈处理完成后,输出路径数量ans

时间复杂度

  • 与递归DFS相同,时间复杂度为O(2^(n+m)),效率较低,适用于小规模网格。

空间复杂度

  • 使用显式的栈结构,空间复杂度为O(n+m),因为栈中最多存储与路径数相当的元素数量。相比递归DFS,栈式DFS在空间管理上更为灵活,避免了递归深度过大的问题。

总结

  • 动态规划:适用于网格较大、需要快速计算路径数量的情况,时间和空间复杂度均为O(n*m)。
  • 递归DFS:实现简单,适用于小规模网格,但可能因栈溢出而受限,时间复杂度为O(2^(n+m))。
  • 栈式DFS:避免递归栈溢出问题,适用于中小规模网格,时间复杂度与递归DFS相同,但空间利用更为高效。

根据具体需求和网格大小的不同,可以选择最适合当前场景的算法。在大多数情况下,动态规划是首选算法,而DFS方法适用于特定的小规模应用或需要明确路径的情况。

二、JavaScript算法源码

代码详解

以下是基于动态规划(DP)、递归实现的深度优先搜索(DFS)以及基于栈实现的深度优先搜索(DFS)的代码,并附有详细注释和讲解。


1. 动态规划(DP)实现
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;

void (async function () {
  // 读取输入:园区的长n(行数)和宽m(列数)
  const [n, m] = (await readline()).split(" ").map(Number);

  // 读取地图矩阵:0表示可以参观,1表示不能参观
  const matrix = [];
  for (let i = 0; i < n; i++) {
    matrix.push((await readline()).split(" ").map(Number));
  }

  // 如果起点(0,0)或终点(n-1, m-1)不能参观,则没有路径
  if (matrix[0][0] == 1 || matrix[n - 1][m - 1] == 1) {
    console.log(0);
    return;
  }

  // 初始化dp数组:dp[i][j]表示从(0,0)到(i,j)的路径数
  const dp = new Array(n).fill(0).map(() => new Array(m).fill(0));
  dp[0][0] = 1; // 起点到起点只有一条路径

  // 动态规划填充dp数组
  for (let i = 0; i < n; i++) {
    for (let j = 0; j < m; j++) {
      // 如果当前格子不能参观,则跳过
      if (matrix[i][j] == 1) continue;

      // 如果可以从上方格子到达当前格子
      if (i > 0) {
        dp[i][j] += dp[i - 1][j];
      }

      // 如果可以从左方格子到达当前格子
      if (j > 0) {
        dp[i][j] += dp[i][j - 1];
      }
    }
  }

  // 输出从起点到终点的路径数
  console.log(dp[n - 1][m - 1]);
})();

代码讲解:

  1. 输入处理
    • 读取园区的长n和宽m
    • 读取地图矩阵,0表示可以参观,1表示不能参观。
  2. 边界检查
    • 如果起点(0,0)或终点(n-1, m-1)不能参观,则直接输出0,因为没有路径。
  3. 动态规划数组dp
    • dp[i][j]表示从起点(0,0)(i,j)的路径数。
    • 初始化dp[0][0] = 1,因为起点到起点只有一条路径。
  4. 状态转移
    • 对于每个格子(i,j),如果可以从上方(i-1,j)到达,则dp[i][j] += dp[i-1][j]
    • 如果可以从左方(i,j-1)到达,则dp[i][j] += dp[i][j-1]
  5. 输出结果
    • 最终dp[n-1][m-1]即为从起点到终点的路径数。

2. 递归实现的深度优先搜索(DFS)
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;

void (async function () {
  // 读取输入:园区的长n(行数)和宽m(列数)
  const [n, m] = (await readline()).split(" ").map(Number);

  // 读取地图矩阵:0表示可以参观,1表示不能参观
  const matrix = [];
  for (let i = 0; i < n; i++) {
    matrix.push((await readline()).split(" ").map(Number));
  }

  // 向下、向右的偏移量
  const offsets = [
    [1, 0], // 向下
    [0, 1], // 向右
  ];

  // 记录题解:从起点到终点的路径数
  let ans = 0;

  // 深度优先搜索函数
  function dfs(x, y) {
    // 如果当前分支可以走到终点,则对应分支路径可行
    if (x == n - 1 && y == m - 1) {
      ans++;
      return;
    }

    // 从当前位置(x, y)向下或者向右走
    for (let [offsetX, offsetY] of offsets) {
      // 新位置(newX, newY)
      const newX = x + offsetX;
      const newY = y + offsetY;

      // 如果新位置没有越界且新位置可以参观,则进入
      if (
        newX >= 0 &&
        newX < n &&
        newY >= 0 &&
        newY < m &&
        matrix[newX][newY] == 0
      ) {
        dfs(newX, newY);
      }
    }
  }

  // 从(0,0)位置开始深搜,深搜对应的每条分支都对应一条路径
  if (matrix[0][0] == 0) {
    dfs(0, 0);
  }

  // 输出结果
  console.log(ans);
})();

代码讲解:

  1. 输入处理
    • 读取园区的长n和宽m
    • 读取地图矩阵,0表示可以参观,1表示不能参观。
  2. 偏移量定义
    • 定义向下和向右的偏移量offsets
  3. 深度优先搜索(DFS)
    • 从起点(0,0)开始递归搜索。
    • 如果当前位置是终点(n-1, m-1),则路径数ans++
    • 对于每个位置,尝试向下和向右移动,如果新位置合法且可以参观,则递归进入。
  4. 输出结果
    • 最终ans即为从起点到终点的路径数。

3. 基于栈实现的深度优先搜索(DFS)
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;

void (async function () {
  // 读取输入:园区的长n(行数)和宽m(列数)
  const [n, m] = (await readline()).split(" ").map(Number);

  // 读取地图矩阵:0表示可以参观,1表示不能参观
  const matrix = [];
  for (let i = 0; i < n; i++) {
    matrix.push((await readline()).split(" ").map(Number));
  }

  // 向下、向右的偏移量
  const offsets = [
    [1, 0], // 向下
    [0, 1], // 向右
  ];

  // 记录题解:从起点到终点的路径数
  let ans = 0;

  // 基于栈的深度优先搜索函数
  function dfs() {
    const stack = [];

    // 如果起点可以参观,则将其压入栈
    if (matrix[0][0] == 0) {
      stack.push(0); // 使用一维索引表示位置
    }

    // 栈不为空时继续搜索
    while (stack.length > 0) {
      const pos = stack.pop();

      // 将一维索引转换为二维坐标
      const y = pos % m;
      const x = (pos - y) / m;

      // 如果当前位置是终点,则路径数加1
      if (x == n - 1 && y == m - 1) {
        ans++;
        continue;
      }

      // 从当前位置向下或向右移动
      for (let [offsetX, offsetY] of offsets) {
        const newX = x + offsetX;
        const newY = y + offsetY;

        // 如果新位置合法且可以参观,则压入栈
        if (
          newX >= 0 &&
          newX < n &&
          newY >= 0 &&
          newY < m &&
          matrix[newX][newY] == 0
        ) {
          stack.push(newX * m + newY); // 使用一维索引表示新位置
        }
      }
    }
  }

  // 从(0,0)位置开始深搜
  dfs();

  // 输出结果
  console.log(ans);
})();

代码讲解:

  1. 输入处理
    • 读取园区的长n和宽m
    • 读取地图矩阵,0表示可以参观,1表示不能参观。
  2. 偏移量定义
    • 定义向下和向右的偏移量offsets
  3. 基于栈的深度优先搜索(DFS)
    • 使用栈模拟递归过程,避免递归调用栈溢出。
    • 将起点(0,0)压入栈。
    • 对于栈中的每个位置,尝试向下和向右移动,如果新位置合法且可以参观,则压入栈。
    • 如果当前位置是终点(n-1, m-1),则路径数ans++
  4. 输出结果
    • 最终ans即为从起点到终点的路径数。

总结

  • 动态规划:适合处理大规模数据,时间复杂度为O(n*m)
  • 递归DFS:简单直观,但可能因递归深度过大导致栈溢出。
  • 基于栈的DFS:避免了递归栈溢出的问题,适合处理较大规模数据。

三、Java算法源码

动态规划实现

import java.util.Scanner;

public class Main {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);

    int n = sc.nextInt(); // 读取行数n,表示网格的长度
    int m = sc.nextInt(); // 读取列数m,表示网格的宽度

    int[][] matrix = new int[n][m]; // 创建网格矩阵
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < m; j++) {
        matrix[i][j] = sc.nextInt(); // 读取每个位置的值,0表示可通过,1表示障碍物
      }
    }

    // 检查起点或终点是否为障碍物,如果是,则无法到达终点,输出0
    if (matrix[0][0] == 1 || matrix[n - 1][m - 1] == 1) {
      System.out.println(0);
      return;
    }

    long[][] dp = new long[n][m]; // 创建动态规划数组,用于存储到达每个位置的路径数量
    dp[0][0] = 1; // 起点只有一种路径

    // 遍历网格,计算每个位置的路径数量
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < m; j++) {
        if (matrix[i][j] == 1) continue; // 如果当前位置是障碍物,跳过

        // 从上方到达当前位置的路径数
        if (i > 0) {
          dp[i][j] += dp[i - 1][j];
        }

        // 从左方到达当前位置的路径数
        if (j > 0) {
          dp[i][j] += dp[i][j - 1];
        }
      }
    }

    // 输出到达终点的路径数量
    System.out.println(dp[n - 1][m - 1]);
  }
}

基于递归的深度优先搜索(DFS)

import java.util.Scanner;

public class Main {
  static int n;
  static int m;
  static int[][] matrix;
  static int[][] offsets = {{1, 0}, {0, 1}}; // 移动方向:下和右

  static int ans = 0; // 记录路径数量

  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);

    n = sc.nextInt(); // 读取行数n
    m = sc.nextInt(); // 读取列数m

    matrix = new int[n][m]; // 创建网格矩阵
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < m; j++) {
        matrix[i][j] = sc.nextInt(); // 读取每个位置的值
      }
    }

    // 如果起点是可通行的,开始递归搜索
    if (matrix[0][0] == 0) {
      dfs(0, 0);
    }

    // 输出路径数量
    System.out.println(ans);
  }

  public static void dfs(int x, int y) {
    // 如果到达终点,增加路径数量
    if (x == n - 1 && y == m - 1) {
      ans++;
      return;
    }

    // 遍历可能的移动方向
    for (int[] offset : offsets) {
      int newX = x + offset[0]; // 新的位置x
      int newY = y + offset[1]; // 新的位置y

      // 检查新位置是否越界,是否为障碍物
      if (newX < 0 || newX >= n || newY < 0 || newY >= m || matrix[newX][newY] == 1) {
        continue; // 如果不符合条件,跳过
      }

      // 递归访问新位置
      dfs(newX, newY);
    }
  }
}

基于栈的深度优先搜索(DFS)

import java.util.LinkedList;
import java.util.Scanner;

public class Main {
  static int n;
  static int m;
  static int[][] matrix;
  static int[][] offsets = {{1, 0}, {0, 1}}; // 移动方向:下和右

  static int ans = 0; // 记录路径数量

  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);

    n = sc.nextInt(); // 读取行数n
    m = sc.nextInt(); // 读取列数m

    matrix = new int[n][m]; // 创建网格矩阵
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < m; j++) {
        matrix[i][j] = sc.nextInt(); // 读取每个位置的值
      }
    }

    // 如果起点是可通行的,开始栈式DFS
    if (matrix[0][0] == 0) {
      dfs();
    }

    // 输出路径数量
    System.out.println(ans);
  }

  public static void dfs() {
    LinkedList<Integer> stack = new LinkedList<>(); // 使用栈来模拟递归

    // 将起点压入栈中,位置编码为x*m + y
    stack.addLast(0 * m + 0);

    while (!stack.isEmpty()) {
      int pos = stack.removeLast(); // 取出栈顶位置
      int x = pos / m; // 计算行号
      int y = pos % m; // 计算列号

      // 如果到达终点,增加路径数量
      if (x == n - 1 && y == m - 1) {
        ans++;
        continue;
      }

      // 遍历可能的移动方向
      for (int[] offset : offsets) {
        int newX = x + offset[0]; // 新的位置x
        int newY = y + offset[1]; // 新的位置y

        // 检查新位置是否越界,是否为障碍物
        if (newX >= 0 && newX < n && newY >= 0 && newY < m && matrix[newX][newY] == 0) {
          stack.addLast(newX * m + newY); // 将新位置压入栈中
        }
      }
    }
  }
}

代码讲解

动态规划实现
  1. 输入处理:读取网格的行数和列数,然后读取网格中的每个值,存储在二维数组matrix中。
  2. 起点和终点检查:如果起点或终点是障碍物(值为1),直接输出0,因为无法到达终点。
  3. 动态规划数组初始化:创建一个与网格大小相同的二维数组dpdp[i][j]表示从起点到位置(i,j)的路径数量。起点dp[0][0]初始化为1。
  4. 遍历网格:对于每个位置,如果它不是障碍物,就将来自上方和左方的路径数相加,得到当前的路径数。
  5. 输出结果:最终,dp[n-1][m-1]即为到达终点的路径数量。
基于递归的DFS
  1. 输入处理:与动态规划实现相同,读取网格的行数、列数和网格值。
  2. 递归搜索初始化:如果起点是可通行的(值为0),调用dfs函数开始递归搜索。
  3. 递归函数dfs
    • 到达终点:如果当前位置是终点,增加路径数量ans
    • 遍历移动方向:尝试向下和向右移动,检查新位置是否越界或为障碍物,如果是,跳过;否则,递归访问新位置。
  4. 输出结果:递归完成后,输出路径数量ans
基于栈的DFS
  1. 输入处理:与动态规划实现相同,读取网格的行数、列数和网格值。
  2. 栈初始化:如果起点是可通行的,将起点位置编码为整数压入栈中。
  3. 栈式DFS循环
    • 取出栈顶位置:将栈顶位置解码为行号和列号。
    • 到达终点:如果当前位置是终点,增加路径数量ans
    • 遍历移动方向:尝试向下和向右移动,检查新位置是否越界或为障碍物,如果是,跳过;否则,将新位置压入栈中。
  4. 输出结果:栈处理完成后,输出路径数量ans

总结

  • 动态规划:通过维护一个二维数组,记录到达每个位置的路径数量,避免重复计算,时间复杂度为O(n*m)。
  • 递归DFS:通过递归遍历所有可能的路径,时间复杂度较高,可能达到O(2^(n+m))。
  • 栈式DFS:模拟递归过程,使用显式的栈结构,避免递归可能导致的栈溢出问题,时间复杂度与递归DFS相同。

每个方法都有其优缺点,动态规划在时间和空间复杂度上更优,而DFS适用于路径数量较少的情况,或者需要记录具体路径的情况。

四、Python算法源码

动态规划实现

# 输入获取
n, m = map(int, input().split())  # 读取网格的长和宽,n表示行数,m表示列数
matrix = [list(map(int, input().split())) for _ in range(n)]  # 读取网格矩阵,0表示可通行,1表示障碍物


# 算法入口
def getResult():
    # 如果起点或终点是障碍物,则无法通过
    if matrix[0][0] == 1 or matrix[n - 1][m - 1] == 1:
        return 0

    # 初始化动态规划二维数组,dp[i][j]表示从起点到达位置(i, j)的路径数量
    dp = [[0 for _ in range(m)] for _ in range(n)]
    dp[0][0] = 1  # 起点初始化为1,表示只有一种方式到达起点(即不移动)

    # 遍历整个网格
    for i in range(n):
        for j in range(m):
            # 如果当前位置是障碍物,跳过
            if matrix[i][j] == 1:
                continue

            # 如果当前位置可以从上方到达,则累加上方的路径数量
            if i > 0:
                dp[i][j] += dp[i - 1][j]

            # 如果当前位置可以从左方到达,则累加左方的路径数量
            if j > 0:
                dp[i][j] += dp[i][j - 1]

    # 返回终点位置的路径数量
    return dp[n - 1][m - 1]


# 算法调用
print(getResult())
代码讲解
  1. 输入处理
    • 读取网格的行数和列数。
    • 读取网格中每个位置的值,0表示可通行,1表示障碍物。
  2. 起点和终点检查
    • 如果起点或终点是障碍物,直接返回路径数量为0
  3. 动态规划数组初始化
    • 创建一个二维数组dp,其大小与网格大小一致。
    • 初始化起点dp[0][0]1,表示只有一种路径从起点到起点。
  4. 遍历网格
    • 如果当前位置是障碍物,则跳过。
    • 如果可以从上方到达当前位置,则将上方路径数量加到当前位置。
    • 如果可以从左方到达当前位置,则将左方路径数量加到当前位置。
  5. 结果输出
    • 返回网格终点位置dp[n-1][m-1]的路径数量。

基于递归实现的深搜

# 输入获取
n, m = map(int, input().split())  # 读取网格的长和宽,n表示行数,m表示列数
matrix = [list(map(int, input().split())) for _ in range(n)]  # 读取网格矩阵,0表示可通行,1表示障碍物

# 定义向下和向右移动的偏移量
offsets = ((1, 0), (0, 1))

# 用于记录可行路径数量
ans = 0


# 深度优先搜索递归函数
def dfs(x, y):
    global ans  # 使用全局变量来记录路径数量

    # 如果到达终点,说明当前分支路径可行,路径数量加1
    if x == n - 1 and y == m - 1:
        ans += 1
        return

    # 遍历可能的移动方向(向下、向右)
    for offsetX, offsetY in offsets:
        newX = x + offsetX  # 计算新位置的行号
        newY = y + offsetY  # 计算新位置的列号

        # 判断新位置是否越界,以及是否是障碍物
        if n > newX >= 0 and m > newY >= 0 and matrix[newX][newY] == 0:
            dfs(newX, newY)  # 如果新位置可行,递归调用


# 算法调用
if matrix[0][0] == 0:  # 如果起点可通行,则开始深搜
    dfs(0, 0)  # 从起点(0, 0)开始递归
print(ans)
代码讲解
  1. 输入处理
    • 与动态规划的输入方式相同。
  2. 定义移动方向
    • 定义一个数组offsets,存储向下移动和向右移动的偏移量。
  3. 递归函数dfs
    • 如果当前点是终点,说明路径可行,路径数量ans1
    • 遍历向下和向右的两种移动方向,计算新的位置。
    • 如果新位置没有越界,也不是障碍物,则递归搜索新位置。
  4. 递归调用
    • 先检查起点是否可通行,如果可通行,从起点开始递归搜索。
  5. 结果输出
    • 递归结束后,输出路径数量ans

基于栈实现的深搜

# 输入获取
n, m = map(int, input().split())  # 读取网格的长和宽,n表示行数,m表示列数
matrix = [list(map(int, input().split())) for _ in range(n)]  # 读取网格矩阵,0表示可通行,1表示障碍物

# 定义向下和向右移动的偏移量
offsets = ((1, 0), (0, 1))

# 用于记录可行路径数量
ans = 0


# 使用栈模拟深度优先搜索
def dfs():
    global ans  # 使用全局变量来记录路径数量

    stack = []  # 用栈来存储搜索的路径

    # 如果起点可通行,则将起点加入栈
    if matrix[0][0] == 0:
        stack.append(0)  # 使用单个整数记录位置,编码方式为 x * m + y

    # 当栈不为空时,继续处理
    while len(stack) > 0:
        pos = stack.pop()  # 取出栈顶的位置

        x = pos // m  # 解码行号
        y = pos % m  # 解码列号

        # 如果到达终点,说明路径可行,路径数量加1
        if x == n - 1 and y == m - 1:
            ans += 1
            continue

        # 遍历向下和向右的两种移动方向
        for offsetX, offsetY in offsets:
            newX = x + offsetX  # 计算新位置的行号
            newY = y + offsetY  # 计算新位置的列号

            # 如果新位置没有越界,也不是障碍物,则将新位置压入栈
            if n > newX >= 0 and m > newY >= 0 and matrix[newX][newY] == 0:
                stack.append(newX * m + newY)  # 将新位置编码为整数后压入栈


# 算法调用
dfs()  # 调用栈实现的深搜
print(ans)
代码讲解
  1. 输入处理
    • 与动态规划的输入方式相同。
  2. 栈初始化
    • 如果起点可通行,将起点位置编码为整数并压入栈。
  3. 循环处理栈
    • 每次从栈中取出一个位置,解码为行号和列号。
    • 如果当前位置是终点,路径数量加1。
    • 遍历向下和向右的移动方向,如果新位置没有越界且可通行,将其编码后压入栈。
  4. 结果输出
    • 栈处理完成后,输出路径数量ans

总结

方法 时间复杂度 空间复杂度 特点
动态规划 O(n * m) O(n * m) 高效,可快速计算路径数量
递归 DFS O(2^(n+m)) O(n + m) 简单实现,但可能导致栈溢出
栈实现 DFS O(2^(n+m)) O(n + m) 避免栈溢出,适用于中小规模问题

根据需求选择适合的算法,动态规划是最佳选择,而DFS适用于路径较少的小规模问题。

五、C/C++算法源码:

动态规划实现

C++实现
#include 
#include 
using namespace std;

int main() {
    // 读取网格的行数(n)和列数(m)
    int n, m;
    cin >> n >> m;

    // 地图矩阵,matrix[i][j]表示网格中第i行第j列的位置
    vector<vector<int>> matrix(n, vector<int>(m));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> matrix[i][j];  // 输入地图矩阵,0表示可通行,1表示障碍物
        }
    }

    // 如果起点或终点是障碍物,直接输出0并结束程序
    if (matrix[0][0] == 1 || matrix[n - 1][m - 1] == 1) {
        cout << 0 << endl;
        return 0;
    }

    // 动态规划表:dp[i][j]表示从起点到达位置(i, j)的路径数量
    vector<vector<long>> dp(n, vector<long>(m, 0));
    dp[0][0] = 1;  // 初始化起点为1,表示只有一种方式到达起点

    // 遍历地图矩阵
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (matrix[i][j] == 1) continue;  // 如果当前位置是障碍物,跳过

            // 如果可以从上方到达当前位置,则累加上方的路径数量
            if (i > 0) dp[i][j] += dp[i - 1][j];

            // 如果可以从左方到达当前位置,则累加左方的路径数量
            if (j > 0) dp[i][j] += dp[i][j - 1];
        }
    }

    // 输出最终从起点到达终点的路径数量
    cout << dp[n - 1][m - 1] << endl;

    return 0;
}

C语言实现
#include 

int main() {
    // 读取网格的行数(n)和列数(m)
    int n, m;
    scanf("%d %d", &n, &m);

    // 定义地图矩阵,0表示可通行,1表示障碍物
    int matrix[n][m];
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            scanf("%d", &matrix[i][j]);  // 输入地图矩阵
        }
    }

    // 如果起点或终点是障碍物,直接输出0并结束程序
    if (matrix[0][0] == 1 || matrix[n - 1][m - 1] == 1) {
        printf("0\n");
        return 0;
    }

    // 动态规划表:dp[i][j]表示从起点到达位置(i, j)的路径数量
    long dp[n][m];
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            dp[i][j] = 0;  // 初始化动态规划表
        }
    }

    dp[0][0] = 1;  // 初始化起点为1

    // 遍历地图矩阵
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (matrix[i][j] == 1) continue;  // 如果当前位置是障碍物,跳过

            // 如果可以从上方到达当前位置,则累加上方的路径数量
            if (i > 0) dp[i][j] += dp[i - 1][j];

            // 如果可以从左方到达当前位置,则累加左方的路径数量
            if (j > 0) dp[i][j] += dp[i][j - 1];
        }
    }

    // 输出最终从起点到达终点的路径数量
    printf("%ld\n", dp[n - 1][m - 1]);

    return 0;
}

基于递归实现的深搜

C++实现
#include 
#include 
using namespace std;

// 网格大小
int n, m;
// 地图矩阵
vector<vector<int>> matrix;
// 向下和向右的偏移量
int offsets[2][2] = {{1, 0}, {0, 1}};
// 记录路径数量
int ans = 0;

// 递归深搜函数
void dfs(int x, int y) {
    // 如果到达终点,记录一条可行路径
    if (x == n - 1 && y == m - 1) {
        ans++;
        return;
    }

    // 遍历移动方向(向下或向右)
    for (int i = 0; i < 2; i++) {
        int newX = x + offsets[i][0];
        int newY = y + offsets[i][1];

        // 如果新位置合法且可通行,继续深搜
        if (newX >= 0 && newX < n && newY >= 0 && newY < m && matrix[newX][newY] == 0) {
            dfs(newX, newY);
        }
    }
}

int main() {
    // 读取网格大小
    cin >> n >> m;
    matrix.resize(n, vector<int>(m));

    // 读取地图矩阵
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> matrix[i][j];
        }
    }

    // 如果起点可通行,则开始深搜
    if (matrix[0][0] == 0) {
        dfs(0, 0);
    }

    // 输出路径数量
    cout << ans << endl;

    return 0;
}

C语言实现
#include 
#include 

// 网格大小
int n, m;
// 地图矩阵
int *matrix;
// 向下和向右的偏移量
int offsets[2][2] = {{1, 0}, {0, 1}};
// 记录路径数量
int ans = 0;

// 递归深搜函数
void dfs(int x, int y) {
    // 如果到达终点,记录一条可行路径
    if (x == n - 1 && y == m - 1) {
        ans++;
        return;
    }

    // 遍历移动方向(向下或向右)
    for (int i = 0; i < 2; i++) {
        int newX = x + offsets[i][0];
        int newY = y + offsets[i][1];

        // 如果新位置合法且可通行,继续深搜
        if (newX >= 0 && newX < n && newY >= 0 && newY < m && matrix[newX * m + newY] == 0) {
            dfs(newX, newY);
        }
    }
}

int main() {
    // 读取网格大小
    scanf("%d %d", &n, &m);
    matrix = (int *)calloc(n * m, sizeof(int));

    // 读取地图矩阵
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            scanf("%d", &matrix[i * m + j]);
        }
    }

    // 如果起点可通行,则开始深搜
    if (matrix[0] == 0) {
        dfs(0, 0);
    }

    // 输出路径数量
    printf("%d\n", ans);
    free(matrix);

    return 0;
}

基于栈实现的深搜

C++实现
#include 
#include 
#include 
using namespace std;

// 网格大小
int n, m;
// 地图矩阵
vector<vector<int>> matrix;
// 向下和向右的偏移量
int offsets[2][2] = {{1, 0}, {0, 1}};
// 记录路径数量
int ans = 0;

void dfs() {
    stack<int> s;

    if (matrix[0][0] == 0) {
        s.push(0);  // 起点位置编码为0
    }

    while (!s.empty()) {
        int pos = s.top();
        s.pop();

        int x = pos / m;  // 行号
        int y = pos % m;  // 列号

        // 如果到达终点,记录一条可行路径
        if (x == n - 1 && y == m - 1) {
            ans++;
            continue;
        }

        // 遍历移动方向(向下或向右)
        for (int i = 0; i < 2; i++) {
            int newX = x + offsets[i][0];
            int newY = y + offsets[i][1];

            // 如果新位置合法且可通行,压入栈中
            if (newX >= 0 && newX < n && newY >= 0 && newY < m && matrix[newX][newY] == 0) {
                s.push(newX * m + newY);
            }
        }
    }
}

int main() {
    cin >> n >> m;
    matrix.resize(n, vector<int>(m));

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> matrix[i][j];
        }
    }

    dfs();
    cout << ans << endl;

    return 0;
}

C语言代码实现和中文解释均与C++类似,但C++更适合现代编程风格,支持STL容器。

六、尾言

什么是华为OD?

华为OD(Outsourcing Developer,外包开发工程师)是华为针对软件开发工程师岗位的一种招聘形式,主要包括笔试、技术面试以及综合面试等环节。尤其在笔试部分,算法题的机试至关重要。

为什么刷题很重要?

  1. 机试是进入技术面的第一关:
    华为OD机试(常被称为机考)主要考察算法和编程能力。只有通过机试,才能进入后续的技术面试环节。

  2. 技术面试需要手撕代码:
    技术一面和二面通常会涉及现场编写代码或算法题。面试官会注重考察候选人的思路清晰度、代码规范性以及解决问题的能力。因此提前刷题、多练习是通过面试的重要保障。

  3. 入职后的可信考试:
    入职华为后,还需要通过“可信考试”。可信考试分为三个等级:

    • 入门级:主要考察基础算法与编程能力。
    • 工作级:更贴近实际业务需求,可能涉及复杂的算法或与工作内容相关的场景题目。
    • 专业级:最高等级,考察深层次的算法以及优化能力,与薪资直接挂钩。

刷题策略与说明:

2024年8月14日之后,华为OD机试的题库转为 E卷,由往年题库(D卷、A卷、B卷、C卷)和全新题目组成。刷题时可以参考以下策略:

  1. 关注历年真题:

    • 题库中的旧题占比较大,建议优先刷历年的A卷、B卷、C卷、D卷题目。
    • 对于每道题目,建议深度理解其解题思路、代码实现,以及相关算法的适用场景。
  2. 适应新题目:

    • E卷中包含全新题目,需要掌握全面的算法知识和一定的灵活应对能力。
    • 建议关注新的刷题平台或交流群,获取最新题目的解析和动态。
  3. 掌握常见算法:
    华为OD考试通常涉及以下算法和数据结构:

    • 排序算法(快速排序、归并排序等)
    • 动态规划(背包问题、最长公共子序列等)
    • 贪心算法
    • 栈、队列、链表的操作
    • 图论(最短路径、最小生成树等)
    • 滑动窗口、双指针算法
  4. 保持编程规范:

    • 注重代码的可读性和注释的清晰度。
    • 熟练使用常见编程语言,如C++、Java、Python等。

如何获取资源?

  1. 官方参考:

    • 华为招聘官网或相关的招聘平台会有一些参考信息。
    • 华为OD的相关公众号可能也会发布相关的刷题资料或学习资源。
  2. 加入刷题社区:

    • 找到可信的刷题交流群,与其他备考的小伙伴交流经验。
    • 关注知名的刷题网站,如LeetCode、牛客网等,这些平台上有许多华为OD的历年真题和解析。
  3. 寻找系统性的教程:

    • 学习一本经典的算法书籍,例如《算法导论》《剑指Offer》《编程之美》等。
    • 完成系统的学习课程,例如数据结构与算法的在线课程。

积极心态与持续努力:

刷题的过程可能会比较枯燥,但它能够显著提升编程能力和算法思维。无论是为了通过华为OD的招聘考试,还是为了未来的职业发展,这些积累都会成为重要的财富。

考试注意细节

  1. 本地编写代码

    • 在本地 IDE(如 VS Code、PyCharm 等)上编写、保存和调试代码,确保逻辑正确后再复制粘贴到考试页面。这样可以减少语法错误,提高代码准确性。
  2. 调整心态,保持冷静

    • 遇到提示不足或实现不确定的问题时,不必慌张,可以采用更简单或更有把握的方法替代,确保思路清晰。
  3. 输入输出完整性

    • 注意训练和考试时都需要编写完整的输入输出代码,尤其是和题目示例保持一致。完成代码后务必及时调试,确保功能符合要求。
  4. 快捷键使用

    • 删除行可用 Ctrl+D,复制、粘贴和撤销分别为 Ctrl+CCtrl+VCtrl+Z,这些可以正常使用。
    • 避免使用 Ctrl+S,以免触发浏览器的保存功能。
  5. 浏览器要求

    • 使用最新版的 Google Chrome 浏览器完成考试,确保摄像头开启并正常工作。考试期间不要切换到其他网站,以免影响考试成绩。
  6. 交卷相关

    • 答题前,务必仔细查看题目示例,避免遗漏要求。
    • 每完成一道题后,点击【保存并调试】按钮,多次保存和调试是允许的,系统会记录得分最高的一次结果。完成所有题目后,点击【提交本题型】按钮。
    • 确保在考试结束前提交试卷,避免因未保存或调试失误而丢分。
  7. 时间和分数安排

    • 总时间:150 分钟;总分:400 分。
    • 试卷结构:2 道一星难度题(每题 100 分),1 道二星难度题(200 分)。及格分为 150 分。合理分配时间,优先完成自己擅长的题目。
  8. 考试环境准备

    • 考试前请备好草稿纸和笔。考试中尽量避免离开座位,确保监控画面正常。
    • 如需上厕所,请提前规划好时间以减少中途离开监控的可能性。
  9. 技术问题处理

    • 如果考试中遇到断电、断网、死机等技术问题,可以关闭浏览器并重新打开试卷链接继续作答。
    • 出现其他问题,请第一时间联系 HR 或监考人员进行反馈。

祝你考试顺利,取得理想成绩!

你可能感兴趣的:(算法汇集笔记总结(保姆级),华为od,c语言,javascript,python,java)