• LeetCode精选200道--字符串篇


    反转字符串

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

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

    你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。

    示例 1:
    输入:[“h”,“e”,“l”,“l”,“o”]
    输出:[“o”,“l”,“l”,“e”,“h”]

    示例 2:
    输入:[“H”,“a”,“n”,“n”,“a”,“h”]
    输出:[“h”,“a”,“n”,“n”,“a”,“H”]

    思路

    反转链表的时候我们可以用双指针,那么反转字符串呢?反转字符串依然是使用双指针的方法,只不过对于字符串的反转,其实要比链表简单一些。

    字符串也是一种数组,所以元素在内存中是连续分布,这就决定了反转链表和反转字符串方式上还是有所差异的。

    这种还是比较容易想到的

    public void reverseString(char[] s) {
    
        for (int i = 0, j = s.length - 1; i < s.length / 2; i++, j--) {
            char a = s[i];
            s[i] = s[j];
            s[j] = a;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    第二种方法:

    通过异或运算

    字符串计算的时候其实也是通过转化为二进制计算的

    每个字母对应着自己的ASCII码

    public void reverseString(char[] s) {
    	int l = 0;
    	int r = s.length - 1;
    	while(r > l){
    		s[l] ^= s[r];
    		s[r] ^= s[l];
            s[l] ^= s[r];
            l++;
            r--;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    异或运算的用途:

    1)翻转指定位

    比如将数 X=1010 1110 的低4位进行翻转,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行异或运算(X^Y=1010 0001)即可得到。

    2)与0相异或值不变

    例如:1010 1110 ^ 0000 0000 = 1010 1110

    3)交换两个数

    实例

    void Swap(int &a, int &b){
    if (a != b){
    a ^= b;
    b ^= a;
    a ^= b;
    }
    }

    如下图示:

    image-20220802114950615

    反转字符串II

    给定一个字符串 s 和一个整数 k,你需要对从字符串开头算起的每隔 2k 个字符的前 k 个字符进行反转。

    如果剩余字符少于 k 个,则将剩余字符全部反转。

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

    题目的意思就是说:反转k个,下k个不反转,如此反复

    示例:

    输入: s = “abcdefg”, k = 2
    输出: “bacdfeg”

    思路

    道题目其实也是模拟,实现题目中规定的反转规则就可以了。

    一些同学可能为了处理逻辑:每隔2k个字符的前k的字符,写了一堆逻辑代码或者再搞一个计数器,来统计2k,再统计前k个字符。

    其实在遍历字符串的过程中,只要让 i += (2 * k),i 每次移动 2 * k 就可以了,然后判断是否需要有反转的区间。

    因为要找的也就是每2 * k 区间的起点,这样写,程序会高效很多。

    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){
                    ch[start] ^= ch[end];
                    ch[end] ^= ch[start];
                    ch[start] ^= ch[end];
                    start++;
                    end--;
                }
            }
            return new String(ch);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    剑指Offer 05.替换空格

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

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

    思路1:双指针

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

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

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

    替换空格

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    思路2:复制字符串,替换空格

    一眼就看懂了,没什么说的…

        public static String replaceSpace(String str){
            if (str == null){
                return null;
            }
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < str.length(); i++) {
                if (str.charAt(i) == ' '){
                    sb.append("%20");
                }else {
                    sb.append(str.charAt(i));
                }
            }
            return sb.toString();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    翻转字符串里的单词

    给你一个字符串 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”
    解释:如果两个单词间有多余的空格,颠倒后的字符串需要将单词间的空格减少到仅有一个。

    思路

    package com.caq.string;
    
    import java.util.Arrays;
    
    public class ReverseString3 {
    
        public static void main(String[] args) {
            String s = "hello  world !  ";
            ReverseString3 re = new ReverseString3();
            String s3 = re.reverseWors(s);
            System.out.println(s3);
        }
    
        /**
         * 1.去除首尾以及中间多余空格
         * 2.反转整个字符串
         * 3.反转单词
         */
        public String reverseWors(String s) {
            // 1.去除首尾以及中间多余空格
            StringBuilder sb = removeSpace(s);
            // 2.反转整个字符串
            reverseString(sb, 0, sb.length() - 1);
            // 3.反转各个单词
            reverseEachWord(sb);
            return sb.toString();
        }
    
        /**
         * 移除空格
         *
         * @param s
         * @return
         */
        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);
                if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') {
                    sb.append(c);
                }
                start++;
            }
            return sb;
        }
    
        /**
         * 反转字符串
         * 反转数组就是最基本的双指针
         * 这里反转字符串借助StringBuilder完成
         */
        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--;
            }
        }
    
        /**
         * 反转字母
         * 怎么反转的呢,一组一组的去反转
         *
         * @param sb
         */
        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++;
                }
                //对字母进行再次反转
                //这里的end-1是因为end的索引是从1开始的,不减1到后面会越界
                reverseString(sb, start, end - 1);
                /**
                 * 注意这里是意思是反转完第一组单词后,进行第二组单词的反转
                 * 此时要给start指针和end指针重新赋值,start指针的赋值为end+1,也就是跳过了空字符串的情况
                 * 上一步经过反转的end指针的位置是end-1
                 */
                start = end + 1;
                end = start + 1;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93

    剑指Offer58-II.左旋转字符串

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

    示例 1:
    输入: s = “abcdefg”, k = 2
    输出: “cdefgab”

    示例 2:
    输入: s = “lrloseumgh”, k = 6
    输出: “umghlrlose”

    限制:
    1 <= k < s.length <= 10000

    思路

    对字符串进行三次旋转即可!

    字符串旋转还是用StringBuilder来完成

    太经典了,理解记忆

    while (start < end) {
        char temp = sb.charAt(start);
        sb.setCharAt(start, sb.charAt(end));
        sb.setCharAt(end, temp);
        start++;
        end--;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    image-20220805090832140

    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--;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    实现 strStr()

    实现 strStr() 函数。

    给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。

    示例 1: 输入: haystack = “hello”, needle = “ll” 输出: 2

    示例 2: 输入: haystack = “aaaaa”, needle = “bba” 输出: -1

    说明: 当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。 对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。

    思路

    本题是KMP 经典题目

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

    什么是KMP

    因为是由这三位学者发明的:Knuth,Morris和Pratt,所以取了三位学者名字的首字母。所以叫做KMP

    KMP有什么用

    KMP主要应用在字符串匹配上。

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

    所以如何记录已经匹配的文本内容,是KMP的重点,也是next数组肩负的重任。

    什么是前缀表

    写过KMP的同学,一定都写过next数组,那么这个next数组究竟是个啥呢?

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

    前缀表有什么作用呢?

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

    为了清楚的了解前缀表的来历,我们来举一个例子:

    要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。

    请记住文本串和模式串的作用,对于理解下文很重要,要不然容易看懵。所以说三遍:

    要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。

    要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。

    要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。

    KMP精讲1

    可以看出,文本串中第六个字符b 和 模式串的第六个字符f,不匹配了。如果暴力匹配,会发现不匹配,此时就要从头匹配了。

    但如果使用前缀表,就不会从头匹配,而是从上次已经匹配的内容开始匹配,找到了模式串中第三个字符b继续开始匹配。

    前缀表是如何记录的呢?

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

    那么什么是前缀表:记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。

    最长相同前后缀

    就是前缀和后缀中最长相等的字符串

    前缀表计算

    就是给你一个字符串,然后你计算它们每个模式串的最长公共前后缀

    例如:

    长度为前1个字符的子串a最长相同前后缀的长度为0。

    image-20220807101221952

    模式串aa最长相同前后缀的长度为1。

    image-20220807101228797

    模式串aab最长相同前后缀的长度为0。

    image-20220807101332515

    以此类推: 长度为前4个字符的子串aaba,最长相同前后缀的长度为1。 长度为前5个字符的子串aabaa,最长相同前后缀的长度为2。 长度为前6个字符的子串aabaaf,最长相同前后缀的长度为0。

    那么把求得的最长相同前后缀的长度就是对应前缀表的元素,如图:

    image-20220807101452365

    可以看出模式串与前缀表对应位置的数字表示的就是:下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。

    再来看一下如何利用 前缀表找到 当字符不匹配的时候应该指针应该移动的位置。如动画所示:

    KMP精讲2

    代码实现

  • 相关阅读:
    讲讲关于Precision 与 Recall 的概念
    Django+Celery框架自动化定时任务开发
    原生Js 提取视频中的音频
    IDEA spring-boot项目启动,无法加载或找到启动类问题解决
    拼多多客服回复话术技巧
    Codeforces Round #802(Div. 2)A~D
    微服务·架构组件之服务注册与发现-Nacos
    WebSocket的简单应用
    iOS原生、Android 原生, flutter 三种方式给照片流添加文字(水印)
    经济发展由新技术推动着来
  • 原文地址:https://blog.csdn.net/qq_45714272/article/details/126222691