Leetcode:392. 判断子序列、115. 不同的子序列(C++)

文章介绍了如何使用双指针和动态规划方法解决字符串子序列的问题。针对392题和115题,分别展示了如何判断一个字符串是否为另一个的子序列以及统计不同子序列的数量。动态规划在这类问题中的应用是通过构建二维数组来存储中间状态,从而优化解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

392. 判断子序列

题目描述:

实现代码与解析:

双指针

原理思路:

动态规划:

原理思路:

115. 不同的子序列

题目描述:

实现代码与解析:

动态规划

原理思路:


392. 判断子序列

题目描述:

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

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

进阶:

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

示例 1:

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

示例 2:

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

提示:

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

实现代码与解析:

双指针

class Solution {
public:
    bool isSubsequence(string s, string t) {
        int i = 0, j = 0;
        while(i < s.size() && j < t.size())
        {
            if (s[i] == t[j])
            {
                i++;
                j++;
            }
            else j++;
        }
        if (i == s.size()) return true;
        else return false;
    }
};

原理思路:

        双指针,较简单。

动态规划:

class Solution {
public:
    bool isSubsequence(string s, string t) {
       
       vector<vector<int>> f(s.size() + 1, vector<int>(t.size() + 1, 0));

        for (int i = 1; i <= s.size(); i++)
            for (int j = 1; j <= t.size(); j++)
            {
                if (s[i - 1] == t[j - 1]) f[i][j] = f[i - 1][j - 1] + 1;
                else f[i][j] = max(f[i - 1][j], f[i][j - 1]);
            }
            if (f[s.size()][t.size()] == s.size()) return true;
            return false;

    }
};

原理思路:

        与求两字符串最长公共子序列相同, 只不过这里是判断一个字符串是不是另一个字符串的子序列而已。dp[i][j]数组含义为s中前 i - 1,t中 j - 1 前(包含 j - 1)的最长公共子序列。

        所以

else f[i][j] = max(f[i - 1][j], f[i][j - 1]);

也可以改为:

else f[i][j] = f[i][j - 1];

        这时dp数组的含义就变为了s中以 i - 1为结尾串和t中 j - 1 前(包含 j - 1)的最长公共子序列。也可以解决问题。

115. 不同的子序列

题目描述:

        给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数。

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

示例 1:

输入:s = "rabbbit", t = "rabbit"
3
解释:
如下所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。
rabbbit、rabbbit、rabbbit

示例 2:

输入:s = "babgbag", t = "bag"
输出5
解释:
如下所示, 有 5 种可以从 s 中得到 "bag" 的方案。 
babgbag、babgbag、babgbag、babgbag、babgbag

提示:

  • 1 <= s.length, t.length <= 1000
  • s 和 t 由英文字母组成

实现代码与解析:

动态规划

class Solution {
public:
    int numDistinct(string s, string t) {
        vector<vector<unsigned long long>> f(s.size() + 1, vector<unsigned long long>(t.size() + 1, 0));
        // 初始化
        for (int i = 0; i < s.size(); i++) f[i][0] = 1;

        for (int j = 1; j < t.size(); j++) f[0][j] = 0;

        for (int i = 1; i <= s.size(); i++)
            for (int j = 1; j <= t.size(); j++)
                if (s[i - 1] == t[j - 1]) f[i][j] = f[i - 1][j - 1] + f[i - 1][j];
                else f[i][j] = f[i - 1][j];
        
        return f[s.size()][t.size()];
    }
};

原理思路:

        沿用前一题的思想,我们dp[i][j]数组的含义为s的下标 i - 1(包含i - 1),t 中以 j  - 1结尾的串中公共子序列的个数。

        根据dp数组的含义写出递推式

·当 s[i-1] == t[j-1]时,f[ i ][ j ] = f[ i - 1 ][ j - 1] + f[ i - 1][ j ] ,两种情况,j 可以选择和 i 匹配,也可以选择不和 i 匹配和 i 前面的匹配,因为这里要计算个数嘛,所以要相加起来。

·当 s[i-1] != t[j-1] 时, f[ i ][ j ] = f[ i - 1][ j ], 既然不相等,那么只能选择和 i 前面的去匹配。 

        本题最重要的是dp数组的初始化

·当 j = 0 时,说明 t 为空,所以 s 中一个都不选才能为空这一种情况,所以赋值为1。

·当 i = 0 时,说明 s 为空,t 无论如何都不可能形成空,所以赋值为0。

·当 i 和 j 都 等于 0 时,显然也是 1。

        可以根据dp数组含义好好思考一下。

### 解题思路 LeetCode 第 674 题的目标是找到给定数组中的最长连续递增子序列的长度。此问题可以通过一次线性扫描来解决,时间复杂度为 O(n),空间复杂度可以优化到 O(1)[^1]。 #### 关键点分析 - **连续性**:题目强调的是“连续”,因此只需要比较相邻两个元素即可判断是否构成递增关系。 - **动态规划 vs 贪心算法**:虽然可以用动态规划的思想解决问题,但由于只需记录当前的最大值而无需回溯历史状态,贪心策略更为高效[^3]。 --- ### Python 实现 以下是基于贪心算法的 Python 实现: ```python class Solution: def findLengthOfLCIS(self, nums): if not nums: # 如果输入为空,则返回0 return 0 max_len = 1 # 至少有一个元素时,最小长度为1 current_len = 1 # 当前连续递增序列的长度初始化为1 for i in range(1, len(nums)): # 从第二个元素开始遍历 if nums[i] > nums[i - 1]: # 判断当前元素是否大于前一个元素 current_len += 1 # 是则增加当前长度 max_len = max(max_len, current_len) # 更新全局最大长度 else: current_len = 1 # 否则重置当前长度 return max_len # 返回最终结果 ``` 上述代码通过维护 `current_len` 和 `max_len` 来跟踪当前连续递增序列的长度以及整体的最大长度。 --- ### Java 实现 下面是等效的 Java 版本实现: ```java public class Solution { public int findLengthOfLCIS(int[] nums) { if (nums.length == 0) { // 处理边界情况 return 0; } int maxLength = 1; // 初始化最大长度 int currentLength = 1; // 初始化当前长度 for (int i = 1; i < nums.length; i++) { if (nums[i] > nums[i - 1]) { // 若满足递增条件 currentLength++; // 增加当前长度 maxLength = Math.max(maxLength, currentLength); // 更新最大长度 } else { currentLength = 1; // 不满足递增条件时重新计数 } } return maxLength; // 返回结果 } } ``` 该版本逻辑与 Python 类似,但在语法上更贴近 Java 的特性[^4]。 --- ### C++ 实现 对于 C++ 用户,下面是一个高效的解决方案: ```cpp #include <vector> #include <algorithm> // 使用 std::max 函数 using namespace std; class Solution { public: int findLengthOfLCIS(vector<int>& nums) { if (nums.empty()) { // 边界处理 return 0; } int result = 1; // 结果变量 int count = 1; // 当前连续递增序列长度 for (size_t i = 1; i < nums.size(); ++i) { if (nums[i] > nums[i - 1]) { // 检查递增条件 count++; result = max(result, count); } else { count = 1; // 重置计数器 } } return result; // 返回最终结果 } }; ``` 这段代码同样遵循了单次遍历的原则,并利用标准库函数简化了一些操作。 --- ### 小结 三种语言的核心思想一致,均采用了一种简单的线性扫描方式完成任务。这种方法不仅易于理解,而且性能优越,在实际应用中非常实用[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cosmoshhhyyy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值