Talking about the classic dynamic programming problem caused by the longest common subsequence

Get into the habit of writing together! This is the first day of my participation in the "Nuggets Daily New Plan · April Update Challenge", click to view the details of the event .

foreword

Pay attention to the official account [Programmer Bai Ze], and take you to a different programmer/student party. The official account will usually update blog articles synchronously, and reply to [Resume] to get the resume template I use.

I hope that the Shanghai epidemic will pass as soon as possible. In fact, there is a stable period of time that is more suitable for precipitating technology, and I am a little loose. I should resume and update the "Hand-Tear MySQL" series of articles in the near future. This article uses a classic example: the longest common subsequence to tell you about dynamic programming, and gives a LeetCode weekly dynamic programming question as a practice and explanation. I believe that after reading the article, you will have a deeper understanding of dynamic programming. understand.

Regarding the following dp practice questions, it is the fourth question of a certain weekly competition. With this question, I will explain how to start from reading the question and solve an algorithm problem step by step immersed in the analysis part later. This process applies to all topics and is more important. Of course, we start with the classic longest common subsequence.

longest common subsequence

Topic link: LeetCode 1143

topic

Given two strings text1 and text2, return the length of the longest common subsequence of the two strings . Returns 0 if no common subsequence exists.

A subsequence of a string refers to a new string: it is a new string composed of the original string after deleting some characters (or no characters) without changing the relative order of the characters, subsequence aceif so .abcde

The common subsequence of two strings is the subsequence that both strings have in common.

analyze

设dp[i] [j]为text1的前i个字符组成的串str1和text2的前j个字符组成的串str2的最长公共子序列,初始化时:dp[0] [j]与dp[i] [0]都为0,因为str1为空或者str2为空都将无法构成子序列

根据上面的表述,text1[i-1]是str1的最后一个字符,而不是text1[i],因为下标从0开始;同理test2[j-1]表示str2的最后一个字符

那么就可以开始讨论状态转移方程: 如果 text[i-1] == text2[j-1],表示str1的最后一个字符和str2的最后一个字符相等,那么dp[i] [j] = dp[i-1] [j-1] + 1,可以理解成两个字符串都去掉相等的末尾字符,然后在前面剩余的字符中再求最长公共子序列,最后结果+1,因为这个过程是可以追溯的,因此满足动态规划的要求

如果 text[i-1] != text2[j-1],则dp[i] [j] = max(dp[i-1] [j], dp[i] [j-1]) ,因为dp[i-1] [j]和dp[i] [j-1]都是已经求出来的字问题的解,所以可以追溯,既然str1和str2的末尾不一样,那么就让str1去掉末尾和str2求解或者str2去掉末尾和str1求解,两者取最大值即可

代码

用的是go语言,但语言不是障碍~

func longestCommonSubsequence(text1 string, text2 string) int {
    dp := make([][]int, len(text1)+1)
    for i := range dp {
        dp[i] = make([]int, len(text2)+1)
    }
    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]) //为节约篇幅max函数就不写明了,这里需要自己实现
            }
        }
    }
    return dp[len(text1)][len(text2)]
}
复制代码

用地毯覆盖后的最少白色砖块

题目链接:LeetCode 2209

题目

给你一个下标从 0 开始的 二进制 字符串 floor ,它表示地板上砖块的颜色。

  • floor[i] = '0' 表示地板上第 i 块砖块的颜色是 黑色 。
  • floor[i] = '1' 表示地板上第 i 块砖块的颜色是 白色 。

同时给你 numCarpets 和 carpetLen 。你有 numCarpets 条 黑色 的地毯,每一条 黑色 的地毯长度都为 carpetLen 块砖块。请你使用这些地毯去覆盖砖块,使得未被覆盖的剩余 白色 砖块的数目 最小 。地毯相互之间可以覆盖。

请你返回没被覆盖的白色砖块的 最少 数目。

分析

其实在拿到题的一开始,如果看不太明白题意,建议先对照输入输出样例去梳理,比如对于如下输入输出

