力扣-字符串

一、字符串常用api

string类

函数说明
char charAt(int index)返回 char指定索引处的值。
boolean contains(CharSequence s)当且仅当此字符串包含指定的char值序列时才返回true。
boolean equals(Object anObject)将此字符串与指定对象进行比较
boolean isEmpty()返回 true如果,且仅当 length()为 0
int length()返回此字符串的长度
String replace(char oldChar, char newChar)返回从替换所有出现的导致一个字符串 oldChar在此字符串 newChar
char[] toCharArray()将此字符串转换为新的字符数组。
static String valueOf(char c)返回 char参数的字符串 char形式。
String[] split(String regex)将此字符串分割为给定的 regular expression的匹配。
static String join(CharSequence delimiter, Iterable<? extends CharSequence> elements)返回一个新 String的副本组成 CharSequence elements与指定的副本一起加入 delimiter 。

StringBuffer类
1、StringBuffer类主要增加了字符串的增删改查等操作
2、StringBuilder类为StringBuffer的子类,为单线程字符串缓存区,优点速度更快

函数说明
StringBuffer append(char c)将 char参数的字符串表示附加到此序列。
StringBuffer insert(int offset, char c)在此序列中插入 char参数的字符串表示形式。
StringBuffer reverse()导致该字符序列被序列的相反代替。
StringBuffer delete(int start, int end)删除此序列的子字符串中的字符。
int indexOf(String str)返回指定子字符串第一次出现的字符串内的索引。

二、翻转字符串里的单词

思路1:
很多语言对字符串提供了 split(拆分),reverse(翻转)和 join(连接)等方法,因此我们可以简单的调用内置的 API 完成操作:

使用 split 将字符串按空格分割成字符串数组;
使用 reverse 将字符串数组进行反转;
使用 join 方法将字符串数组拼成一个字符串。
代码:

class Solution {
    public String reverseWords(String s) {
        // 除去开头和末尾的空白字符
        s = s.trim();
        // 正则匹配连续的空白字符作为分隔符分割
        List<String> wordList = Arrays.asList(s.split("\\s+"));
        Collections.reverse(wordList);
        return String.join(" ", wordList);
    }
}

思路2:

1.去除首尾以及中间多余空格
2.反转整个字符串
3.反转各个单词

class Solution {
   /**
     * 1.去除首尾以及中间多余空格
     * 2.反转整个字符串
     * 3.反转各个单词
     */
    public String reverseWords(String s) {
    
        // 1.去除首尾以及中间多余空格
        StringBuilder sb = removeSpace(s);
        // 2.反转整个字符串
        reverseString(sb, 0, sb.length() - 1);
        // 3.反转各个单词
        reverseEachWord(sb);
        return sb.toString();
    }

    private StringBuilder removeSpace(String s) {
        int start = 0;
        int end = s.length() - 1;
        //首先移除首尾空格
        while (s.charAt(start) == ' ') start++;
        while (s.charAt(end) == ' ') end--;
        StringBuilder sb = new StringBuilder();
        while (start <= end) {
            char c = s.charAt(start);
            //如果字符不为空,或者sb中存储的最后的一个字符不是空字符
            if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') {
                sb.append(c);
            }
            start++;
        }
        return sb;
    }

    /**
     * 反转字符串指定区间[start, end]的字符
     */
    public void reverseString(StringBuilder sb, int start, int end) {
        while (start < end) {
            char temp = sb.charAt(start);
            sb.setCharAt(start, sb.charAt(end));
            sb.setCharAt(end, temp);
            start++;
            end--;
        }
    }

    private void reverseEachWord(StringBuilder sb) {
        int start = 0;
        int end = 1;
        int n = sb.length();
        while (start < n) {
            while (end < n && sb.charAt(end) != ' ') {
                end++;
            }
            reverseString(sb, start, end - 1);
            start = end + 1;
            end = start + 1;
        }
    }
}

三、字符串匹配-KMP

KMP的经典思想是: 当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。

next数组就是一个前缀表(prefix table)。

前缀表有什么作用呢?

前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。

前缀: 是指不包含最后一个字符的所有以第一个字符开头的连续子串。

后缀: 是指不包含第一个字符的所有以最后一个字符结尾的连续子串。

