344.反转字符串541. 反转字符串II剑指Offer 05.替换空格151.翻转字符串里的单词剑指Offer58-II.左旋转字符串

344.反转字符串541. 反转字符串II剑指Offer 05.替换空格151.翻转字符串里的单词剑指Offer58-II.左旋转字符串

344.反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 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”]

思路

左右双指针法
在字符串首尾定义left,right指针。指针指向的字符交换后left–,right++.直至left >= right

代码如下

//时间复杂度: O(n)
//空间复杂度: O(1)
public static void reverseStr(char[] s) {
    if (s.length == 0 || s == null) {
        return;
    }
    int left = 0;
    int right = s.length - 1;
    while (left < right) {
        char temp = s[left];
        s[left] = s[right];
        s[right] = temp;
        left++;
        right--;
    }
}

541. 反转字符串II

力扣题目链接(opens new window)

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

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

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

示例:

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

思路

左右双指针法
每隔2k个反转前k个,尾数不够k个时候全部反转,尾数够k不够2k个时候全部反转
针对这三种情况分别控制即可

代码如下

//时间复杂度: O(n)
//空间复杂度: O(1)
public static String reverseStrII(String s, int k) {
    if (s == null || s.length() == 0) {
        return null;
    }
    char[] chars = s.toCharArray();
    for (int i = 0; i < chars.length; i = i + 2 * k) {
        // 每隔2k个反转前k个
        if (i + 2 * k <= chars.length) {
            reverse(i, i +  k - 1, chars);
        }
        // 尾数不够k个时候全部反转
        else if(chars.length-1-i<k){
            reverse(i,  chars.length-1, chars);
        }else{
            // 尾数够k不够2k个时候全部反转
            reverse(i, i +  k - 1, chars);
        }
    }
    return new String(chars);

}

public static void reverse(int left, int right, char[] s) {
    while (left < right) {
        char temp = s[left];
        s[left] = s[right];
        s[right] = temp;
        left++;
        right--;
    }
}

剑指Offer 05.替换空格

力扣题目链接(opens new window)

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

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

思路 快慢双指针法

思路 双指针法
最常规的思路,无非是把字符串转化成数组,然后从头开始遍历字符串,最后将空格替换成%20,最后再把数组转化成字符串。相信大家的第一感觉是这么想的
但是往往会忽略,空格占一个元素,%20占据两个元素,从头开始遍历的话需要对数组扩容,那么时间复杂度就是o(n2)
低时间复杂的的一种解法是,将字符串转换成Stringbuilder,对其扩容,有几个空格,那就再字符串尾部添加2倍的空间。
定义双指针left,right。left指向扩容前数组的尾部,right指向扩容后数组的尾部。从尾部遍历,这样只会消耗0(n)的时间复杂度
如果left所指向的元素不是空格,那么直接赋值给rhght。同时两个指针向左移动
如果left所指向的元素是空格,那么把%20赋值给right和right–,同时两个指针向左移动
最后直到left指针指向-1,返回字符串

代码如下

// 时间复杂度0(n)
// 空间复杂度0(1)
public static String replaceSpace(String s) {
    if (s == null) {
        return null;
    }
    StringBuffer stringBuffer = new StringBuffer();
    stringBuffer.append(s);
    for (int i = 0; i < s.length(); i++) {// 如果元素等于空格,那么在字符串尾部添加来给你个空格
        if(s.charAt(i) == ' '){
            stringBuffer.append("  ");
        }
    }
    int left = s.length()-1;
    int right = stringBuffer.length()-1;// 定义左右双指针位置
    char[] chars = stringBuffer.toString().toCharArray();
    while(left >= 0){
        if(stringBuffer.charAt(left) != ' '){
           chars[right] = chars[left];
           right--;
           left--;
        }else{
            chars[right--] = '0';
            chars[right--] = '2';
            chars[right--] = '%';
            left--;
        }
    }
    return new String(chars);

}

