代码随想录刷题4 「字符串」

代码随想录刷题4 「字符串」

1. 字符串翻转相关

  1. 字符串翻转相关问题主要涉及字符串整体的翻转(Leetcode 344),字符串间隔分组翻转(Leetcode 541),字符串中单词词序翻转(Leetcode 151),以及字符串的旋转(Leetcode 剑指Offer58-II)。

  2. 经典题目:Leetcode 344 。建议不直接使用库函数,可以采用前后双指针法,交换字符直至相遇。对于交换过程的操作既可以采用temp储存法,也可以采用异或法。利用异或的性质:a ^ (a ^ b) = b,进行位运算,避免引入temp,其原理:a ^ (a ^ b) = (a ^ a) ^ b,又因为a ^ a = 0,原式可以简化成0 ^ b = b,但异或法除了装逼没有其他用更耗时。

    	//假设交换前s[i] = a, s[j] = b
    	s[i] ^= s[j];//此时s[i] = a ^ b, s[j] = b
        s[j] ^= s[i];//此时s[i] = a ^ b, s[j] = a
        s[i] ^= s[j];//此时s[i] = b, s[j] = a, 完成交换
    
    //Leetcode344    
    	//时间复杂度:O(n)。
    	//空间复杂度:O(1)。
    	public void reverseString(char[] s) {
            int i = 0;
            int j = s.length - 1;
            //利用异或的性质a^(a^b) = b,进行位运算,避免引入temp
            //推导:a^(a^b)=(a^a)^b,又因为a^a=0,原式可以简化成0^b=b。
            while(i < j){
                //假设交换前s[i] = a, s[j] = b
                s[i] ^= s[j];//此时s[i] = a ^ b, s[j] = b
                s[j] ^= s[i];//此时s[i] = a ^ b, s[j] = a
                s[i] ^= s[j];//此时s[i] = b, s[j] = a, 完成交换
    
                i++;
                j--; 
            }
        }
    
  3. 经典题目:Leetcode 541。需要做到每2k个字符一组,翻转组内前k个字符。做最后一组不足k,则全部翻转。只需要将边界移动周期设定为2k即可,注意最后一次交换的特殊情况。

    //Leetcode541    
    	//时间复杂度:O(n)。
    	//空间复杂度:O(n)。
    	public String reverseStr(String s, int k) {
            int n = s.length();
            char[] cS = s.toCharArray();
            int left = 0;
            int right = k - 1;
            int i, j;
            char temp;
            while (left < n){
                i = left;
                j = Math.min(right, n - 1);
                while(i < j){
                    //change
                    temp = cS[i];
                    cS[i++] = cS[j];
                    cS[j--] = temp;    
                }
                left += 2 * k;
                right += 2 * k;
            }        
            return new String(cS);
        }
    
  4. 经典题目:Leetcode 151。将字符串中单词词序翻转,但是不翻转一个单词中的字母序,因此可以在去除前后空格之后,先将整个字符串翻转(Leetcode344),再将每个单词翻转。或采取模拟的方法,新建一个长度为去除掉首尾空格和内部重复空格后的res数组用于存放最终答案。从前向后遍历原始字符数组,将每个单词从后向前地存放如新的res数组。注意:填充res时,从后向前填充单词,但是对于每一个单词内字母的填充,仍然从前向后填充。

    //Leetcode151
    	//时间复杂度:O(n)。
    	//空间复杂度:O(n)。
    	public String reverseWords(String s) {
            if(s.length() <= 1){
                return s;
            }
            char[] cS = s.toCharArray();
            int i = 0;
            while(cS[i] == ' '){
                i++;//将i定位到第一个非空格字符
            }
            int j = s.length() - 1;
            while(cS[j] == ' '){
                j--;//将j定位到最后一个非空格字符
            }
            int len = j - i + 1;
            for(int k = i; k <= j; k++){
                if(cS[k] == ' ' && cS[k + 1] == ' '){
                    //内部有连续空格的,只计数一次。
                    len--;
                }
            }            
            //此时len为真正的新串长度
            char[] res = new char[len];
    
            int left = i;
            int right = i;
            int count = 0;
            int temp = len - 1;
            while(true){
                right = left;
                while(right <= j && cS[right] != ' '){
                    right++;//right最终移动至本单词的后一个空格位置
                    
                }
                count = right - left;//count记录本单词长度
                if(right > j){
                    break;//操作最后一个单词时会出现该情况
                }
                //填充res时,从后向前填充单词,但是对于每一个单词内字母的填充,仍然从前向后填充。
                for(int k = count - 1; k >= 0; k--){
                    res[temp - k] = cS[left++];
                }
                res[temp - count] = ' ';
                temp = temp - count - 1;
    
                while(cS[left] == ' '){
                    left++;//过滤掉内部空格
                }
            }
            //填充最后一个单词
            int l = 0;
            while(left < right){
                res[l++] = cS[left++];
            }
            return new String(res);
        }
    
  5. 经典题目:Leetcode 剑指Offer58-II,左旋字符串n位,可以采用多次字符串翻转进行。可以证明,通过:

    ① 将字符串前n个字符翻转,即下标范围在[0, n - 1]的子字符串翻转。

    ② 将字符串后部分翻转,即下标范围在[n, s.length() - 1]的子字符串翻转。

    ③ 将整个字符串翻转,即下标范围在[0, s.length() - 1]的字符串翻转。

    此方法只需要对char[]数组进行操作即可,不需要创建StringBuilder对象,节省时间。

    //Leetcode 剑指Offer58-II
    	//时间复杂度:O(n)。
    	//空间复杂度:O(n)。
    	public String reverseLeftWords(String s, int n) {
            //翻转前n个字符,翻转后字符,再翻转整个字符串。
            int len = s.length();
            char[] cS = s.toCharArray();
            reverseByIndex(cS, 0, n - 1);
            reverseByIndex(cS, n, len - 1);
            reverseByIndex(cS, 0, len - 1);
            return new String(cS);
        }
    
        public static void reverseByIndex(char[] cS, int start, int end){
            char temp;
            for(int i = start, j = end; i < j; i++, j--){
                temp = cS[i];
                cS[i] = cS[j];
                cS[j] = temp;
            }
        }
    

