LeetCode字符串——代码随想录

字符串

344. 反转字符串

class Solution {
    public void reverseString(char[] s) {
        for(int i=0; i<s.length/2; i++){
            char t = s[i];
            s[i] = s[s.length- 1 - i];
            s[s.length- 1 - i] = t; 
        }
    }
}

541. 反转字符串 II

初步设计:笨逼的数学方法
class Solution {
    public String reverseStr(String s, int k) {
        char[] c = s.toCharArray();
        int tail = s.length()-1;
        boolean flag =false;

        for(int i = 0 ; i < s.length(); i++){
            int flayer = i/(2*k);
            int index = i%(2*k);
            if(index < k/2){
                int left = i;
                int right = 2*k*flayer+k-1-index;
                if(right >s.length()-1){
                    flag = true;
                }
                if(flag){
                    right = tail--;
                    if(right <= left){
                        break;
                    }
                }
                //right = right < s.length()-1? right: s.length()-index-1 ;

                char temp = c[left];
                c[left] = c[right];
                c[right] = temp;
            }
        }
        return String.valueOf(c);
    }
}

用两个循环,还快,1ms

// 解法二还可以用temp来交换数值,会的人更多些
class Solution {
    public String reverseStr(String s, int k) {
        char[] ch = s.toCharArray();
        for(int i = 0;i < ch.length;i += 2 * k){
            int start = i;
            // 判断尾数够不够k个来取决end指针的位置
            int end = Math.min(ch.length - 1,start + k - 1);
            while(start < end){
                
                char temp = ch[start];
                ch[start] = ch[end];
                ch[end] = temp;

                start++;
                end--;
            }
        }
        return new String(ch);
    }
}
标记right/left2
class Solution {

    public void reverse(char[] arr, int left, int right) {
        while (left < right) {
            char temp = arr[left];
            arr[left] = arr[right];
            arr[right] = temp;
            left++;
            right--;
        }
    }

    public String reverseStr(String s, int k) {
        char[] s1 = s.toCharArray();
        for(int left = 0;left < s.length();left+= 2*k){
            int right = left + k -1;
            if(right > s.length()-1){
                right = s.length()-1;
            }
            reverse(s1,left,right);
        }
        return new String(s1);
    }
    
}

剑指 Offer 05. 替换空格

初步思想:利用 StringBuffer
class Solution {
    public String replaceSpace(String s) {
        char[] s1 = s.toCharArray();
        StringBuffer temp = new StringBuffer();
        for(int i=0; i<s1.length; i++){
            if(s1[i] == ' ') {
                temp.append("%20");
            }else{
                temp.append(s1[i]);
            }
        }
        return temp.toString();
    }
}
双指针法

在java中多此一举,并不能节省更多的空间。

从后往前走,不然复杂度为O(n^2),因为后续元素都要移动

//方式二:双指针法
public String replaceSpace(String s) {
    if(s == null || s.length() == 0){
        return s;
    }
    //扩充空间,空格数量2倍
    StringBuilder str = new StringBuilder();
    for (int i = 0; i < s.length(); i++) {
        if(s.charAt(i) == ' '){
            str.append("  ");
        }
    }
    //若是没有空格直接返回
    if(str.length() == 0){
        return s;
    }
    //有空格情况 定义两个指针
    int left = s.length() - 1;//左指针:指向原始字符串最后一个位置
    s += str.toString();
    int right = s.length()-1;//右指针:指向扩展字符串的最后一个位置
    char[] chars = s.toCharArray();/
    while(left>=0){
        if(chars[left] == ' '){
            chars[right--] = '0';
            chars[right--] = '2';
            chars[right] = '%';
        }else{
            chars[right] = chars[left];
        }
        left--;
        right--;
    }
    return new String(chars);
}

在这里插入图片描述

151. 颠倒字符串中的单词

初步设计:借助顺序表

利用StringBuffer取各个单词,再储存到List里面从后往前取。