问题

对于边缘条件的判断出错.

题目要求把字符串 s 中的每个空格替换成"%20"。如果字符串长度为0,即"",那么应该原样返回,但是我的判断条件却返回null

if (s == null || s.length() == 0) {
     return null;
}

改为

if (s == null) {
    return null;
}

思路 stringbuilder扩容

从头开始遍历的话需要对数组扩容,那么时间复杂度就是o(n2)
但可以换一种思路,不使用数组来扩容,而使用stringbuilder
如果字符等于’ ',那么拼接%20即可

// 时间复杂度0(n)
// 空间复杂度0(1)
public static String replaceSpace1(String s) {
    if (s == null) {
        return null;
    }
    StringBuffer stringBuffer = new StringBuffer();
    for(int i =0;i<s.length();i++){
        if(s.charAt(i) != ' '){
            stringBuffer.append(s.charAt(i));
        }else{
            stringBuffer.append("%20");
        }
    }
    return stringBuffer.toString();

}

151.翻转字符串里的单词

力扣题目链接(opens new window)

给定一个字符串,逐个翻转字符串中的每个单词。

示例 1:
输入: “the sky is blue”
输出: “blue is sky the”

示例 2:
输入: " hello world! "
输出: “world! hello”
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。

示例 3:
输入: “a good example”
输出: “example good a”
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

思路

思路 双指针法(快慢双指针)
1.先对字符串整体做翻转
2.移除多余的空格.这里可以参考数组模块,【去除多余元素】算法。
在移除多余的空格后,还需要保留字符串之间的空格,那么需要对【去除多余元素】算法做改进
改进前的内容

int left = 0;
int right;
for (right = 0; right < chars.length; right++) { //去除所有空格并在相邻单词之间添加空格, 快慢指针
	if (chars[right] != ' ') { //遇到非空格就处理,即删除所有空格。
    // 补上该单词,遇到空格说明单词结束
        chars[left++] = chars[right];
    }
}

改进后的内容 :

题目要求每个单词间用空格分割,那么移动一个单词,当right遇到空格或者遍历到数组尾部单词结束,之后在单词后添加空格。

int left = 0;
int right;
for (right = 0; right < chars.length; right++) { //去除所有空格并在相邻单词之间添加空格, 快慢指针
	if (chars[right] != ' ') { //遇到非空格就处理,即删除所有空格。
    	if (left != 0) { // 特殊处理 单词之间用空格分割
        	chars[left++] = ' ';
    	}

		while (chars[right] != ' '&& right < chars.length) { // 补上该单词,遇到空格说明单词结束
    		chars[left++] = chars[right++];
		}

	}
}

这里跟去除数组元素类似,但是有区别。大概理解为,去除数组元素是以字符为单位处理,而移除多余空格是以单词为单位处理,真的很难理解

3.然后对每个单词做翻转
此时单词间用空格做分隔,使用快慢双指针法找到单词首尾下标,翻转单词

代码如下

// 时间复杂度0(n)
// 空间复杂度0(1)
public static String reverseWords(String s) {
        if (s == null) {
            return null;
        }


        // 先对字符串整体做翻转
        char[] chars = reverseStr(s.toCharArray(), 0, s.length() - 1);
        // 移除多余空格
        char[] charsCopy = removeElement(chars);
        // 然后对每个单词做翻转
        int left = 0;
        int right;
        for (right = 0; right <= charsCopy.length; right++) {
            if (right == charsCopy.length || charsCopy[right] == ' ') {
                reverseStr(charsCopy, left, right - 1);
                left = right + 1;
            }
        }
        return new String(charsCopy);

    }

    private static char[] removeElement(char[] chars) {
        int left = 0;
        int right;
        for (right = 0; right < chars.length; right++) { //去除所有空格并在相邻单词之间添加空格, 快慢指针

            if (chars[right] != ' ') { //遇到非空格就处理,即删除所有空格。
                if (left != 0) { // 特殊处理 单词之间用空格分割
                    chars[left++] = ' ';
                }

                while (right < chars.length && chars[right] != ' ') { // 补上该单词,遇到空格说明单词结束.遍历到数组尾部单词结束
                    chars[left++] = chars[right++];
                }
            }
        }

        char[] charsCopy = new char[left];// 相当于c++ resize 操作
        System.arraycopy(chars, 0, charsCopy, 0, left);
        return charsCopy;
    }

    public static char[] reverseStr(char[] s, int left, int right) {
        if (s.length == 0 || s == null) {
            return null;
        }
        while (left < right) {
            char temp = s[left];
            s[left] = s[right];
            s[right] = temp;
            left++;
            right--;
        }
        return s;
    }
}

