【刷题笔记04】- 字符串

刷题笔记系列

【刷题笔记01】- 数组
【刷题笔记02】-链表
【刷题笔记03 -哈希表】



1、LeetCode541 反转字符串II

给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
如果剩余字符少于 k 个,则将剩余字符全部反转。
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

public class Solution {
    public string ReverseStr(string s, int k) {
        char[] str = s.ToCharArray();
        for(int i = 0; i < str.Length; i+=(2*k)){
            //说明剩余字符大于k个
            if(i+k < str.Length){
                Reverse(str,i,i+k-1);
            }else{//剩余字符小于k个
                Reverse(str,i,str.Length-1);
            }
        }
        return new string(str);
    }

    //反转范围[start,end]
    public char[] Reverse(char[] s,int start,int end){
        for(int i = start,j = end;i < j;i++,j--){
            s[i] ^= s[j];
            s[j] ^= s[i];
            s[i] ^= s[j];  
        }
        return s;
    }
}

2、剑指 Offer 05. 替 换空格

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
代码随想录中有一个解法是先遍历一遍string,记录下空格数以此给数组扩容。再用双指针法从后往前再次遍历string,根据当前左指针指向的字符决定右指针的对应赋值和移动。

这个解法在C和C++中是比较高效的,因为这两个语言直接s[i]就能对原字符串进行处理。而java和C#则需要更高的内存消耗,java无法直接在原string上取值赋值,要转成char数组;C#可以通过索引器取值,用s[i]从string中获取到索引对应的char,但是这个属性是只读的,还是需要一个char数组来改值。
遍历了两次+额外的char数组开销,所以在C#和java中这个方法效率不如直接Stringbuilder遍历一遍来得高。

3、反转字符串中的单词

在这里插入图片描述

4、找出字符串中第一个匹配项的下标( KMP算法实现

LeetCode28 找出字符串中第一个匹配项的下标

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。

这个题是KMP算法的经典应用,代码如下

class Solution {
    public int strStr(String haystack, String needle) {
        int[] next = getNext(needle);
        int j = 0;
        for(int i = 0; i < haystack.length(); i++){
            while(j > 0 && haystack.charAt(i) != needle.charAt(j)){
                j = next[j-1];
            }
            if(haystack.charAt(i) == needle.charAt(j))
                j++;
            if(j == needle.length())
                return i - j + 1;
        }
        return -1;
    }

    public int[] getNext(String needle){
        int[] next = new int[needle.length()];
        next[0] = 0;
        int j = 0;//j表示前缀末尾,i表示后缀末尾
        for(int i = 1; i < needle.length(); i++){
            while( j > 0 && needle.charAt(j) != needle.charAt(i)){
                j = next[j-1];
            }
            if(needle.charAt(j) == needle.charAt(i))
                j++;
            next[i] = j;
        }
        return next;
    }
}

有几个点:

  1. 我们会如何去计算next数组呢?
    ①假设 j 指向前缀末尾,i 指向后缀末尾。初始化next数组,next[0] = 0
    ②在遍历中[i, length-1] 进行字符比较并更新next[j]数组
    比较的规则:
    当 needle.charAt(j) != needle.charAt(i) 时 j 一直回退,直到 i 和 j 指向的字符相等或者 j 退无可退已为0
    若needle.charAt(j) == needle.charAt(i) 则 j++
    更新数组:
    经过上面的处理后,已经能得到当前 i 位置对应的 最长相等前后缀的长度了,即为 j
 public int[] getNext(String needle){
        int[] next = new int[needle.length()];
        next[0] = 0;
        int j = 0;//j表示前缀末尾,i表示后缀末尾
        for(int i = 1; i < needle.length(); i++){
            while( j > 0 && needle.charAt(j) != needle.charAt(i)){
                j = next[j-1];
            }
            if(needle.charAt(j) == needle.charAt(i))
                j++;
            next[i] = j;
        }
        return next;
    }
  1. 为什么KMP算法的next数组可以达到减少次数的作用?
    KMP算法中我们用next[ j ]数组记录模式串的规律,在匹配比较过程中根据next数组记录找到模式串指针应该回退的地方,回退后继续匹配而不是从第一个字符开始重新匹配。
    其中起重要作用的就是next[ j ]数组。
    next[ j ]数组记录的“规律”—— j 之前的子串的最长相等前后缀的长度(前缀是不包含尾字母一定包含首字母的子串,后缀是不包含首字母,一定包含尾字母的子串),我们实际上是根据这个记录以等价替换原则来得到状态推理,从而减少字符串匹配次数。

    假设匹配串由子串 A.A…CAAB组成,模式串由A’A’‘B组成。
    通过求得next数组得到分析结果后,我们知道A’和A’‘相等。
    那么当匹配目标串 A.A…C(AAB) — 模式串A’A’‘B 时,
    因为C和B不相等我们需要回退(但是匹配到此处说明A.A…C中的A.A…与 A’A’‘B中的A’A’‘相等)
    因为A’’ = A… ,A’ = A’’ => A’ = A… ,模式串就无需再比较A’, 而是从A’'开始与目标串的C比较。

  2. 为什么目标串和模式串的匹配过程和 模式串求next数组的过程如此相似?
    因为我们也可以把模式串求next数组 看成 在模式串中拿等长前后缀去做字符匹配,匹配成功就是我们的next值。所以我们会发现在求next数组的过程中就已经用上了上一个next数组值来减少比较次数。因为当前若不匹配,那就按最长相等前后缀找回退的位置去匹配,这其实就KMP的核心。
    而目标串和模式串匹配就是在两个串中做字符匹配。所以串匹配时i的下标是从0开始的,而求next数组,i是从1开始(因为next[0] = 0,是动态规划的初始化条件,也是递归的终止条件)


总结

KMP算法值得反复回味。看起来很复杂,但是代码又很精炼。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值