时间复杂度O(n),空间复杂度O(n)

class Solution {
    public String reverseWords(String s) {
        char[] s1 = s.toCharArray();
        StringBuffer temp = new StringBuffer();
        List<String> sequence = new ArrayList<>();
        for(int i=0; i<s1.length; i++){
            if(s1[i] == ' ') {
                if(temp.length() != 0) {
                	sequence.add(temp.toString());
                }
                temp.delete( 0,temp.length());
            }else{
                temp.append(s1[i]);
            }
        }
         if(temp.length()!=0){
            sequence.add(temp.toString());
        }
        temp.delete( 0,temp.length());
        for(int i = sequence.size() -1 ; i >0; i--){
            temp.append(sequence.get(i));
            temp.append(" ");
        }
        temp.append(sequence.get(0));

        return temp.toString();
    }
}

改进判断写法:

      	for(int i=0; i<s1.length; i++){
            if(s1[i] == ' ' && temp.length() != 0) {
                sequence.add(temp.toString());
                temp.delete( 0,temp.length());
            }else if(s1[i] != ' '){
                temp.append(s1[i]);
            }
        }

第二次写※:

class Solution {
    public String reverseWords(String s) {
        char[] c = s.toCharArray();
        StringBuilder builder = new StringBuilder();
        List<String> list = new ArrayList();
        String result = new String();

        for(int i = 0; i< c.length ; i++){
            if(c[i] != ' '){
                builder.append(c[i]);
            }
            else if( (i> 0&& c[i-1] != ' ' ) ){
                list.add(builder.toString());
                builder.delete(0,builder.length());
            }
        }
        if(builder.length()!= 0){
            list.add(builder.toString());
            builder.delete(0,builder.length());
        }

        for(int i = list.size()-1; i>0; i--){
            builder.append(list.get(i));
            builder.append(' ');
        }
        builder.append(list.get(0));

        return builder.toString();
    }
}
方法一:使用语言特性

部分正则表达式:

符号作用
\s匹配任何空白字符 包括指标符 换行符 。等价于 [ \f\n\r\t\v]
\S匹配任何非空白字符。等价于 [^ \f\n\r\t\v。
+如\d+ 匹配前面的子表达式一次或多次。

部分用到的函数:

String trim()返回字符串的副本,忽略前导空白和尾部空白。
static String join(CharSequence delimiter, CharSequence… elements)返回由 CharSequence elements的副本组成的新String,该副本与指定的 delimiter的副本连接在一起。
String[] split(String regex)根据给定正则表达式的匹配拆分此字符串。

代码:

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);
    }
}

时间复杂度O(n),空间复杂度O(n)

方法二:自行编写对应的函数

时间复杂度O(n),空间复杂度O(n)

在这里插入图片描述

class Solution {
    public String reverseWords(String s) {
        //去空格
        StringBuilder sb = trimSpaces(s);
        // 翻转字符串
        reverse(sb, 0, sb.length() - 1);
        // 翻转每个单词
        reverseEachWord(sb);
        
        return sb.toString();
    }

    public StringBuilder trimSpaces(String s) {
        int left = 0, right = s.length() - 1;
        // 去掉字符串开头的空白字符
        while (left <= right && s.charAt(left) == ' ') {
            ++left;
        }

        // 去掉字符串末尾的空白字符
        while (left <= right && s.charAt(right) == ' ') {
            --right;
        }

        // 将字符串间多余的空白字符去除
        StringBuilder sb = new StringBuilder();
        while (left <= right) {
            char c = s.charAt(left);
            if (c != ' ') {
                sb.append(c);
            } else if (sb.charAt(sb.length() - 1) != ' ') {//上个位置不是空格,就可以填
                sb.append(c);
            }
            ++left;
        }
        return sb;
    }

