【动态规划篇】最少分割回文 && 编辑距离 && 不同的子序列

简介: 【动态规划篇】最少分割回文 && 编辑距离 && 不同的子序列

👉最少分割回文👈


给定一个字符串 s,请将 s 分割成一些子串,使每个子串都是回文串。返回符合要求的最少分割次数 。

844ae2dc71ca4669b44a318a7ef1a52f.png


思路:


f2e8810a18bc43619d9632ed2654006e.png


class Solution 
{
private:
    bool isPalindrome(const string& s, int begin, int end)
    {
        while(begin < end)
        {
            if(s[begin] != s[end])
            {
                return false;
            }
            ++begin;
            --end;
        }
        return true;
    }
public:
    int minCut(string s) 
    {
        int len = s.size();
        vector<int> minCut(len + 1);
        // 初始状态:F(i) = i - 1
        for(int i = 1; i <= len; ++i)
        {
            minCut[i] = i - 1;
        }
        for(int i = 2; i <= len; ++i)
        {
            // [1, i]整体是回文串
            if(isPalindrome(s, 0, i - 1))
            {
                minCut[i] = 0;
                continue;
            }
            // j < i && [j + 1, i]是否为回文串
            for(int j = 1; j < i; ++j)
            {
                if(isPalindrome(s, j, i - 1))
                {
                    minCut[i] = min(minCut[i], minCut[j] + 1);
                }
            }
        }
        return minCut[len];
    }
};

8bdceb5067ad48569d8e30005f8c6ccf.png


class Solution 
{
private:
    bool isPalindrome(const string& s, int begin, int end)
    {
        while(begin < end)
        {
            if(s[begin] != s[end])
            {
                return false;
            }
            ++begin;
            --end;
        }
        return true;
    }
public:
    int minCut(string s) 
    {
        int len = s.size();
        vector<int> minCut(len + 1);
        // 初始状态:F(i) = i - 1
        for(int i = 0; i <= len; ++i)
        {
            minCut[i] = i - 1;
        }
        // F(0) = -1,若整个字符串为回文串,也能得出最小分割次数为0
        for(int i = 2; i <= len; ++i)
        {
            // j < i && [j + 1, i]是否为回文串
            for(int j = 0; j < i; ++j)
            {
                if(isPalindrome(s, j, i - 1))
                {
                    minCut[i] = min(minCut[i], minCut[j] + 1);
                }
            }
        }
        return minCut[len];
    }
};


94cb30c9df09404897f49f586d7e972d.png


上面的解法是 O(N ^ 3) 的算法,两层 for 循环再加一个判断回文串 O(N) 的算法,所以这个解法的时间复杂度为 O(N ^ 3)。我们可以先将回文串的结果保存下来,要用时可以直接用,这样就可以将时间复杂度将到 O(N ^ 2) 了。


判断回文串也是需要用到动态规划的。


4b4c7f303f2a44e48a5c327eada62e5b.png


class Solution 
{
private:
    vector<vector<bool>> getMat(const string& s)
    {
        int n = s.size();
        vector<vector<bool>> Mat(n, vector<bool>(n, false));
        // 从矩阵的右下角开始更新
        for(int i = n - 1; i >= 0; --i)
        {
            for(int j = i; j < n; ++j)
            {
                if(j == i)  // 初始状态
                    Mat[i][j] = true;
                else if(j == i + 1)
                    Mat[i][j] = s[i] == s[j];
                else    
                    Mat[i][j] = ((s[i] == s[j]) && (Mat[i + 1][j - 1]));
            }
        }
        return Mat;
    }
public:
    int minCut(string s) 
    {
        int len = s.size();
        vector<vector<bool>> Mat = getMat(s);
        vector<int> minCut(len + 1);
        // 初始状态:F(i) = i - 1
        for(int i = 0; i <= len; ++i)
        {
            minCut[i] = i - 1;
        }
        // F(0) = -1,若整个字符串为回文串,也能得出最小分割次数为0
        for(int i = 2; i <= len; ++i)
        {
            // j < i && [j + 1, i]是否为回文串
            for(int j = 0; j < i; ++j)
            {
                if(Mat[j][i - 1])
                {
                    minCut[i] = min(minCut[i], minCut[j] + 1);
                }
            }
        }
        return minCut[len];
    }
};

e12f7a550f8e4b5bbff2dc660c89aecf.png


该解法的时间复杂度为 O(N ^ 2),空间复杂度为 O(N ^ 2),典型的以空间换时间的做法。


👉编辑距离👈


给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符

删除一个字符

替换一个字符

c0943433caef48aebc97502b3270cca7.png


思路:


c73aeea5e5dd44afae0c052d95933121.png


