最小移动次数使数组相等
题目链接:
题目大意:
小C有两个长度为 N 的数组 A 和 B。可以进行以下两种操作,来将数组 A 转换为数组 B:
- 反转数组
A,即使数组A的元素顺序完全颠倒。 - 在
[1, N]范围内选择一个整数i,然后可以对A[i]添加或减去任意值。
任务是找到使数组 A 等于数组 B 所需的最小操作次数。
例如:当 N = 3,A = [1, 2, 5],B = [4, 2, 1] 时,最佳操作如下:
- 第一步反转数组
A,得到新数组A = [5, 2, 1]。 - 第二步从位置
1减去1,得到新数组A = [4, 2, 1]。
因此,答案是2。
这道题可以通过动态规划求解。
动态规划
动态规划主要适用于具有以下特征的题目:
- 最优子结构:问题的最优解可以由其子问题的最优解组合而成。
- 重叠子问题:问题的递归方案会反复求解相同子问题。
- 无后效性:给定某一阶段的状态,它以后的状态不受这个阶段以前的状态的影响。
这个问题具有重叠子问题和最优子结构:
- 重叠子问题
在这个问题中,我们需要将数组 A 转换为数组 B,并且可以通过反转数组 A 或对 A 中的元素进行加减操作来实现。我们可以将问题分解为以下子问题:
- 将
A的前i个元素转换为B的前i个元素所需的最小操作次数。这些子问题会在计算过程中多次重复出现。
- 最优子结构
- 如果我们知道将
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]);
}