    public void reverse(StringBuilder sb, int left, int right) {
        while (left < right) {
            char tmp = sb.charAt(left);
            sb.setCharAt(left++, sb.charAt(right));
            sb.setCharAt(right--, tmp);
        }
    }

    public void reverseEachWord(StringBuilder sb) {
        int n = sb.length();
        int start = 0, end = 0;

        while (start < n) {
            // 循环至单词的末尾
            while (end < n && sb.charAt(end) != ' ') {
                ++end;
            }
            // 翻转单词
            reverse(sb, start, end - 1);
            // 更新start,去找下一个单词
            start = end + 1;
            ++end;
        }
    }
}
方法三:双端队列

与初步设计思想一致,都是借助额外空间最终实现逆向访问。

利用队列将元素放在头部实现逆序

时间复杂度O(n),空间复杂度O(n)

class Solution {
    public String reverseWords(String s) {
        int left = 0, right = s.length() - 1;
        // 去掉字符串开头的空白字符
        while (left <= right && s.charAt(left) == ' ') {
            ++left;
        }

        // 去掉字符串末尾的空白字符
        while (left <= right && s.charAt(right) == ' ') {
            --right;
        }

        Deque<String> d = new ArrayDeque<String>();
        StringBuilder word = new StringBuilder();
        
        while (left <= right) {
            char c = s.charAt(left);
            if ((word.length() != 0) && (c == ' ')) {
                // 将单词 push 到队列的头部
                d.offerFirst(word.toString());
                word.setLength(0);
            } else if (c != ' ') {
                word.append(c);
            }
            ++left;
        }
        d.offerFirst(word.toString());

        return String.join(" ", d);
    }
}

剑指 Offer 58 - II. 左旋转字符串

初步设计:简单的StringBuffer
class Solution {
    public String reverseLeftWords(String s, int n) {
        StringBuilder sequence = new StringBuilder();
        char[] s1 = s.toCharArray();
        for(int i= n;i<s1.length;i++){
            sequence.append(s1[i]);
        }
         for(int i = 0; i<n ; i++){
            sequence.append(s1[i]);
        }
        return sequence.toString();
    }
}
字符串切片
class Solution {
    public String reverseLeftWords(String s, int n) {
        return s.substring(n, s.length()) + s.substring(0, n);
    }
}
局部反转+整体反转

具体步骤为:

  1. 反转区间为前n的子串
  2. 反转区间为n到末尾的子串
  3. 反转整个字符串
class Solution {
    public String reverseLeftWords(String s, int n) {
        int len=s.length();
        StringBuilder sb=new StringBuilder(s);
        reverseString(sb,0,n-1);
        reverseString(sb,n,len-1);
        return sb.reverse().toString();
    }
     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--;
            }
        }
}

28. 实现 strStr()

class Solution {
    public int strStr(String haystack, String needle) {
        char[] s1 = haystack.toCharArray();
        char[] s2 = needle.toCharArray();
        if(s2.length == 0){
            return 0;
        }
        for(int i = 0;i<s1.length;i++){
            int k = i;
            int j = 0;
            while(k<s1.length && j<s2.length && s1[k] == s2[j] ){
                k++;
                j++;
            }
            if(j == s2.length){
                return i;
            }
        }
        return -1;
    }
}

第二种,一样O(m * n)

class Solution {
    public int strStr(String haystack, String needle) {

        char[] big = haystack.toCharArray();
        char[] small = needle.toCharArray();
        int fastIndex = 0;
        int slowIndex = 0;

        while(fastIndex<big.length){
            if(big[fastIndex] == small[slowIndex]){
                slowIndex++;
                fastIndex++;
                if(slowIndex == small.length){
                    return fastIndex - slowIndex;
                }
            }else{
                fastIndex = fastIndex-slowIndex+1;
                slowIndex = 0;
            }
        }
        return -1;
    }
}

KMP算法

1.前缀表

前缀表要求的就是相同前后缀的长度。

在这里插入图片描述

2.利用前缀表匹配