kmp 最长公共前后缀: 最长相等前后缀

其中n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。

构造next数组其实就是计算模式串s,前缀表的过程。
主要有如下三步:

1、初始化:
定义两个指针i和j,j指向前缀起始位置(同时也表示前后缀最长相等长度),i指向后缀起始位置。

然后还要对next数组进行初始化赋值,如下:

int j = -1;
next[0] = j;

2、处理前后缀不相同的情况
因为j初始化为-1,那么i就从1开始,进行s[i] 与 s[j+1]的比较。

所以遍历模式串s的循环下标i 要从 1开始,代码如下:

for(int i = 1; i < s.size(); i++) {
如果 s[i] 与 s[j+1]不相同,也就是遇到 前后缀末尾不相同的情况,就要向前回退。

怎么回退呢?

next[j]就是记录着j(包括j)之前的子串的相同前后缀的长度。

那么 s[i] 与 s[j+1] 不相同,就要找 j+1前一个元素在next数组里的值(就是next[j])。

所以,处理前后缀不相同的情况代码如下:

while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
    j = next[j]; // 向前回退
    //因为这里其实使用的j+1去做运算的,这里用了-1运算
    //j+1 = next[j];
}

3、处理前后缀相同的情况
如果s[i] 与 s[j + 1] 相同,那么就同时向后移动i 和j 说明找到了相同的前后缀,同时还要将j(前缀的长度)赋给next[i], 因为next[i]要记录相同前后缀的长度。

代码如下:

if (s[i] == s[j + 1]) { // 找到相同的前后缀
    j++;
}
next[i] = j;

整合代码:

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]
    }
}

完整代码(建议直接使用原本的前缀数组):

class Solution {
    public int strStr(String haystack, String needle) {
        int n = haystack.length(), m = needle.length();
        if (m == 0) {
            return 0;
        }
        //求next前缀数组,起始是0,没有-1也没有后移动
        int[] pi = new int[m];
        for (int i = 1, j = 0; i < m; i++) {//初始化 i = 1, j=0
            while (j > 0 && needle.charAt(i) != needle.charAt(j)) {//前后缀末尾不匹配,j前缀末尾根据记录前移
                j = pi[j - 1];
            }
            if (needle.charAt(i) == needle.charAt(j)) {//前后缀匹配,ij同时向后+1
                j++;
            }
            pi[i] = j; //前缀数组赋值
        }
       //进行两个字符串的匹配
        for (int i = 0, j = 0; i < n; i++) {
            while (j > 0 && haystack.charAt(i) != needle.charAt(j)) {
                j = pi[j - 1];//如果不匹配,则调整j的位置
            }
            if (haystack.charAt(i) == needle.charAt(j)) {
                j++; //匹配,j+1
            }
            if (j == m) {//判断j是否已经匹配成功
                return i - m + 1;
            }
        }
        return -1;
    }
}

五、重复的子字符串-没有理解

https://leetcode-cn.com/problems/repeated-substring-pattern/

https://mp.weixin.qq.com/s?__biz=MzUxNjY5NTYxNA==&mid=2247490351&idx=2&sn=5049d24aa9dab51651e7772b67955006&scene=21#wechat_redirect

给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。

示例 1:
输入: “abab”
输出: True
解释: 可由子字符串 “ab” 重复两次构成。
在这里插入图片描述
在这里插入图片描述

class Solution {
    public boolean repeatedSubstringPattern(String s) {
        if (s.equals("")) return false;

        int len = s.length();

        int[] next = new int[len + 1];
        // 构造 next 数组过程,
        for (int i = 1, j = 0; i < len; i++) {
            // 匹配不成功,j回到前一位置 next 数组所对应的值
            while (j > 0 && s.charAt(i) != s.charAt(j)) j = next[j-1];
            // 匹配成功,j往后移
            if (s.charAt(i)==s.charAt(j)) j++;
            // 更新 next 数组的值
            next[i] = j;
        }

        // 最后判断是否是重复的子字符串,这里 next[len] 即代表next数组末尾的值
        if (next[len-1] > 0 && len % (len - next[len-1]) == 0) {
            return true;
        }
        return false;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值