cpp
class Solution {
public:
// 思考:能否用滚动数组进行优化
int minimumMoves(vector<int>& arr) {
// 定义状态dp[i][j]为i-j的最小步数
int n = arr.size();
vector<vector<int>> dp(n, vector<int>(n, 1e9 + 7));
// 可以把这 1 次理解为一种 最小操作单位 或者
// 基准操作次数后续计算更长的子数组的最小移动次数时
// 都是基于这个基准进行递推和比较的 如果将单个元素的情况定义为 0 次
// 那么在后续的状态转移计算中
// 可能会出现逻辑上的不顺畅或者需要额外的特殊处理来区分这种情况
// 反而会使算法变得复杂和难以理解
for (int i = 0; i < n; ++i) {
dp[i][i] = 1;
}
for (int i = 0; i + 1 < n; ++i) {
if (arr[i] == arr[i + 1]) {
dp[i][i + 1] = 1;
} else {
dp[i][i + 1] = 2;
}
}
// 前面解决的是长度为2的子数组;
// 现在解决的是长度为3及其以上的子数组的最小移动次数;
for (int step = 3; step <= n; ++step) {
// i+step-1表示索引的位置是从0位置到2及其以上的位置;
for (int i = 0; i + step - 1 < n; ++i) {
int j = i + step - 1;
for (int k = i; k < j; ++k) {
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]);
}
if (arr[i] == arr[j]) {
// 如果arr[i] == arr[j] 则dp[i][j] = min(dp[i][j] dp[i +
// 1][j - 1]) 这是因为当子数组的首尾元素相同时
// 可以考虑将首尾元素之外的子数组[i + 1, j -
// 1]变为相同元素,然后首尾元素自然就相同了
// 取这种情况下的最小移动次数与当前dp[i][j]的值比较
// 取较小值更新dp[i][j]
dp[i][j] = min(dp[i][j], dp[i + 1][j - 1]);
}
}
}
return dp[0][n - 1];
}
};
// 以下是这段代码中状态表示这样定义的原因:
// 一、全面覆盖问题的子问题空间
// 定义 dp[i][j] 表示将数组 arr 中从索引 i 到索引 j
// 的子数组变为相同元素所需的最小移动次数,这种二维的状态表示能够涵盖数组的所有子数组情况。
// 从单个元素的子数组(i = j)到整个数组(i = 0,j = n - 1,其中 n
// 是数组长度),通过这种方式可以系统地考虑和处理每一种可能的子数组组合,确保不会遗漏任何一种情况,为求解整个问题提供了全面的基础。
// 二、便于状态转移和递推计算
// 在计算 dp[i][j]
// 的过程中,需要基于更短的子数组的状态来推导。例如,通过枚举分割点 k(i <= k <
// j),将 [i, j] 子数组分为 [i, k] 和 [k + 1, j] 两部分,此时 dp[i][k] 和 dp[k
// + 1][j] 就是已经计算过的更短子数组的状态。
// 这种二维状态表示使得在进行状态转移时,可以很方便地根据子数组的分割关系来建立状态之间的联系,通过取
// dp[i][k] + dp[k + 1][j] 的最小值等方式来更新
// dp[i][j],符合动态规划中通过子问题的最优解来构建更大问题最优解的思想。
// 三、与问题的求解目标紧密相关
// 最终问题是求将整个数组变为相同元素的最小移动次数,而 dp[0][n - 1]
// 正好对应了这个最终目标。通过逐步计算从单个元素到整个数组的所有子数组的状态,最终能够得到
// dp[0][n - 1] 的值,即整个问题的解。
// 这种状态表示方式直接针对问题的核心需求,将问题的求解过程转化为对一系列子数组状态的计算和更新,使得算法的设计和实现能够紧密围绕问题的本质,提高算法的针对性和有效性。
// bool isPalindrome(const std::vector<int>& arr, int start, int end) {
// while (start < end) {
// if (arr[start] != arr[end]) {
// return false;
// }
// start++;
// end--;
// }
// return true;
// }
// int minimumMoves(std::vector<int>& arr) {
// int n = arr.size();
// std::vector<int> dp(n, n);
// dp[0] = 1;
// for (int i = 1; i < n; ++i) {
// dp[i] = dp[i - 1] + 1;
// for (int j = 0; j < i; ++j) {
// if (isPalindrome(arr, j, i)) {
// dp[i] = std::min(dp[i], (j > 0 ? dp[j - 1] : 0) + 1);
// }
// }
// }
// return dp[n - 1];