next数组既可以就是前缀表,也可以是前缀表统一减一(右移一位,初始位置为-1)。
在这里插入图片描述

3.构建next数组
1.初始化
int j = -1;
next[0] = j;
2.处理前后缀不相同情况

找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少。

为什么要前一个字符的前缀表的数值呢,因为要找前面字符串的最长相同的前缀和后缀。所以要看前一位的 前缀表的数值。

前一个字符的前缀表的数值是2, 所以把下标移动到下标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]; // 向前回退
}
3.处理前后缀相同的情况

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

代码如下:

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

最后整体构建next数组的函数代码如下:

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

代码构造next数组的逻辑流程动画如下:
在这里插入图片描述
如果不减一,直接和j比较

    void getNext(int* next, const string& s) {
        int j = 0;
        next[0] = 0;
        for(int i = 1; i < s.size(); i++) {
            while (j > 0 && s[i] != s[j]) { // j要保证大于0,因为下面有取j-1作为数组下标的操作
                j = next[j - 1]; // 注意这里,是要找前一位的对应的回退位置了
            }
            if (s[i] == s[j]) {
                j++;
            }
            next[i] = j;
        }
    }

4.使用next数组来做匹配

i就从0开始,遍历文本串,代码如下:

for (int i = 0; i < s.size(); i++) 

接下来就是 s[i] 与 t[j + 1] (因为j从-1开始的) 进行比较。

如果 s[i] 与 t[j + 1] 不相同,j就要从next数组里寻找下一个匹配的位置。

代码如下:

while(j >= 0 && s[i] != t[j + 1]) {
    j = next[j];
}

如果 s[i] 与 t[j + 1] 相同,那么i 和 j 同时向后移动, 代码如下:

if (s[i] == t[j + 1]) {
    j++; // i的增加在for循环里
}

如何判断在文本串s里出现了模式串t呢,如果j指向了模式串t的末尾,那么就说明模式串t完全匹配文本串s里的某个子串了。

那么使用next数组,用模式串匹配文本串的整体代码如下:

int j = -1; // 因为next数组里记录的起始位置为-1
for (int i = 0; i < s.size(); i++) { // 注意i就从0开始
    while(j >= 0 && s[i] != t[j + 1]) { // 不匹配
        j = next[j]; // j 寻找之前匹配的位置
    }
    if (s[i] == t[j + 1]) { // 匹配,j和i同时向后移动
        j++; // i的增加在for循环里
    }
    if (j == (t.size() - 1) ) { // 文本串s里出现了模式串t
        return (i - t.size() + 1); //模式串出现的第一个位置 (从0开始)
    }
}

在这里插入图片描述

5.前缀表减一全实现
class Solution {

    private int[] getNext(String needle){
        //初始化
        int n = needle.length();
        int[] next = new int[n];
        next[0] = -1;
       //开始构造
        for(int i = 1,j = -1; i  < n; i++){  // 注意i从1开始
            //前后缀不相同了,匹配失败,调整j位置
            while(j >= 0 && needle.charAt(j+1) != needle.charAt(i)){
                j = next[j];  // 向前回退
            }
            //找到相同的前后缀,匹配成功
            if(needle.charAt(j+1) == needle.charAt(i)){
                j++;
            }
            next[i] = j;// 将j(前缀的长度)赋给next[i]
        }
        return next;
    }

    public int strStr(String haystack, String needle) {
        int m = haystack.length();
        int n = needle.length();
        if(n == 0){
            return 0;
        }
        int[] next = getNext(needle);
        // 因为next数组里记录的起始位置为-1,注意i就从0开始
        for(int i = 0, j = -1; i< m ;i++){
            //不匹配
            while(j>= 0 && haystack.charAt(i) != needle.charAt(j+1)){
                j = next[j];// j 寻找之前匹配的位置
            }
            //匹配,i与j同时移动
            if(haystack.charAt(i) == needle.charAt(j+1)){
                j++;
            }
            //判断是否满足全匹配
            if(j == n-1){
                return(i - j);//模式串出现的第一个位置 (从0开始)
            }
        }
        return -1;
    }
}

