动态规划常见算法题(3)

动态规划

子序列或连续序列问题

最长递增子序列

力扣题目链接

  • 题目

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

  • dp定义:dp[i] 代表 以nums[i]为结尾的最长递增子序列的长度
  • 初始化:dp数组都为1
  • 遍历:
for (i = 1; i < n; ++i) {
	for (j = 0; j < i; ++j) {
		if (nums[i] > nums[j]) {
			dp[i] = max(dp[i], dp[j] + 1);
		}
		res = max(res, dp[i]);
	}
}

最长连续递增序列

力扣题目链接

  • 题目:

给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。

连续递增的子序列 可以由两个下标 l 和 r(l < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], …, nums[r - 1], nums[r]] 就是连续递增子序列。

  • dp定义:dp[i] 代表 以nums[i]为结尾的最长连续递增序列的长度
  • 初始化:dp数组都为1
  • 遍历:
for (i = 0; i < n - 1; ++i) {
	if (nums[i + 1] > nums[i]) 
		dp[i + 1] = dp[i] + 1;
}

最长重复子数组

力扣题目链接

  • 题目:注意,这里是子数组,不是子序列

给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。

示例:

输入:

  • A: [1,2,3,2,1]
  • B: [3,2,1,4,7]
  • 输出:3
  • 解释:长度最长的公共子数组是 [3, 2, 1] 。
  • dp定义:dp[ i ] [ j ] 代表 以 A[i-1]和B[j-1]为结尾的最长的子数组的长度
  • 初始化:dp[0] [j] = dp[i] [0] = 0
  • 遍历
dp [lena+1][lenb+1];
for (i = 1; i <= lena; ++i) {
    for (j = 1; j <= lenb; ++j) {
        if (A[i - 1] == B[j - 1]) {
            dp[i][j] = dp[i - 1][j - 1] + 1;
        } else {
            // /*这里是子序列的写法*/dp[i][j] = max(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]);
            dp[i][j] = 0;
        }
    }
}

最长公共子序列

力扣题目链接(opens new window)

  • 题目:

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

示例 1:

  • 输入:text1 = “abcde”, text2 = “ace”
  • 输出:3
  • 解释:最长公共子序列是 “ace”,它的长度为 3。
  • dp定义:dp[i] [j] 代表text1[0 ~ i-1] 和 text2[0 ~ j-1]两个子数组最长公共子序列的长度
  • 初始化:dp[0] [j] = dp[i] [0] = 0
  • 遍历
dp[n][m]; n:text1.size m:text2.size
for (i = 1; i <= n; ++i) {
    for (j = 1; j <= m; ++j) {
        if (text1[i-1] == text2[j-1]) {
            dp[i][j] = dp[i-1][j-1] + 1;	// 二维
            // dp[j] = dp[j-1] + 1;	// 一维
        } else {
            dp[i][j] = max({dp[i - 1][j], dp[i][j-1]}); // 二维
            // dp[j] = max({dp[j], dp[j-1]}); // 一维
        }
    }
}

不相交的线

力扣题目链接

  • 题目:这道题主要是理解,找规律,注意不能相交这个点。连线要在两个相同的数字,不能相交:要求按照相对顺序来。那不就是找按相对顺序的相同数字的对数,那不就是求最长子序列。

我们在两条独立的水平线上按给定的顺序写下 A 和 B 中的整数。

现在,我们可以绘制一些连接两个数字 A[i] 和 B[j] 的直线,只要 A[i] == B[j],且我们绘制的直线不与任何其他连线(非水平线)相交。

以这种方法绘制线条,并返回我们可以绘制的最大连线数。

  • dp定义:dp[i] [j] 为 A[0 ~ i-1] 和 B[0 ~ j-1] 最长相同子序列的长度
  • 初始化:dp[0] [j] = dp[i] [0] = 0
  • 遍历
for (i = 1; i <= n; ++i) {
    for (j = 1; j <= m; ++j) {
        if (A[i - 1] == B[i - 1]) {
            dp[i][j] = dp[i - 1][j - 1] + 1;
        } else {
            dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
        }
    }
}
return dp[n][m]

判断子序列

力扣题目链接(opens new window)

  • 题目:其实还是求公共子序列。求s和t的最长子序列,看是不是为s本身
  • 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

示例 1:

  • 输入:s = “abc”, t = “ahbgdc”
  • 输出:true

示例 2:

  • 输入:s = “axc”, t = “ahbgdc”
  • 输出:false

提示:

  • 0 <= s.length <= 100
  • 0 <= t.length <= 10^4

两个字符串都只由小写字符组成。

不同的子序列

力扣题目链接(opens new window)

  • 题目:

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

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

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

  • dp定义:dp[i] [j] 代表 s[0 ~ i-1] 的子序列中出现 t[0 ~ j-1]的个数
  • 初始化:dp[0] [j] = dp[i] [0] = 0, dp[0] [0] = 1
  • 遍历:
