代码随想录--字符串习题总结

代码随想录–字符串习题总结

1.LeetCode344 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

示例 1:
输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]

解题思路: 直接使用双指针,分别从数组的第一个和最后一个元素进行遍历,然后交换这两个元素即可。

public void reverseString(char[] s) {
    int left = 0, right = s.length - 1;
    while (left < right) {
        char temp = s[left];
        s[left++] = s[right];
        s[right--] = temp;
    }
}

2.LeetCode541 反转字符串II

给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。

如果剩余字符少于 k 个,则将剩余字符全部反转。
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

示例 1:
输入:s = "abcdefg", k = 2
输出:"bacdfeg"
示例 2:
输入:s = "abcd", k = 2
输出:"bacd"

解题思路: 这个题不涉及什么算法,只是复杂逻辑的模拟。

  • 首先要明确的是,这道题中遍历字符串,是以2k个单位进行遍历的。
  • 我们写一个循环,来遍历整个字符串,进入循环后首先需要计算剩余元素的个数s.length() - i,后续我们会根据剩余元素的个数来判断是否执行不同的逻辑
  • 如果剩余元素的个数是>=2k的,那么之间反转前k个元素即可reverseStr(chars, i, i + k - 1), 然后i移动,遍历下一个2k区间
  • 如果剩余元素个数位于[k,2k)区间内,则依然遍历前k个元素即可reverseStr(chars, i, i + k - 1),只不过直接break掉循环就可以了,因为后面也不可能在凑够2k个元素了,所以循环到这里直接终止就可以了
  • 如果剩余元素<k,则直接将剩余的所有元素反转即可reverseStr(chars, i, chars.length - 1),同理反转完成后也可以直接break掉
public static String reverseStr(String s, int k) {
    char[] chars = s.toCharArray();
    // 开始循环遍历字符串
    for (int i = 0; i < s.length() - 1;) {
        // 计算当前剩余元素个数
        int count = s.length() - i;
        // 判断是否满足2k的条件
        if (2 * k <= count) {
            // 反转前k个元素
            // TODO 反转
            reverseStr(chars, i, i + k - 1);
            i = i + 2 * k;
        } else if (count < 2 * k && count >= k) {
            // TODO 反转前k个
            reverseStr(chars, i, i + k - 1);
            break;
        } else if (count < k) {
            // TODO 反转剩余元素
            reverseStr(chars, i, chars.length - 1);
            break;
        }
    }
    return new StringBuffer().append(chars).toString();
}

private static void reverseStr(char[] chars, int start, int end) {
    while (start < end) {
        char temp = chars[start];
        chars[start++] = chars[end];
        chars[end--] = temp;
    }
}

image-20230120160851580

这个题还需要注意Java中String和char[]的互相转换

String 转 char[]

char[] chars = str.toCharArray();

char[] 转 String 需要借助StringBuilder

String str = new StringBuffer().append(chars).toString();

3. 剑指offer05 替换空格

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

示例 1:
输入:s = "We are happy."
输出:"We%20are%20happy."

解题思路1: 直接使用StringBuilder,遍历数组,只要发现有空格,就向StringBuilder中append%20,否则就把原来字符串中字符append。

public static String replaceSpace(String s) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < s.length(); i++) {
        // 这个地方比较的是字符相等不相等
        // if (" ".equals(s.charAt(i)))
        if (s.charAt(i) == ' ') {
            sb.append("%20");
            continue;
        }
        sb.append(s.charAt(i));
    }
    return sb.toString();
}

这个地方需要注意这个条件" ".equals(s.charAt(i))使用charAt(i)函数返回的是一个char,因此应该用==而不是equal方法

解题思路2: 使用双指针

  • 首先遍历整个字符串,统计空格个数
  • 然后按照空格个数,将字符串进行扩容
  • 然后i,j指针分别指向扩容前数组最后的元素,和扩容后数组的元素
  • 然后开始遍历,如果s[i]不是空格,则s[j] = s[i]
  • 如果是空格,则s[i] = '0';s[i - 1] = '2';s[i - 2] = '%';
  • 直到i==j停止循环。

image-20230120164523735

因为在Java中字符串是一个常量,我们没有办法像C++一样直接修改字符串中的某个字符,因此在Java语言中,这种方法实际上要比思路1时间复杂度更高一些。并不推荐使用,但是在C++中是可以的。下面给出C++和Java使用双指针解决这道题的代码。

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

4.Leetcode151 反转单词

给你一个字符串 s ,请你反转字符串中 单词 的顺序。

单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。

返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。

注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。

示例 1:
输入:s = "the sky is blue"
输出:"blue is sky the"
示例 2:
输入:s = "  hello world  "
输出:"world hello"
解释:反转后的字符串中不能存在前导空格和尾随空格。
示例 3:
输入:s = "a good   example"
输出:"example good a"
解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。