注意,将这两个字符串的长度m与n存储,在循环和判断时不用再次调用length方法,时间从1ms降为0ms。

合并函数
class Solution {

    public int strStr(String haystack, String needle) {
        int m = haystack.length();
        int n = needle.length();
        if(n == 0){
            return 0;
        }
        //初始化
        int[] next = new int[n];
        next[0] = -1;
       //开始构造
        for(int i = 1,j = -1; i  < n; i++){
            //匹配失败,调整j位置
            while(j >= 0 && needle.charAt(j+1) != needle.charAt(i)){
                j = next[j];
            }
            //匹配成功
            if(needle.charAt(j+1) == needle.charAt(i)){
                j++;
            }
            next[i] = j;
        }

        for(int i = 0, j = -1; i< m ;i++){
            //不匹配
            while(j>= 0 && haystack.charAt(i) != needle.charAt(j+1)){
                j = next[j];
            }
            //匹配,i与j同时移动
            if(haystack.charAt(i) == needle.charAt(j+1)){
                j++;
            }
            //判断是否满足全匹配
            if(j == n-1){
                return(i - j);
            }
        }
        return -1;
    }
}

459. 重复的子字符串

1.暴力解法

子串与父串判断,O(n^2)

class Solution {
    public boolean repeatedSubstringPattern(String s) {
        int m = s.length();
        for(int i = 0; i < m/2; i++){// m/2,判断到中间,再往后整个字符串肯定能构成自己
            if(m%(i+1) == 0){
                //substring(m,n),m代表想要截取的字符串中初始位置的下角标
                //n代表截取至字符串下角标为n的位置(不包括n,m≤n≤str.length)
                String sub = s.substring(0,i+1);
                if(ifSubString(s,sub)){
                    return true;
                }
            }
        }
        return false;
    }

    public boolean ifSubString(String s, String sub){
        int m = s.length();
        int n = sub.length();
        for(int i = 0; i < m ; i++){
            if(s.charAt(i) != sub.charAt(i%n)){
                return false;
            }
        }
        return true;
    }

}

第二种优化,依然是O(n^2)但快不少

class Solution {
    public boolean repeatedSubstringPattern(String s) {
        int n = s.length();
        for (int i = 1; i * 2 <= n; ++i) {
            if (n % i == 0) {
                boolean match = true;
                for (int j = i; j < n; ++j) {
                    if (s.charAt(j) != s.charAt(j - i)) {//每个重复与上一个重复比较!
                        match = false;
                        break;
                    }
                }
                if (match) {
                    return true;
                }
            }
        }
        return false;
    }
}
2.KMP算法

求最大相同前缀后缀next表
在这里插入图片描述

class Solution {
    public boolean repeatedSubstringPattern(String s) {
        //剪枝
        if(s.isEmpty()){
            return false;
        }
        
        //构造next数组
        int length = s.length();
        int[] next = new int[length];
        next[0] = -1;
        char[] chars = s.toCharArray();
        for(int i = 1,j = -1; i  < length; i++){  // 注意i从1开始
            while(j >= 0 && chars[j+1] != chars[i]){
                j = next[j];  // 向前回退
            }
            if(chars[j+1] == chars[i]){
                j++;
            }
            next[i] = j;// 将j(前缀的长度)赋给next[i]
        }

        //最长前后缀
        int max = next[length-1] +1;
        //存在重复情况下的一个周期长度
        int circle = length - max;
        //最后判断是否是重复的子字符串
        if(max > 0 && length % circle == 0){
            return true;
        }
        return false;
    }
}

在这里插入图片描述

注意,构造chars数组,chars[i]比 s.charAt(i) 快很多,从8ms将为4ms

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值