问题1

去除多余空格代码出现数组越界的问题

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 7
at algorithm.character.reverseWords.removeElement(reverseWords.java:93)
at algorithm.character.reverseWords.reverseWords(reverseWords.java:69)
at algorithm.character.reverseWords.main(reverseWords.java:12)

原因出自【去除多余空格】判断条件。当right = chars.length, while循环先执行chars[right] != ’ '。导致数组越界

while (chars[right] != ' ' && right < chars.length) {
    chars[left++] = chars[right++];
}
 改成 right < chars.length 放到&&while (right < chars.length && chars[right] != ' ') {
    chars[left++] = chars[right++];
}

问题2

尾部的单词没有反转。举例 the sky is blue 反转后 blue is sky eht
原因出自【对每个单词做反转】。当right = chars.length ,跳出循环,没有处理最后一个单词
改进前

for (right = 0; right < charsCopy.length; right++) {
    if (charsCopy[right] == ' ') {
        reverseStr(charsCopy, left, right - 1);
        left = right + 1;
    }
}

改进后
for循环条件 right < charsCopy 改为 right <= charsCopy
if语句增加right == charsCopy.length 处理最后一个单词 要加在 charsCopy[right] == ’ '前,避免数组越界

for (right = 0; right <= charsCopy.length; right++) {
    if (right == charsCopy.length || charsCopy[right] == ' ') {
        reverseStr(charsCopy, left, right - 1);
        left = right + 1;
    }
}

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

力扣题目链接(opens new window)

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

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

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

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

思路 substring截取字符串

思路:substring截取字符串
substring截取字符串,然后StringBuilder重新拼接

代码如下

// 时间复杂度o(n)
// 空间复杂度o(n)
public static String reverseLeftWords(String s, int n) {
    if (s == null) {
        return null;
    }
    if (n < 0 && n > s.length()) {
        return s;
    }
    String s1 = s.substring(0, n);
    String s2 = s.substring(n);
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append(s2).append(s1);
    return new String(stringBuilder);
}

思路:双指针法

多次翻转字符串
先翻转0到n-1下标的字符串,再反转剩余字符串.
整个字符串翻转
可以用Stringbuilder翻转,也可以将字符串转化为数组翻转

代码如下

//时间复杂度o(n)
//空间复杂度o(1)
public static String reverseLeftWords1(String s, int n) {
    if (s == null) {
        return null;
    }
    if (n < 0 && n > s.length()) {
        return s;
    }
    StringBuilder stringBuilder = new StringBuilder(s);
    reverserStr(0, n - 1, stringBuilder);
    reverserStr(n, s.length() - 1, stringBuilder);
    reverserStr(0,s.length()-1,stringBuilder);

    return new String(stringBuilder);
}

private static void reverserStr(int left, int right, StringBuilder stringBuilder) {
    while (left < right) {
        char temp = stringBuilder.charAt(left);
        stringBuilder.setCharAt(left, stringBuilder.charAt(right));
        stringBuilder.setCharAt(right, temp);
        left++;
        right--;
    }
}

你可能感兴趣的:(算法,数据结构,leetcode)