最长递增子序列
-
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序
-
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
-
注意这里不是要dp[i] 与 dp[j] + 1进行比较,而是我们要取dp[j] + 1的最大值。
-
双指针 i遍历s j遍历0-i
-
dp[i]表示i之前包括i的以nums[i]结尾最长上升子序列的长度
func lengthOfLIS(nums []int) int {
//双指针+动态规划
//dp[i]表示i下标的位置,最长递增子串长度
//用j去遍历0-i-1区间,如果满足 nums[i]>nums[j] 那么dp更新
dp := make([]int,len(nums))
for i:=0;i<len(nums);i++{
dp[i] = 1
}
res := 0
for i:=0;i<len(nums);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])
}
return res
}
最长连续递增序列
-
连续
-
dp[i]:以下标i为结尾的数组的连续递增的子序列长度为dp[i] 。
-
因为本题要求连续递增子序列,所以就必要比较nums[i + 1]与nums[i],而不用去比较nums[j]与nums[i]
func findLengthOfLCIS(nums []int) int {
//dp[i]:以下标i为结尾的数组的连续递增的子序列长度为dp[i]。
dp := make([]int,len(nums))
for i:=0;i<len(nums);i++{
dp[i] = 1
}
res := 0
for i:=0;i<len(nums)-1;i++{
if nums[i]<nums[i+1]{
dp[i+1] = dp[i]+1
}
}
for i:=0;i<len(nums);i++{
res = max(res,dp[i])
}
return res
}
最长重复子数组
-
子数组,其实就是连续子序列
-
dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。 “以下标i - 1为结尾的A” 标明一定是 以A[i-1]为结尾的字符串
-
即当A[i - 1] 和B[j - 1]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1;
-
dp[i][0] 和dp[0][j]初始化为0。
func findLength(nums1 []int, nums2 []int) int {
//dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。
//地推公式 即当A[i - 1] 和B[j - 1]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1;
m,n := len(nums1),len(nums2)
res := 0
dp := make([][]int,m+1)
for i:=0;i<=m;i++{
dp[i] = make([]int,n+1)
}
for i:=1;i<=m;i++{
for j:=1;j<=n;j++{
if nums1[i-1]==nums2[j-1]{
dp[i][j] = dp[i-1][j-1]+1
}
res = max(res,dp[i][j])
}
}
return res
}
最长公共子序列
-
区别在于这里不要求是连续的了
-
dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]
-
text1[i - 1] 与 text2[j - 1]相同,text1[i - 1] 与 text2[j - 1]不相同
func longestCommonSubsequence(text1 string, text2 string) int {
dp:=make([][]int,len(text1)+1)
for i:=0;i<len(dp);i++{
dp[i] = make([]int,len(text2)+1)
}
res := 0
for i:=1;i<=len(text1);i++{
for j:=1;j<=len(text2);j++{
if text1[i-1] == text2[j-1]{
dp[i][j] = dp[i-1][j-1] + 1
}else{
dp[i][j] = max(dp[i-1][j],dp[i][j-1])
}
res = max(res,dp[i][j])
}
}
return res
}
不相交的线
- 直线不能相交,这就是说明在字符串A中 找到一个与字符串B相同的子序列,且这个子序列不能改变相对顺序,只要相对顺序不改变,链接相同数字的直线就不会相交。
func maxUncrossedLines(nums1 []int, nums2 []int) int {
//本题说是求绘制的最大连线数,其实就是求两个字符串的最长公共子序列的长度!
dp := make([][]int,len(nums1)+1)
for i:=0;i<=len(nums1);i++{
dp[i] = make([]int,len(nums2)+1)
}
res := 0
for i:=1;i<=len(nums1);i++{
for j:=1;j<=len(nums2);j++{
if nums1[i-1]==nums2[j-1]{
dp[i][j] = dp[i-1][j-1] + 1
}else{
dp[i][j] = max(dp[i][j-1],dp[i-1][j])
}
res = max(res,dp[i][j])
}
}
return res
}
最大子序和
- 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
贪心
func maxSubArray(nums []int) int {
maxSum := nums[0]
for i := 1; i < len(nums); i++ {
if nums[i] + nums[i-1] > nums[i] {
nums[i] += nums[i-1]
}
if nums[i] > maxSum {
maxSum = nums[i]
}
}
return maxSum
}
-
dp[i]:包括下标i之前的最大连续子序列和为dp[i] 。
-
dp[i]只有两个方向可以推出来:
-
dp[i - 1] + nums[i],即:nums[i]加入当前连续子序列和
-
nums[i],即:从头开始计算当前连续子序列和
func maxSubArray(nums []int) int {
if len(nums) == 1{
return nums[0]
}
//dp[i]表示子数组的和
dp := make([]int,len(nums))
dp[0] = nums[0]
res := nums[0]
for i:=1;i<len(dp);i++{
dp[i] = max(dp[i-1]+nums[i],nums[i])
res =max(res,dp[i])
}
return res
}
判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
-
dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j] 。
-
if (s[i - 1] == t[j - 1]),那么dp[i][j] = dp[i - 1][j - 1] + 1;
-
if (s[i - 1] != t[j - 1]),此时相当于t要删除元素,t如果把当前元素t[j - 1]删除,那么dp[i][j] 的数值就是 看s[i - 1]与 t[j - 2]的比较结果了,即:dp[i][j] = dp[i][j - 1];
-
就是说不相等的时候要去取t的上一个状态的值
-
最后 dp == len(s)说明相同子序列的长度等于s的长度,说明他就是t的子序列
func isSubsequence(s string, t string) bool {
//dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]。
dp := make([][]int,len(s)+1)
for i:=0;i<=len(s);i++ {
dp[i] = make([]int,len(t)+1)
}
for i:=1;i<len(dp);i++{
for j:=1;j<len(dp[i]);j++{
if s[i-1] == t[j-1]{
dp[i][j] = dp[i-1][j-1]+1
}else{
dp[i][j] = dp[i][j-1]
}
}
}
return dp[len(s)][len(t)] == len(s)
}
不同子序列
给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。 s大 t小
-
这道题目如果不是子序列,而是要求连续序列的,那就可以考虑用KMP。
-
本题相当于只有删除操作,
-
- s[i - 1] 与 t[j - 1]相等
-
s[i - 1] 与 t[j - 1] 不相等
当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成。
-
一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。
-
一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。
-
就是说你相等了 你可以选择的机会就多了,可以选择相等时候的状态,把相等的删了,也可把s的最新的删了, dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
-
当s[i - 1] 与 t[j - 1]不相等时,dp[i][j]只有一部分组成,不用s[i - 1]来匹配,即:dp[i - 1][j]。。,。 所以递推公式为:dp[i][j] = dp[i - 1][j];
两个字符串的删除操作
-
给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。
-
和上一题相比,其实就是两个字符串都可以删除,整体思路不变
-
dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。
-
当word1[i - 1] 与 word2[j - 1]相同的时候,dp[i][j] = dp[i - 1][j - 1];
-
当word1[i - 1] 与 word2[j - 1]不相同的时候,有三种情况:
-
情况一:删word1[i - 1],最少操作次数为dp[i - 1][j] + 1
-
情况二:删word2[j - 1],最少操作次数为dp[i][j - 1] + 1
-
情况三:同时删word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2
-
-
dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1});
func minDistance1(word1 string, word2 string) int {
//dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。
dp := make([][]int,len(word1)+1)
for i:=0;i<len(dp);i++{
dp[i] = make([]int,len(word2)+1)
}
//初始化
for i:=0;i<len(dp);i++{
dp[i][0] = i
}
for j:=0;j<len(dp[0]);j++{
dp[0][j] = j
}
//当word1[i - 1] 与 word2[j - 1]相同的时候,dp[i][j] = dp[i - 1][j - 1];
//当word1[i - 1] 与 word2[j - 1]不相同的时候,有三种情况:
//情况一:删word1[i - 1],最少操作次数为dp[i - 1][j] + 1
//情况二:删word2[j - 1],最少操作次数为dp[i][j - 1] + 1
//情况三:同时删word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2
//那最后当然是取最小值,所以当word1[i - 1] 与 word2[j - 1]不相同的时候,递推公式:dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1});
for i:=1;i<len(dp);i++{
for j:=1;j<len(dp[0]);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,min(dp[i][j-1]+1,dp[i-1][j-1]+2))
}
}
}
return dp[len(word1)][len(word2)]
}
编辑距离
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数
-
插入一个字符
-
删除一个字符
-
替换一个字符
-
dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j] 。
if (word1[i - 1] == word2[j - 1])
不操作
if (word1[i - 1] != word2[j - 1])
增
删
换
- dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
- 替换 dp[i - 1][j - 1] + 1 理解成i-2 和 j-1都相等了,下面做一次替换就行了
func minDistance(word1,word2 string)int{
m,n:=len(word1),len(word2)
dp := make([][]int,len(word1)+1)
for i:=0;i<len(dp);i++{
dp[i] = make([]int,len(word2)+1)
}
for i:=0;i<len(dp);i++{
dp[i][0] = i
}
for i:=0;i<len(dp[0]);i++{
dp[0][i] = i
}
for i:=1;i<len(dp);i++{
for j:=1;j<len(dp[i]);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]+1,dp[i-1][j]+1,dp[i][j-1]+1)
}
}
}
return dp[m][n]
}
回文子串---连续
-
布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false。
-
当s[i]与s[j]不相等,那没啥好说的了,dp[i][j]一定是false。
-
当s[i]与s[j]相等时,这就复杂一些了,有如下三种情况
- 情况一:下标i 与 j相同,同一个字符例如a,当然是回文子串
- 情况二:下标i 与 j相差为1,例如aa,也是回文子串
- 情况三:下标:i 与 j相差大于1的时候,例如cabac,此时s[i]与s[j]已经相同了,我们看i到j区间是不是回文子串就看aba是不是回文就可以了,那么aba的区间就是 i+1 与 j-1区间,这个区间是不是回文就看dp[i + 1][j - 1]是否为true。
-
递归顺序看dp公式,大for反序小for正序
func countSubstrings(s string)int{
ans := 0
dp := make([][]bool,len(s))
for i:=0;i<len(dp);i++{
dp[i] = make([]bool,len(s))
}
for i:=len(s)-1;i>=0;i--{
for j:=i;j<len(s);j++{
if s[i] != s[j]{
dp[i][j] = false
}else{
if j-i<=2{
dp[i][j] = true
}else if j-i > 2 && dp[i+1][j-1]{
dp[i][j] = true
}
}
if dp[i][j]{
ans++
}
}
}
return ans
}
最长回文序列---可以不连续
给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。
示例 1: 输入: "bbbab" 输出: 4 一个可能的最长回文子序列为 "bbbb"。
- dp[i][j]:字符串s在[i, j]范围内最长的回文子序列的长度为dp[i][j] 。
- 如果s[i]与s[j]相同,那么dp[i][j] = dp[i + 1][j - 1] + 2;
- 如果s[i]与s[j]不相同,说明s[i]和s[j]的同时加入 并不能增加[i,j]区间回文子串的长度,那么分别加入s[i]、s[j]看看哪一个可以组成最长的回文子序列。
- dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
func countSubstrings(s string)int{
ans := 0
dp := make([][]bool,len(s))
for i:=0;i<len(dp);i++{
dp[i] = make([]bool,len(s))
}
for i:=len(s)-1;i>=0;i--{
for j:=i;j<len(s);j++{
if s[i] != s[j]{
dp[i][j] = false
}else{
if j-i<=2{
dp[i][j] = true
}else if j-i > 2 && dp[i+1][j-1]{
dp[i][j] = true
}
}
if dp[i][j]{
ans++
}
}
}
return ans
}