n = s.size();
m = t.size();
for (i = 1; i <= n; ++i) {
    for (j = 1; j <= m; ++j) {
        if (s[i-1] == t[j-1]) {
			dp[i][j] = dp[i-1][j] /*s[i-1]不参与匹配*/ + dp[i-1][j-1]/*s[i-1]参与匹配*/;
        } else {
            dp[i][j] = dp[i-1][j]	/*s[i-1]不参与匹配*/
        }
    }
}

return dp[n][m]

编辑距离

两个字符串的删除操作

力扣题目链接(opens new window)

  • 题目:
  • 给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。

示例:

  • 输入: “sea”, “eat”
  • 输出: 2
  • 解释: 第一步将"sea"变为"ea",第二步将"eat"变为"ea"
  • dp定义:dp[i] [j] 代表 word1[0 ~ i-1] 和 word2[0 ~ j-1] 相同需要的最小步数
  • 初始化:dp[0] [j] = j ; dp[i] [0] = i ; dp[0] [0] = 0
  • 遍历:
for (i = 1; i <= n; ++i) {
	for (j = 1; j <= m; ++j) {
        if (word1[i - 1] == word2[j - 1]) {
            dp[i][j] = dp[i-1][j-1];
        } else {
            dp[i][j] = min({dp[i-1][j]+1, dp[i][j-1]+1, dp[i-1][j-1]+2});
        }
    }
}

return dp[n][m]

编辑距离

力扣题目链接(opens new window)

  • 题目:

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

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

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符
  • 示例 1:
  • 输入:word1 = “horse”, word2 = “ros”
  • 输出:3
  • 解释: horse -> rorse (将 ‘h’ 替换为 ‘r’) rorse -> rose (删除 ‘r’) rose -> ros (删除 ‘e’)
  • 示例 2:
  • 输入:word1 = “intention”, word2 = “execution”
  • 输出:5
  • 解释: intention -> inention (删除 ‘t’) inention -> enention (将 ‘i’ 替换为 ‘e’) enention -> exention (将 ‘n’ 替换为 ‘x’) exention -> exection (将 ‘n’ 替换为 ‘c’) exection -> execution (插入 ‘u’)
  • dp定义:dp[i] [j] 代表 word1[0 ~ i-1] 和 word2[0 ~ j-1] 相同需要的最小编辑次数步数
  • 初始化:dp[0] [j] = j ; dp[i] [0] = i ; dp[0] [0] = 0
  • 遍历:
for (i = 1; i <= n; ++i) {
	for (j = 1; j <= m; ++j) {
        if (word1[i - 1] == word2[j - 1]) {
            dp[i][j] = dp[i-1][j-1];
        } else {
            dp[i][j] = min(
                {
      dp[i-1][j]+1/*删除或修改word1[i-1]*/, 		
      dp[i][j-1]+1/*删除或修改word2[j-1]*/, 
/*没必要*/ dp[i-1][j-1]+2/*删除word1[i-1]和word2[i-2]*/,
                });
        }
    }
}

return dp[n][m]

回文

回文子串

力扣题目链接(opens new window)

  • 题目:这道题dp不好作,用双指针遍历更简单

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

示例 1:

  • 输入:“abc”
  • 输出:3
  • 解释:三个回文子串: “a”, “b”, “c”
class Solution {
public:
    int helper(const string& s, int left, int right) {
        int ret = 0;
        while (left >= 0 && right < s.size()) {
            if (s[left] == s[right]) {
                --left;
                ++right;
                ++ret;
            } else {
                break;
            }
        }
        return ret;
    }
    int countSubstrings(string s) {
        int ret = 0;
		for (int i = 0; i < s.size(); ++i) {
            ret += helper(s, i, i + 1);
            ret += helper(s, i, i);
        }
        return ret;
    }
};

最长回文子序列

力扣题目链接(opens new window)

  • 题目: 注意是子序列,不是连续的

给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。

示例 1: 输入: “bbbab” 输出: 4 一个可能的最长回文子序列为 “bbbb”。

示例 2: 输入:“cbbd” 输出: 2 一个可能的最长回文子序列为 “bb”。

  • dp定义:dp[i] [j] 代表 以s[i ~ j]的最长回文子序列的最长长度
  • 初始化:dp[i] [i] = 1; else = 0
  • 遍历:这里注意遍历顺序。(因为上一行(i)是依赖下一行(i+1),所以需要先计算下一行)。并且 i > j 时,dp[i] [j] 无意义,所以j是从i+1开始
class Solution {
public:
    int longestPalindromeSubseq(string s) {
        vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
        for (int i = 0; i < s.size(); i++) dp[i][i] = 1;
        for (int i = s.size() - 1; i >= 0; i--) {
            for (int j = i + 1; j < s.size(); j++) {
                if (s[i] == s[j]) {
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                } else {
                    dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[0][s.size() - 1];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值