【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];
相关推荐
相思半8 分钟前
Web前端开发入门学习笔记之CSS 57-58--新手超级友好版- 盒子模型以及边框线应用篇
前端·css·笔记·学习·职场和发展
yanglee016 分钟前
L4-Prompt-Delta
人工智能·算法·语言模型·prompt
廖显东-ShirDon 讲编程1 小时前
《零基础Go语言算法实战》【题目 2-1】使用一个函数比较两个整数
算法·程序员·go语言·web编程·go web
刘争Stanley2 小时前
训练一只AI:深度学习在自然语言处理中的应用
人工智能·深度学习·算法·链表·自然语言处理·贪心算法·排序算法
竹下为生2 小时前
LeetCode---147周赛
算法·leetcode·职场和发展
多多*2 小时前
后端技术选型 sa-token校验学习 下 结合项目学习 后端鉴权
java·开发语言·前端·学习·算法·bootstrap·intellij-idea
夕泠爱吃糖2 小时前
选择排序&冒泡排序
数据结构·c++·算法
ExRoc2 小时前
蓝桥杯真题 - 最大开支 - 题解
c++·算法·蓝桥杯
廖显东-ShirDon 讲编程4 小时前
《零基础Go语言算法实战》【题目 1-16】字符串的遍历与比较
算法·程序员·go语言·web编程·go web