459.重复的子字符串,28. 实现 strStr(),剑指Offer 05.替换空格

简介: 本内容涵盖三个字符串处理问题的解决方案,包括算法思路与代码实现。 **一、重复的子字符串(459题)**:通过构造新字符串并查找原字符串是否存在其中,巧妙判断是否由重复子串组成,时间复杂度低。 **二、找出字符串中第一个匹配项的下标(28题)**:运用KMP算法构建前缀表优化匹配过程,减少不必要的回溯操作,提高效率。 **三、替换空格(剑指Offer 05题)**:采用双指针法从后向前替换,避免频繁移动元素,空间复杂度更优。以上方法均针对字符串操作的经典问题,展示了高效算法设计的精髓。

 题目:459. 重复的子字符串

给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。

示例 1:

输入: s = "abab"

输出: true

解释: 可由子串 "ab" 重复两次构成。

示例 2:

输入: s = "aba"

输出: false

示例 3:

输入: s = "abcabcabcabc"

输出: true

解释: 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)

提示:

1 <= s.length <= 104

s 由小写英文字母组成

思考过程与知识点:

暴力的解法, 就是一个for循环获取 子串的终止位置, 然后判断子串是否能重复构成字符串,又嵌套一个for循环,所以是O(n^2)的时间复杂度。

有的同学可以想,怎么一个for循环就可以获取子串吗? 至少得一个for获取子串起始位置,一个for获取子串结束位置吧。

其实我们只需要判断,以第一个字母为开始的子串就可以,所以一个for循环获取子串的终止位置就行了。 而且遍历的时候 都不用遍历结束,只需要遍历到中间位置,因为子串结束位置大于中间位置的话,一定不能重复组成字符串。

题解:

class Solution {
public:
    bool repeatedSubstringPattern(string s) {
        string t = s + s;
        t.erase(t.begin()); t.erase(t.end() - 1); // 掐头去尾
        if (t.find(s) != std::string::npos) return true; // r
        return false;
    }
};

题目:28. 找出字符串中第一个匹配项的下标

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回  -1 。

示例 1:

输入:haystack = "sadbutsad", needle = "sad"

输出:0

解释:"sad" 在下标 0 和 6 处匹配。

第一个匹配项的下标是 0 ,所以返回 0 。

示例 2:

输入:haystack = "leetcode", needle = "leeto"

输出:-1

解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。

提示:

1 <= haystack.length, needle.length <= 104

haystack 和 needle 仅由小写英文字符组成

思考过程与知识点:

前缀表是如何记录的呢?

首先要知道前缀表的任务是当前位置匹配失败,找到之前已经匹配上的位置,再重新匹配,此也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置。

有了next数组,就可以根据next数组来 匹配文本串s,和模式串t了。

在文本串s里 找是否出现过模式串t。

定义两个下标j 指向模式串起始位置,i指向文本串起始位置。

那么j初始值依然为-1,为什么呢? 依然因为next数组里记录的起始位置为-1。

很多KMP算法的时间都是使用next数组来做回退操作,那么next数组与前缀表有什么关系呢?

next数组就可以是前缀表,但是很多实现都是把前缀表统一减一(右移一位,初始位置为-1)之后作为next数组。

为什么这么做呢,其实也是很多文章视频没有解释清楚的地方。

其实这并不涉及到KMP的原理,而是具体实现,next数组既可以就是前缀表,也可以是前缀表统一减一(右移一位,初始位置为-1)。

题解:

class Solution {
public:
    void getNext(int* next, const string& s) {
        int j = -1;
        next[0] = j;
        for(int i = 1; i < s.size(); i++) { // 注意i从1开始
            while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
                j = next[j]; // 向前回退
            }
            if (s[i] == s[j + 1]) { // 找到相同的前后缀
                j++;
            }
            next[i] = j; // 将j(前缀的长度)赋给next[i]
        }
    }
    int strStr(string haystack, string needle) {
        if (needle.size() == 0) {
            return 0;
        }
        int next[needle.size()];
        getNext(next, needle);
        int j = -1; // // 因为next数组里记录的起始位置为-1
        for (int i = 0; i < haystack.size(); i++) { // 注意i就从0开始
            while(j >= 0 && haystack[i] != needle[j + 1]) { // 不匹配
                j = next[j]; // j 寻找之前匹配的位置
            }
            if (haystack[i] == needle[j + 1]) { // 匹配,j和i同时向后移动
                j++; // i的增加在for循环里
            }
            if (j == (needle.size() - 1) ) { // 文本串s里出现了模式串t
                return (i - needle.size() + 1);
            }
        }
        return -1;
    }
};

题目:剑指 Offer 05. 替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

示例 1:

输入:s = "We are happy."

输出:"We%20are%20happy."

限制:

0 <= s 的长度 <= 10000

思考过程与知识点:

如果想把这道题目做到极致,就不要只用额外的辅助空间了!

首先扩充数组到每个空格替换成"%20"之后的大小。

然后从后向前替换空格,也就是双指针法,过程如下:

i指向新长度的末尾,j指向旧长度的末尾。

题解:

class Solution {
public:
    string replaceSpace(string s) {
        int count = 0; // 统计空格的个数
        int sOldSize = s.size();
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == ' ') {
                count++;
            }
        }
        // 扩充字符串s的大小,也就是每个空格替换成"%20"之后的大小
        s.resize(s.size() + count * 2);
        int sNewSize = s.size();
        // 从后先前将空格替换为"%20"
        for (int i = sNewSize - 1, j = sOldSize - 1; j < i; i--, j--) {
            if (s[j] != ' ') {
                s[i] = s[j];
            } else {
                s[i] = '0';
                s[i - 1] = '2';
                s[i - 2] = '%';
                i -= 2;
            }
        }
        return s;
    }
};


目录
打赏
0
0
0
0
59
分享
相关文章
软考软件评测师——计算机组成与体系结构(分级存储架构)
本内容全面解析了计算机存储系统的四大核心领域:虚拟存储技术、局部性原理、分级存储体系架构及存储器类型。虚拟存储通过软硬件协同扩展内存,支持动态加载与地址转换;局部性原理揭示程序运行特性,指导缓存设计优化;分级存储架构从寄存器到外存逐级扩展,平衡速度、容量与成本;存储器类型按寻址和访问方式分类,并介绍新型存储技术。最后探讨了存储系统未来优化趋势,如异构集成、智能预取和近存储计算等,为突破性能瓶颈提供了新方向。
104.二叉树的最大深度 , 111.二叉树的最小深度,222.完全二叉树的节点个数
本内容主要讲解了三道与二叉树相关的算法题及其解法,包括“二叉树的最大深度”、“二叉树的最小深度”和“完全二叉树的节点个数”。通过递归方法(前序或后序遍历)实现求解。 - **最大深度**:利用后序遍历计算根节点到最远叶子节点的路径长度。 - **最小深度**:同样采用后序遍历,但需特别处理单子树为空的情况,确保找到从根到最近叶子节点的路径。 - **完全二叉树节点数**:基于递归后序遍历统计左右子树节点数量并累加。 代码示例清晰展示了递归逻辑,帮助理解二叉树深度与高度的概念及其实现方式。
669. 修剪二叉搜索树 ,108.将有序数组转换为二叉搜索树 , 538.把二叉搜索树转换为累加树
1. **修剪二叉搜索树(669号题)**:通过递归方法,移除值不在指定范围 `[low, high]` 内的节点,同时保持树中剩余节点的相对结构不变。核心思想是根据当前节点值与边界的关系决定保留左子树还是右子树。 2. **将有序数组转换为二叉搜索树(108号题)**:将一个升序排列的数组转化为一棵高度平衡的二叉搜索树。采用分治法,选取数组中间元素作为根节点,递归构建左右子树。即使数组长度为偶数,选择任一中间值均可满足条件。 3. **把二叉搜索树转换为累加树(538号题)**:通过修改二叉搜索树中每个节点的值,使其等于原树中所有大于或等于该节点值的和。
455.分发饼干 ,376. 摆动序列 , 53. 最大子序和
**简介:** 本文介绍了三道经典的算法题及其解法,涵盖贪心算法、动态规划等重要思想。第一题“分发饼干”通过贪心策略,将大尺寸饼干优先分配给胃口大的孩子,实现满足最多孩子的目标。第二题“摆动序列”利用差值变化判断峰值,统计最长摆动子序列长度,需处理平坡与边界情况。第三题“最大子数组和”采用动态规划思想,在局部最优中寻找全局最大连续子数组和。三道题目均附有详细解析与C++代码实现,帮助理解算法核心逻辑与实现细节。
513.找树左下角的值,112. 路径总和 113.路径总和ii, 106.从中序与后序遍历序列构造二叉树 105.从前序与中序遍历序列构
本内容涵盖了三道与二叉树相关的算法题及其解决方案。第一题“找树左下角的值”通过深度优先搜索(DFS)找到二叉树最底层最左边的节点值。第二题“路径总和”判断是否存在从根到叶子节点的路径,其节点值之和等于目标值。第三题“从中序与后序遍历序列构造二叉树”利用中序和后序遍历结果还原二叉树结构。每个题解均采用递归方法实现,逻辑清晰且高效,适用于处理大规模数据的二叉树问题。
122.买卖股票的最佳时机II ,55. 跳跃游戏 ,45.跳跃游戏II
**简介:** 本文介绍了三道经典算法题的解法,涵盖贪心算法的核心思想与应用。 1. **买卖股票的最佳时机 II**:通过收集每天的正利润实现最大收益,局部最优推导全局最优。 2. **跳跃游戏**:利用贪心算法扩展覆盖范围,判断是否能到达终点。 3. **跳跃游戏 II**:基于最大覆盖范围计算最小跳跃次数,平衡当前步与下一步的覆盖距离。 三道题目均采用贪心策略,通过优化局部选择实现整体最优解,代码简洁高效,时间复杂度低,适合解决类似问题。
上交大、上海人工智能实验室开源首个多轮安全对齐数据集 SafeMTData
最近,以 OpenAI o1 为代表的 AI 大模型的推理能力得到了极大提升,在代码、数学的评估上取得了令人惊讶的效果。OpenAI 声称,推理可以让模型更好的遵守安全政策,是提升模型安全的新路径。
解释网络切片:安全挑战与解决方案
解释网络切片:安全挑战与解决方案
228 4
前端 JS 经典:最长递增子序列
前端 JS 经典:最长递增子序列
135 2

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问