class Solution 
{
public:
    int minDistance(string word1, string word2) 
    {
        int row = word1.size();
        int col = word2.size();
        vector<vector<int>> minEdit(row + 1, vector<int>(col + 1));
        // 初始状态:F(i, 0) = 0 和 F(0, j) = j
        for(int i = 0; i <= row; ++i)   minEdit[i][0] = i;
        for(int j = 1; j <= col; ++j)   minEdit[0][j] = j;
        for(int i = 1; i <= row; ++i)
        {
            for(int j = 1; j <= col; ++j)
            {
                // 插入和删除
                minEdit[i][j] = min(minEdit[i][j - 1], minEdit[i - 1][j]) + 1;
                // 替换 word1[i - 1]是否等于word2[j - 1]
                if(word1[i - 1] == word2[j - 1])
                {
                    minEdit[i][j] = min(minEdit[i][j], minEdit[i - 1][j - 1]);
                }
                else
                {
                    minEdit[i][j] = min(minEdit[i][j], minEdit[i - 1][j - 1] + 1);
                }
            }
        }
        return minEdit[row][col];
    }
};

21185135bb76474eaec54a7f15ade7e5.png


👉不同的子序列👈


给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)


题目数据保证答案符合 32 位带符号整数范围。

ae4cfdbe21ca4158ae301e7cc3ab951b.png

思路:


5bd75c6a33c3422da2ebf5edcc0e43a2.png


class Solution 
{
public:
    int numDistinct(string s, string t) 
    {
        int row = s.size();
        int col = t.size();
        vector<vector<unsigned int>> dp(row + 1, vector<unsigned int>(col + 1, 0));
        for(int i = 0; i <= row; ++i)   dp[i][0] = 1;   // 初始状态
        for(int i = 1; i <= row; ++i)
        {
            for(int j = 1; j <= col; ++j)
            {
                if(s[i - 1] == t[j - 1])
                    dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                else
                    dp[i][j] = dp[i - 1][j];
            }
        }
        return dp[row][col];
    }
};



44395b67197743a28d2863b1b72c6ef8.png


空间复杂度优化


由于当前的dp[i][j]仅取决于dp[i - 1][j - 1]dp[i - 1][j],所以我们不需要用二维数组将所有状态的解都保留下来,可以用一维数组将上一次的解保留下来就行了。但需要注意的是,需要从后向前更新,不然无法拿到上一次的解。


class Solution 
{
public:
    int numDistinct(string s, string t) 
    {
        int row = s.size();
        int col = t.size();
        // 初始状态
        vector<unsigned int> dp(col + 1, 0);
        dp[0] = 1;
        for(int i = 1; i <= row; ++i)
        {
            for(int j = col; j >= 1; --j)
            {
                if(s[i - 1] == t[j - 1])
                    dp[j] = dp[j - 1] + dp[j];  // dp += dp[i - 1]
                // s[i - 1] != t[j - 1]时,不需要更新dp[j]
            }
        }
        return dp[col];
    }
};


711ba06ae4bc49429ba791a3ab982a0d.png


👉总结👈


动态规划的难点:

状态如何定义:从问题中抽象出状态,每一个状态都对应着一个子问题。状态的定义可能不止一种方式,那如何定义状态的合理性呢?某一个状态的阶或者多个状态的解能否推出最终问题的解,也就是状态之间能够形成递推关系(状态转移方程)。

一维状态 VS 二维状态:首先尝试一维状态,如果一维状态的合理性不满足时,再去尝试二维状态。

常见问题的状态:字符串:状态一般对应子串,状态一般每次增加一个新的字符。二维状态有时候能够优化成一维状态。


那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️















相关文章
|
19天前
|
机器学习/深度学习 算法 JavaScript
【动态规划】【回文】【字符串】1278分割回文串 III
【动态规划】【回文】【字符串】1278分割回文串 III
|
19天前
|
人工智能 算法 测试技术
【动态规划】【字符串】【C++算法】940. 不同的子序列 II
【动态规划】【字符串】【C++算法】940. 不同的子序列 II
|
19天前
|
算法 C++
【动态规划】【子序列除重】【C++算法】1987不同的好子序列数目
【动态规划】【子序列除重】【C++算法】1987不同的好子序列数目
|
19天前
leetcode-72:编辑距离
leetcode-72:编辑距离
26 0
|
19天前
|
Java
leetcode-131:分割回文串
leetcode-131:分割回文串
22 1
|
19天前
|
算法 程序员 索引
【算法训练-动态规划 三】【双序列DP问题】最长重复子数组、最长公共子串、最长公共子序列、编辑距离
【算法训练-动态规划 三】【双序列DP问题】最长重复子数组、最长公共子串、最长公共子序列、编辑距离
71 0
|
10月前
|
机器学习/深度学习 存储 算法
算法训练Day29|* 491.递增子序列* 46.全排列* 47.全排列 II
算法训练Day29|* 491.递增子序列* 46.全排列* 47.全排列 II
|
11月前
1276:【例9.20】编辑距离
1276:【例9.20】编辑距离
|
12月前
|
存储
Leecoe 72. 编辑距离
Leecoe 72. 编辑距离
25 0
|
12月前
|
算法 Java C++
最长上升序列模型 acwing 1016.最大上升子序列和
最长上升序列模型 acwing 1016.最大上升子序列和
35 0