2. 字符串空格替换

  1. 将字符串中的空格替换为"%20",经典题目:Leetcode 剑指Offer 05.替换空格。需要通过一次遍历确定原串中的空格数量,来确定新串的容量。新串填充过程中,需要在遇到原串空格时,改填充为’%', ‘2’, ‘0’ 三个字符。

    //Leetcode 剑指Offer05
    	//时间复杂度:O(n)。
    	//空间复杂度:O(n)。
    	public String replaceSpace(String s) {
            int n = s.length();
            char[] cS = s.toCharArray();
            StringBuilder sb = new StringBuilder();
            for(int i = 0; i < n; i++){
                if(cS[i] == ' '){
                    sb.append("  ");
                }
            }
            s += sb.toString();
            cS = s.toCharArray();
            int len = s.length();
            int left = n - 1;
            int right = len - 1;
            while(left >= 0){
                if(cS[left] == ' '){
                    cS[right--] = '0';
                    cS[right--] = '2';
                    cS[right--] = '%';
                }else{
                    cS[right--] = cS[left];
                }
                left--;
            }
            return new String(cS);
        }
    

3. 字符串匹配(KMP)

  1. 算法中next数组的求解主要分为三部分:回退、自增、赋值
    ① 当left大于0并且匹配不成功时,left需要回退至匹配成功或回到0,回退操作原理参考题解(反复多看)。

    多图预警详解 KMP 算法 - 实现 strStr() - 力扣(LeetCode)

    while(left > 0 && p.charAt(left) != p.charAt(right)){
    	left = next[left - 1];
    }
    

    ② 当匹配成功时,left自增继续匹配

    if(p.charAt(left) == p.charAt(right)){
    	left++;
    }
    

    ③ 每一次外层循环中,都要对next[right]赋值,其值为进行过①②操作之后的left值。

    next[right] = left;
    
  2. 算法中匹配过程主要含有三部分:跳转、自增、检验

    ① 匹配不成功则根据next数组**跳转 **

    while(j > 0 && s.charAt(i) != p.charAt(j)){
    	j = next[j - 1];
    }
    

    ② 匹配成功自增

    if(s.charAt(i) == p.charAt(j)){
    	j++;
    }
    

    ③ 时刻检验是否匹配完毕子串

    if(j == pLen){
    	return i - j + 1;
    }
    

  1. 经典题目:Leetcode28。

    //Leetcode28
    	//时间复杂度:O(n × m)。
    	//空间复杂度:O(1)。
        public int strStr(String haystack, String needle) {
            String s = haystack;
            String p = needle;
            int sLen = s.length();
            int pLen = p.length();
    
            if(pLen == 0){
                return -1;
            }
            //获取next数组
            int[] next = new int[pLen];
            //next[0] = 0;
            for(int left = 0, right = 1; right < pLen; right++){
                while(left > 0 && p.charAt(left) != p.charAt(right)){
                    left = next[left - 1];
                }
                if(p.charAt(left) == p.charAt(right)){
                    left++;
                }
                next[right] = left;
            }
    
            //进行KMP搜索:内含三部分:1)匹配不成功则根据next数组跳转 2)匹配成功后移 3)时刻检验是否匹配完毕子串
            for(int i = 0, j = 0; i < sLen; i++){
                while(j > 0 && s.charAt(i) != p.charAt(j)){
                    j = next[j - 1];
                }
                if(s.charAt(i) == p.charAt(j)){
                    j++;
                }
                if(j == pLen){
                    return i - j + 1;
                }
            }
            return -1;
        }	
    
  2. 经典题目:Leeetcode459,检查一个字符串是否可以通过由它的一个子串重复多次构成。考虑如果字符串可以由它的一个子串重复构成,即字符串中存在重复的子串。将字符串进行左旋(右旋也可),旋转次数在1 ~ s.length() - 1次范围内,总能与原字符串相同(匹配)。考虑到进行旋转并遍历耗时严重,故引入新的字符串为原串与原串的相加,即String str = s + s,在新的str中,从第1位到第str.length() - 2位的子串中,包含了原串左旋1 ~ s.length() - 1次的结果,故只需要在新的str从第1位到第str.length() - 2位的子串中匹配出原串即可说明满足题意。涉及字符串匹配,采用KMP,注意:此处传入KMP方法的参数列表需要增加起始下标结束下标,即此处必须在str的第1位到第str.length() - 2位的子串中匹配。

    //Leetcode459    
    	//时间复杂度:O(n)。
    	//空间复杂度:O(n)。
    	public boolean repeatedSubstringPattern(String s) {
            String sCopy = s + s;
            return indexKMP(sCopy, s, 1, sCopy.length() - 1);       
        }
    
        //在字符串s的[start, end)子串中匹配p
        public boolean indexKMP(String s, String p, int start, int end){
            int sLen = end - start;
            int pLen = p.length();
            char[] sC = s.toCharArray();
            char[] pC = p.toCharArray();
            if(pLen == 0){
                return false;
            }
    
            //获取next数组
            int[] next = new int[pLen];
            //next[0] = 0
            for(int left = 0, right = 1; right < pLen; right++){
                while(left > 0 && pC[left] != pC[right]){
                    left = next[left - 1];
                }
                if(pC[left] == pC[right]){
                    left++;
                }
                next[right] = left;
            }
            //KMP匹配
            for(int i = start, j = 0; i < end; i++){
                if(j > 0 && pC[j] != sC[i]){
                    j = next[j - 1];
                }
                if(pC[j] == sC[i]){
                    j++;
                }
                if(j == pLen){
                    return true;
                }
            }
            return false;
        }
    

你可能感兴趣的:(代码随想录刷题,算法,leetcode,java)