0
点赞
收藏
分享

微信扫一扫

[LeetCode]-4-字符串

橙子好吃吗 2022-03-12 阅读 30

LeetCode-字符串

344.反转字符串-头尾双指针

简单的头尾双指针,初始化一个头指针指向0号元素,尾指针指向最后一个元素,每次循环交换两个指针指向的位置然后头指针向右移尾指针向左移重复循环,直到两个指针相遇,就反转结束

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

541.反转字符串2

顺着344题的思路然后进行模拟

public String reverseStr(String s, int k) {
    if(s == null) return "";
    char[] chars = s.toCharArray();
    int length = s.length();
    int kk = k << 1;
    int i = 0;
    int j;
    while (i < length){
        j = i + kk - 1;
        //根据题意设计分支条件
        if(j < length){
            reverseString(chars,i,i + k - 1);
        }else{
            int rest = length - i;
            if(rest >= k){
                reverseString(chars,i,i + k - 1);
            }else{
                reverseString(chars,i,length - 1);
            }
            break;
        }
        i = j + 1;
    }
    //由于toString()方法不是单纯把字符数组变成字符串,还会携带中括号以及逗号,空格,所以要替换掉
    return Arrays.toString(chars).replace("[","")
                                 .replace("]","")
                                 .replace(" ","")
                                 .replace(",","");
}
public void reverseString(char[] s,int begin,int end) {
    while (begin < end){
        char temp = s[begin];
        s[begin] = s[end];
        s[end] = temp;
        begin++;
        end--;
    }
}

然而,根据字符数组得到对应的字符串有更为方便和直接的方法就是在String构造函数中直接传入字符数组,所以最后的return语句应该改为:

return new String(chars);

151.翻转字符串里面的单词

直接想到的做法,就是遍历字符串,找出所有单词,然后逆序重组成题目要的新字符串

public String reverseWords(String s) {
    int length = s.length();
    List<String> list = new ArrayList<>();
    for (int i = 0; i < length; i++) {
        if(s.charAt(i) != ' '){
            int j = i;
            //找到以i为开头的单词的结尾
            while (j + 1 < length && s.charAt(j + 1) != ' '){
                j++;
            }
            list.add(s.substring(i,j + 1));
            i = j + 1;
        }
    }
    StringBuilder res = new StringBuilder();
    int size = list.size();
    //因为需要加上空格,字符串最后又不能有空格,所以加空格的操作不能放在循环中加单词后进行,
    //这里先把最后一个单词放进去,然后再循环放单词前先加空格
    if(size > 0){
        res.append(list.get(size - 1));
    }
    for (int i = size - 2; i >= 0; i--) {
        res.append(" ").append(list.get(i));
    }
    return res.toString();
}

还了解到了另一个思路:先移除所有空格,再翻转整个字符串,再翻转每个单词。多利用翻转的思想,实现常数级空间复杂度

28.实现strStr()-KMP算法

其实return haystack.indexOf(needle)就可以过题了,但意义肯定不是这样。所以就学了KMP来解这道题。
学习这个算法首先要知道这个算法好在哪里:我们知道匹配文本串haystack跟模式串needle,肯定需要去遍历两个字符串,使用暴力法去匹配的话做法可能就是在文本串中从i=0开始判断[i,i + needle.length() - 1]与needle是否相同,不相同的话i+1,但其实[i,i + needle.length() - 1]中可能会存在某部分的后缀是有效的,并没有被利用起来,而是从新的i开始一个个字符判断。所以KMP处理了这个问题,把每次匹配失败的字符串的有效部分以某种形式保存下来(就是代码中的next数组)。而且文本串的所有元素只可能会被遍历一次。
整个算法的难点应该集中在next数组如何生成。关于next数组的生成以及使用next数组完成整个匹配过程的解析在下面代码中的注释:

public int strStr(String haystack, String needle) {
    //如果模式串是空串直接返回0
    if(needle.length() == 0){
        return 0;
    }
    int j = -1;
    int[] next = getNext(needle);
    int i;
    int needleLength = needle.length();
    for (i = 0; i < haystack.length() && j != needleLength - 1;) {
        //next保存的是前一个前缀的下标,在匹配文本串和模式串时出现不相符的情况时要回退到前一个相符的字符串的前缀然后重新匹配,
        //此时跳回去的next[j]是符合条件的有效的字段,所以要从next[j]+1开始与文本串匹配,所以为了统一成匹配j + 1的格式,j从-1开始
        if(haystack.charAt(i) == needle.charAt(j + 1)){
            i++;
            j++;
        }else if(haystack.charAt(i) != needle.charAt(j + 1) && j != -1){
            //如果当前文本串的字符与模式串的下一个字符不相同,模式串就要回退到上次记录到的前缀,
            //也就是KMP的算法思想,将之前检索过的有效部分保留下来
            j = next[j];
        }else {
            //如果既不匹配,也没有前缀可以回退(j == -1),那就只能移动i,在文本串中找下一个字符进行匹配
            i++;
        }
    }
    if(j == needleLength - 1){
        //由for循环中的第一个if可知,最后一个字符匹配成功后i也会++,所以要减一
        return i - 1 - needleLength + 1;
    }
    return -1;
}
//生成模式串的next数组。前缀表指的是最长相等前缀和后缀的长度,如果长度减一那么得到的就是前缀最后一个字符的下标,
//更适合在代码中利用,因此以前缀表的数值减一作为next数组的元素值
public int[] getNext(String needle){
    int length = needle.length();
    int[] next = new int[length];
    //初始化next[0]值为-1,因为0在前缀表的值为0
    next[0] = -1;
    //next[0]已经赋值所以从next[1]开始计算
    for (int i = 1; i < length; i++) {
        //如果i对应字符与[0,i-1]子串的最长前缀的下一个字符相同,那么[0,i-1]子串的最长前缀加上其下一个字符就是[0.i]子串的最长前缀
        if(needle.charAt(i) == needle.charAt(next[i - 1] + 1)){
            next[i] = next[i - 1] + 1;
        }else{
            //如果i对应字符与[0,i-1]子串的最长前缀的下一个字符不相同,我们就要往前找更短的最长相等前缀,而且同时还要保证我们找的前缀在[0,i-1]子串中有对应的相等后缀,
            //为了这一点,k的取值是k = next[k],而不是k = next[k-1]。也就是说,对于[0,i]这个子串,我们要找到和 [0.i-1]这个子串的某个后缀和i指向的字符构成的新的后缀
            //相等的前缀,那么这个前缀的最后一个字符就和i指向的字符相同,剩下的部分和那个新的后缀中的[0.i-1]这个子串的后缀相同,为了这个条件,我们找的[0,i-1]的前缀必须是
            //之前出现的最长相等前缀,所以k = next[k]
            int k = next[i - 1];
            //如果找到某个之前出现过的前缀的下一个字符与i字符相同,那么这个前缀就和[0,i-1]的某个后缀相同,这个前缀的下一个字符就和i字符相同,那么这个前缀和他的下一个字符
            //构成的就符合[o,i]子串的最长相等前缀,根据next数组存放的是最长相等前缀的最后一个字符的下标,next[i]存放的就应该是k + 1
            while (k != -1 && needle.charAt(k + 1) != needle.charAt(i)){
                k = next[k];
            }
            //如果找不到那样的前缀,说明没有最长相等前缀存在,next[i]自然就为-1;不过要注意,当k回退到-1时,要看看0字符跟i字符是否相等,相等那next[i]应该等于0,也就还是k + 1。如"ababcaabc"
            if(k == -1 && needle.charAt(0) != needle.charAt(i)){
                next[i] = -1;
            }else {
                next[i] = k + 1;
            }
        }
    }
    return next;
}

459.重复的子字符串-KMP

尝试计算一下存在重复子字符串的字符串的前缀表(或next数组),会发现有一定规律。下面代码中的next数组是前缀表不是前缀表的值减一。也就是next数组的值是最长相等前缀的长度而不是前缀中最后一个字符的下标

public boolean repeatedSubstringPattern(String s) {
    int length = s.length();
    int[] next = new int[length];
    next[0] = 0;
    for (int i = 1; i < length; i++) {
        if(s.charAt(i) == s.charAt(next[i - 1])){
            next[i] = next[i - 1] + 1;
        }else{
            int k = next[i - 1];
            while (k != 0 && s.charAt(k) != s.charAt(i)){
                k = next[k - 1];
            }
            if(k == 0 && s.charAt(0) != s.charAt(i)){
                next[i] = 0;
            }else {
                next[i] = k + 1;
            }
        }
    }
    //如果字符串存在最长相等前缀且存在重复的子字符串,那么这个前缀肯定是由这个字符串重复构成,那么整个字符串减去这个前缀后剩下的部分也就是这个重复的子字符串,只要判断整个字符串的长度和剩下的部分的长度是否是倍数关系就可以了
    //如果最后一个字符没有最长相等前缀那肯定不存在重复的子字符串,但取余0肯定会得到0,所以要排除掉这种情况
    if(next[length - 1] != 0 &&length % (length - next[length - 1]) == 0){
        return true;
    }
    return false;
}

8.字符串转换整数(atoi)-模拟+long最大值

首先:long的范围为-9223372036854775808~9223372036854775807
根据题意以及案例模拟就可以了。要注意的点,题目说到计算结果可能会超出int的范围,所以在保存运算的中间结果时先用long类型的res变量来保存,对字符串处理完毕后对res判断是否超出int范围即可;还有一个点就是计算过程还可能超出long的范围,比如输入"9223372036854775808"这个样例,所以对每个原酸得到的中间变量都可以判断是否超出int范围,超出就不用继续往下算了,直接可以判断后返回结果,也就不用担心后面在超出long的范围了

public int myAtoi(String s) {
    int length = s.length();
    if(length == 0) return 0;
    int index = 0;
    //去除前导空格,注意不能用" ".equals(s.charAt(index))来判断
    while(index < length && ' ' == (s.charAt(index))){
        index++;
    }
    //去除空格后字符串就结尾了,返回0
    if(index == length) return 0;
    //检查下一个字符符号,是负号则结果应为负号;其他符号的话结果都为正号
    //flag置为0表示负号,1表示正号
    int flag;
    if(s.charAt(index) == '-') flag = 0;
    else flag = 1;
    //开始读入字符
    long res = 0L;
    int temp = index;
    //如果上一步检查到的符号是正号或负号,那就要从下一个字符开始读取数字;如果是数字或其他字符就直接从这一个字符开始读
    if(s.charAt(temp) == '-' || s.charAt(temp) == '+') temp++;
    while(temp < length){
        char c = s.charAt(temp);
        if(c >= '0' && c <= '9'){
            res *= 10;
            res += Integer.parseInt(String.valueOf(c));
            //已经溢出int的范围不用继续计算
            if(res > Integer.MAX_VALUE || res < Integer.MIN_VALUE) break;
            temp ++;
        }else{
        	//遇到非数字字符直接结束
            break;
        }
    }
    res = flag == 0 ? -res : res;
    if(res > Integer.MAX_VALUE) return Integer.MAX_VALUE;
    else if(res < Integer.MIN_VALUE) return Integer.MIN_VALUE;
    return (int)res;
}
举报

相关推荐

0 条评论