青训营字节 MarsCode 技术训练营第五课——题解 | 豆包MarsCode AI 刷题

38 阅读3分钟

最小移动次数使数组相等

题目链接:

最小移动次数使数组相等 - MarsCode

题目大意:

小C有两个长度为 N 的数组 A 和 B。可以进行以下两种操作,来将数组 A 转换为数组 B

  1. 反转数组 A,即使数组 A 的元素顺序完全颠倒。
  2. 在 [1, N] 范围内选择一个整数 i,然后可以对 A[i] 添加或减去任意值。

任务是找到使数组 A 等于数组 B 所需的最小操作次数。

例如:当 N = 3A = [1, 2, 5]B = [4, 2, 1] 时,最佳操作如下:

  • 第一步反转数组 A,得到新数组 A = [5, 2, 1]
  • 第二步从位置 1 减去 1,得到新数组 A = [4, 2, 1]
    因此,答案是 2

这道题可以通过动态规划求解。

动态规划

动态规划主要适用于具有以下特征的题目:

  1. 最优子结构:问题的最优解可以由其子问题的最优解组合而成。
  2. 重叠子问题:问题的递归方案会反复求解相同子问题。
  3. 无后效性:给定某一阶段的状态,它以后的状态不受这个阶段以前的状态的影响。

这个问题具有重叠子问题和最优子结构:

  1. 重叠子问题

在这个问题中,我们需要将数组 A 转换为数组 B,并且可以通过反转数组 A 或对 A 中的元素进行加减操作来实现。我们可以将问题分解为以下子问题:

  • 将 A 的前 i 个元素转换为 B 的前 i 个元素所需的最小操作次数。这些子问题会在计算过程中多次重复出现。
  1. 最优子结构
  • 如果我们知道将 A 的前 i-1 个元素转换为 B 的前 i-1 个元素所需的最小操作次数,那么我们可以通过这个信息来计算将 A 的前 i 个元素转换为 B 的前 i 个元素所需的最小操作次数。

解题思路

状态定义

  • 设 dp[i][0] 表示将 A 的前 i 个元素转换为 B 的前 i 个元素所需的最小操作次数,且不反转数组 A
  • 设 dp[i][1] 表示将 A 的前 i 个元素转换为 B 的前 i 个元素所需的最小操作次数,且反转数组 A

状态转移方程

  • 如果 A[i-1] == B[i-1],则 dp[i][0] = dp[i-1][0]
  • 如果 A[i-1] != B[i-1],则 dp[i][0] = dp[i-1][0] + 1
  • 如果 A[N-i] == B[i-1](反转后的 A 的第 i 个元素),则 dp[i][1] = dp[i-1][1]
  • 如果 A[N-i] != B[i-1],则 dp[i][1] = dp[i-1][1] + 1

初始状态

  • dp[0][0] = 0(空数组不需要操作)。
  • dp[0][1] = 1(空数组反转一次)。

最终结果

  • 最终结果为 min(dp[N][0], dp[N][1])

如果不使用动态规划,通过直接比较和操作数组的话,每次操作都需要判断当前是否需要进行数组反转,计算数组A和数组 B的差异,并计算调整次数,相比较而言,动态规划或者递归求解更适合这个问题,代码量能得到显著减少。

具体实现

public static int solution(int N, int[] A, int[] B) {
    // write code here
    int[][] dp = new int[N + 1][2];

    dp[0][0] = 0; // 不反转
    dp[0][1] = 1; // 反转

    for (int i = 1; i <= N; i++) {
        // 不反转的情况
        if (A[i - 1] == B[i - 1]) {
            dp[i][0] = dp[i - 1][0];
        } else {
            dp[i][0] = dp[i - 1][0] + 1;
        }

        // 反转的情况
        if (A[N - i] == B[i - 1]) {
            dp[i][1] = dp[i - 1][1];
        } else {
            dp[i][1] = dp[i - 1][1] + 1;
        }
    }

    // 返回最小操作次数
    return Math.min(dp[N][0], dp[N][1]);
}