解题思路1

  • 使用trim函数取出字符串开头和结尾的空格
  • 使用split分割单词,形成一个string[], 每一个元素是一个单词
  • 然后反转string[]数组即可。
public String reverseWords(String s) {
    // 如果一个字符串中有多个连续的空格
    // trim()的作用
    String[] strings = s.trim().split(" ");
    int left = 0, right = strings.length - 1;
    // 反转数组
    while (left < right) {
        String temp = strings[left];
        strings[left++] = strings[right];
        strings[right--] = temp;
    }
    // 构建字符串
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < strings.length; i++) {
        if ("".equals(strings[i]))
            continue;
        if (i == strings.length - 1) {
            sb.append(strings[i]);
            continue;
        }
        sb.append(strings[i] + " ");
    }
    return sb.toString();
}

这里需要特别注意的是,如果String str = " hello world ";,那么使用split(" ")之后的得到的数组是:

image-20230120174654062

所以需要在添加到StringBuilder中忽略掉这些空字符串。

第二个需要注意的是,如果想去除掉字符串开头和结尾的空格,可以使用str.trim()函数

解题思路2: 将字符串先整体反转一次,然后在每一个单词内部反转一次,就可以得到最终的结果。这个地方的难点是因为给的字符串可能在开头和结尾都包含若干个空格,以及每一个单词之间还有可能存在多个空格,所以需要删除掉字符串中额外的空格。

public String reverseWords(String s) {
    // 去掉字符串中额外的空格
    String removeSpaces = removeSpaces(s);
    // 整个字符串反转一次
    char[] chars = removeSpaces.toCharArray();
    reverse(chars, 0, chars.length - 1);
    // 每个单词内部反转
    int rec = 0; // 记录单词的左边界
    for (int i = 0; i <= chars.length; i++) {
        if (i == chars.length || chars[i] == ' ') {
            // 第一个单词遍历完毕
            reverse(chars, rec, i - 1);
            rec = i + 1;
        }
    }
    return new StringBuilder().append(chars).toString();
}

private String removeSpaces(String s) {
    char[] chars = s.toCharArray();
    int slow = 0;
    for (int fast = 0; fast < chars.length; fast++) {
        if (chars[fast] != ' ') {
            // 是单词
            // 判断slow 是不是在初始位置,
            // 如果不在初始位置,则需要在前面加空格
            if (slow != 0) {
                chars[slow++] = ' ';
            }
            // while循环将fast指向的单词添加到slow中
            // 只要chars[fast] != ' ' 那么说明fast一直指向的都是这个单词
            while (fast < chars.length && chars[fast] != ' ') {
                chars[slow++] = chars[fast++];
            }
        }
    }
    return new StringBuilder().append(chars, 0, slow).toString();
}

/**
     * 反转字符串
     * @param chars
     * @param start
     * @param end
     */
private void reverse(char[] chars, int start, int end) {
    while (start < end) {
        char temp = chars[start];
        chars[start++] = chars[end];
        chars[end--] = temp;
    }
}

image-20230120202653860

for (int i = 0; i < chars.length; i++) {
 if (chars[i] == ' ') {
     // 第一个单词遍历完毕
     reverse(chars, rec, i - 1);
     rec = i + 1;
 }
}

需要注意的是,在进行单词内部反转的时候,我们在条件上不能写上面的这种。判断只要有空格,那么就反转之间的单词。如果这样写,就会发现最后一个单词实际上没有反转。

原因是最后一个单词后面没有空格,但是也要反转。下面的这种写法才对。当执行到最后一个元素的时候,也要反转。

为什么是i<=chars.length,是因为要反转的区间是[rec, i - 1] 所以i必须执行到=chars.length

for (int i = 0; i <= chars.length; i++) {
 if (i == chars.length || chars[i] == ' ') {
     // 第一个单词遍历完毕
     reverse(chars, rec, i - 1);
     rec = i + 1;
 }
}

5.剑指Offer58 左旋转字符串

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。

示例 1:
输入: s = "abcdefg", k = 2
输出: "cdefgab"
示例 2:
输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"

解题思路1: 进行两次字符串的反转即可

  • 首先将整个字符串进行反转
  • 然后再以n为界限,分割成两个子字符串,对这两个子字符串分别进行反转,得到最终结果。
public String reverseLeftWords(String s, int n) {
    // 先完全反转一次
    char[] chars = s.toCharArray();
    reverse(chars, 0, chars.length - 1);
    // 然后再左右分别反转一次
    reverse(chars, 0, chars.length - 1 - n);
    reverse(chars, chars.length - n, chars.length - 1);
    return new StringBuilder().append(chars).toString();
}
private void reverse(char[] chars, int start, int end) {
    while (start < end) {
        char temp = chars[start];
        chars[start++] = chars[end];
        chars[end--] = temp;
    }
}

图解如下:

image-20230120211411593

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值