【LeetCode】:删除回文子数组【困难】


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];
相关推荐
君义_noip24 分钟前
信息学奥赛一本通 1524:旅游航道
c++·算法·图论·信息学奥赛
YYJ333_33330 分钟前
蓝桥杯训练士兵
职场和发展·蓝桥杯
烁34733 分钟前
每日一题(小白)动态规划篇5
算法·动态规划
独好紫罗兰34 分钟前
洛谷题单2-P5717 【深基3.习8】三角形分类-python-流程图重构
开发语言·python·算法
滴答滴答嗒嗒滴40 分钟前
Python小练习系列 Vol.8:组合总和(回溯 + 剪枝 + 去重)
python·算法·剪枝
lidashent1 小时前
数据结构和算法——汉诺塔问题
数据结构·算法
Tom Boom1 小时前
【3. 软件工程】3.1 软件过程模型
职场和发展·系统架构·软件工程
小王努力学编程1 小时前
动态规划学习——背包问题
开发语言·c++·学习·算法·动态规划
f狐0狸x3 小时前
【蓝桥杯每日一题】4.1
c语言·c++·算法·蓝桥杯
ん贤3 小时前
2023第十四届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组(真题&题解)(C++/Java题解)
java·c语言·数据结构·c++·算法·蓝桥杯