【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];
相关推荐
We་ct8 分钟前
LeetCode 73. 矩阵置零:原地算法实现与优化解析
前端·算法·leetcode·矩阵·typescript
天赐学c语言8 分钟前
2.1 - 反转字符串中的单词 && 每个进程的内存里包含什么
c++·算法·leecode
程序员泠零澪回家种桔子11 分钟前
OpenManus开源自主规划智能体解析
人工智能·后端·算法
请注意这个女生叫小美13 分钟前
C语言 实例20 25
c语言·开发语言·算法
好学且牛逼的马14 分钟前
【Hot100|22-LeetCode 206. 反转链表 - 完整解法详解】
算法·leetcode·矩阵
hans汉斯17 分钟前
国产生成式人工智能解决物理问题能力研究——以“智谱AI”、“讯飞星火认知大模型”、“天工”、“360智脑”、“文心一言”为例
大数据·人工智能·算法·aigc·文心一言·汉斯出版社·天工
v_for_van20 分钟前
力扣刷题记录3(无算法背景,纯C语言)
c语言·算法·leetcode
ValhallaCoder23 分钟前
hot100-矩阵
数据结构·python·算法·矩阵
散峰而望24 分钟前
【基础算法】穷举的艺术:在可能性森林中寻找答案
开发语言·数据结构·c++·算法·随机森林·github·动态规划
心.c26 分钟前
Vue3+Node.js实现文件上传分片上传和断点续传【详细教程】
前端·javascript·vue.js·算法·node.js·哈希算法