输入:floor = "10110101", numCarpets = 2, carpetLen = 2
输出:2,表示最少有2块白色没有被覆盖到(1表示白色)
复制代码

image-20220407162920898

结合样例你大概懂了题意,好像是要用黑色的地毯尽可能去覆盖白色的连续区域,使得最后剩余的白色最少。大概明白做什么之后,去看输入输出数据的取值范围,因为这涉及到你设计的算法的时间复杂度(主要是时间),如下:

1 <= carpetLen <= floor.length <= 1000
floor[i] 要么是 '0' ,要么是 '1' 。
1 <= numCarpets <= 1000
复制代码

错误思路

floor长度1000,看样子可以写一个O(N^2)的算法,你放心了。然后想:既然是尽可能去覆盖白色连续区域,且每次就是拿一个长度为L的地毯去覆盖,那么我只要每次找一个长度为L的拥有最多白色的块的区间去给他覆盖不就行了,然后把白色改成黑色,外循环是地毯数量,核心是贪心!我又行了!

下面给出一组测试数据:

输入:floor = "101111", numCarpets = 2, carpetLen = 3
输出:0
复制代码

如果是贪心,那么首先会找到连续的3个1的部分,然后将其修改为100001,然后再找到包含一个1的长度为3的区间,将其修改为000001或者100000,无法到达最优效果:第一次覆盖前3块,第二次覆盖后3块。

正确思路

对于给出的数据,思考是否能使用dp求解,对于动态规划来说,首先要确定规模,一维的dp本题无法胜任,因为地毯数量有多块。

如果是二维dp,那么i和j分别表示什么,一般来说:我习惯于将j设置为被具体操作的“对象”空间(就像是0-1背包我会将背包空间设置为j,而物品的种类设置为i,因为所有i种物品都会放置在j大小的空间中,背包空间此时是被操作"对象"),本题所有的地毯覆盖到一个floor上,因此j的纬度是地砖数量(那么i的维度就是地毯的数量)

The final dp[i][j] represents the minimum number of remaining white floor tiles using i carpets to cover the first j tiles

The state transition equation is given below:

If the ith carpet chooses to cover the floor tile with index j, then dp[i] [j] = dp[i-1] [j-carpetLen] , which means that only the first i-1 carpet is considered to cover the first j-carpetLen Minimum number of white blocks for a position (because positions that cover the i-th block are all black)

If the ith carpet chooses not to cover the floor tile with index j, then dp[i] [j] = dp[i] [j-1]+(floor[j] - '0') , which is equivalent to only considering i carpets cover the floor tiles of j-1, and the jth tile may be white, so add

code

func minimumWhiteTiles(floor string, numCarpets int, carpetLen int) int {
    dp := make([][]int, numCarpets+1)
    for i := range dp {
        dp[i] = make([]int, len(floor))
    }
    num := 0
    for i := 0; i < len(floor); i++ {
        num += int(floor[i] - '0')
        dp[0][i] = num
    }
    for i := 1; i <= numCarpets; i++ {
        // 注意j的起始位置,前carpetLen长度就是一块地毯的空间,此时的dp[i][j]一定是0
        for j := carpetLen; j < len(floor); j++ {
            // 对于j位置,需要在两种情况中选择白色数量最少的一种保留
            dp[i][j] = min(dp[i][j-1]+int(floor[j] - '0'), dp[i-1][j-carpetLen])
        }
    }
    return dp[numCarpets][len(floor)-1]
}
复制代码

end

It is recommended to do the two questions 1143 and 2209 by yourself after reading the article. I believe that your mastery of dynamic programming will definitely improve. The algorithm level is still very important in the written interview, and the classic dynamic programming questions are the source of templates for many questions, which are worth learning.

Pay attention to the official account [Programmer Bai Ze], and take you to a different programmer/student party. The official account will usually update blog articles synchronously, and reply to [Resume] to get the resume template I use.

\

Guess you like

Origin juejin.im/post/7083807680367689735