【LC#392&&70】判断子序列&&爬楼梯(dp算法 第一期)

392. 判断子序列 - 力扣(LeetCode)

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

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

进阶:

如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

致谢:

特别感谢 @pbrother 添加此问题并且创建所有测试用例。

示例 1:

输入:s = "abc", t = "ahbgdc"
输出:true
示例 2:

输入:s = "axc", t = "ahbgdc"
输出:false

提示:

0 <= s.length <= 100
0 <= t.length <= 10^4
两个字符串都只由小写字符组成。

面对这道题第一问,最简单直接的方法就是双指针,定义一个指针p1指向匹配字符串s,定义p2指向原字符串t;

1. 因为两个字符串都只由小写字符组成,所以如果p2遇到末尾符号,则立刻结束并返回错误

2. 如果p1元素与p2元素相等,则p1 p2都++;

3. 如果不相等,则只给p2++;

4. 如果 p1能够遍历到结束标志,说明匹配完成,退出循环并返回正确

实现代码

bool isSubsequence(char* s, char* t) {
    int p1=0,p2=0;
    while(*(p1+s)!='\0'){
        if(*(p2+t)=='\0'){
            return false;
        }
        if(*(t+p2)==*(s+p1)){
            p1++;
        }
        p2++;
    }
    return true;
}

不写注释了,代码很短很简单。

 

进阶问题!!!(动态规划)

如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

这种情况下,想要用第一种方法快速高效解决问题根本不可能。因为第一种方法时间复杂度为:

                                                      T(M,N)= O(M+N)

M若是扩张到十亿的数量级,CPU的负担是很大的。

这里,我们引入一个新的算法 动态规划。

我们先不看这个问题本身,先思考下面一个最经典的爬楼梯问题:

70. 爬楼梯 - 力扣(LeetCode)

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 1:

输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:

输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
 

提示:

1 <= n <= 45

这个问题大家应该在某个电影里看过哈哈,主角还是占扑算出来的

《少年班》细节解析:王大法用龟壳解题,现实中可能实现吗?_哔哩哔哩_bilibili

咱们先不去想占扑可不可能,咱们思考一下如何在计算机系统解决这个问题?

从最简单的开始,如果爬3阶楼梯,有哪几种爬法?

1. 第一脚 1 第二脚 2

2. 第一脚 2 第二脚 1

3. 第一脚 1 第二脚 1 第三脚 1

统计过后可以得出是三种,但是我们是否发现一个规律呢?

如果我们假设达到第i个台阶的走法为dp[ i ], 那么我们能倒推出两种到达 i 的情况,第一种是从第i-1个台阶走一步到达i,另外一种是从i-2个台阶走两步到达i

那么,到第i个台阶的走法不就等于到i-1走法,和到i-2走法之和吗?

这样我们可以列出如下公式:

                                               dp[i] = dp[i-1] + dp[i-2]

这个公式,叫做动态规划的  状态转移方程  (state transition equation

咋样,是不是有点像现代控制理论里的状态转移矩阵推导?确实,这种算法和对应思想也是为各行各业的数学建模打下了基础,所以哪里都能看到它的影子。

其次,斐波那契数列也是在这个规律下形成的。

好了,那我们唠嗑完毕后,应该仔细想一想这个算法该如何写了:

1. 开辟一串长为n+1的整形内存dp用于迭代求和(这里为什么是n+1?因为在定义数据结构时,开发者们经常会绕开起点0避免标签出错,包括各类教科书、考研大纲中也会出现这样的写法,换言之我们的计算从dp[1]和dp[2]开始 ,当然你不觉得0开始太抽象的话也可以呢样写

2. 接着,根据上面3阶情况分析,设定dp[1] = 1, dp[2] = 2

3. 接下来进入一个循环,不断计算dp[i] = dp [i-1] + dp[i-2], 直到i=n+1结束;

4. 输出结果sum = dp [n];

代码实现

int climbStairs(int n) {
    if(n==1){
        return 1;
    }
    //首先明确一点,循环算法对1阶和2阶情况不起作用,所以要对这两个单独处理
    if(n==2){
        return 2;
    }
    int* dp = (int *)malloc((n+1)*sizeof(int));
    //可以在堆区分配dp内存,当然也可以在栈区定义一个动态数组
    //只要能求出最后一个元素并赋值给sum就符合要求
    dp[1]=1;
    dp[2]=2;
    //一切从1 2阶的基本事实开始
    for (int i=3; i

        像这样,通过定义一维状态数组 dp[i] 表示与单一维度变量 i 相关的子问题最优解,并基于前序状态递推转移方程 dp[i]=f(dp[0..i−1]) 求解问题的动态规划方法,需设定初始条件并按顺序计算状态值的算法,我们称作一维动态规划

        一维动态规划有很多典型例题,比如爬楼梯、打劫问题、换零钱等,其中某些问题使用贪心算法也能勉强解决,但是对于大部分问题而言,贪心所需的局部最优解是很难寻找的。所以,想要每道OJ都躺着AC,掌握动态规划是极其重要的。

        接下来我们重新思考这个子序列问题进阶,题解将在下一期放出。

如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,
你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

 

 

 

你可能感兴趣的:(【LC#392&&70】判断子序列&&爬楼梯(dp算法 第一期))