代码随想录算法训练营第三期day08-字符串01

目录

1.T344:反转字符串

2.T541:反转字符串Ⅱ

代码实现

法1、最本分的一种解法:

法2、利用Java的Math类来省去条件判断逻辑:

3.剑指Offer 05:替换空格【题意简单,但不要小看~】

代码实现

法1、双指针法:

法2、充分利用StringBuilder类:

思考

4.T151:翻转字符串里的单词【本日最顶之题】

代码实现

法1、Carl思路:

法2、借助StringBuilder类:

思考

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

代码实现

法1、朴素法:

法2:借助StringBuilder类

思考


1.T344:反转字符串

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

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

提示:

  • 1 <= s.length <= 105

  • s[i] 都是 ASCII 码表中的可打印字符

S:没啥好说的,直接上代码

    public void reverseString(char[] s) {
        int l = 0, r = s.length - 1;
        while (l < r) {
            //self
            s[l] = (char) (s[l] ^ s[r]);
            // s[r] = (char) (s[l] ^ s[r]);
            s[r] = (char) (s[r] ^ s[l]);//都可以
            s[l] = (char) (s[l] ^ s[r]);

            //精简(和self二选一)
            s[l] ^= s[r];  //构造 a ^ b 的结果,并放在 a 中
            s[r] ^= s[l];  //将 a ^ b 这一结果再 ^ b ,存入b中,此时 b = a, a = a ^ b
            s[l] ^= s[r];  //a ^ b 的结果再 ^ a ,存入 a 中,此时 b = a, a = b 完成交换
            
            ++l;
            --r;
        }
    }

写self报错的时候真的怀疑人生了,对着答案看怎么都不觉得自己写的有什么不一样,CV到IDEA里发现是类型不一致导致的编译错误,必须强转一下🤦‍

2.T541:反转字符串Ⅱ

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

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

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

提示:

  • 1 <= s.length <= 104

  • s 仅由小写英文组成

  • 1 <= k <= 104

S:没必要for(...; ...; ++i)这样一个字符一个字符遍历,直接每次跳2k,再统一处理区间内字符就行

代码实现

法1、最本分的一种解法:

class Solution {
    //一定要注意自己定义的函数作用区间!
    public void reverse(char[] ch, int start, int end) {
        // for (int i = start, j = end - 1; i < j; ++i, --j) {//左闭右开
        for (int i = start, j = end; i < j; ++i, --j) {
            ch[i] ^= ch[j];
            ch[j] ^= ch[i];
            ch[i] ^= ch[j];
        }
    }
    public String reverseStr(String s, int k) {
        char[] chars = s.toCharArray();
        for (int i = 0; i < chars.length; i += 2 * k) {
            if (i + k <= chars.length) {
                // reverse(chars, i, i + k);//解答错误:左闭右开
                reverse(chars, i, i + k - 1);//注意自己定义reverse的区间
            } else {
                // reverse(chars, i, chars.length);//左闭右开
                reverse(chars, i, chars.length - 1);
            }
        }
        return new String(chars);
    }
}

法2、利用Java的Math类来省去条件判断逻辑:

    public String reverseStr(String s, int k) {
        char[] chars = s.toCharArray();
        for (int i = 0; i < chars.length; i += 2 * k) {
            int start = i;//别本能赋0
            int end = Math.min(start + k - 1, chars.length - 1);//灵魂,而且要清楚为什么要-1
            while (start < end)
                chars[start] ^= chars[end];//所以上面要-1
                chars[end] ^= chars[start];
                chars[start] ^= chars[end];
                ++start;//别漏
                --end;
            }
        }
        return new String(chars);
    }

3.剑指Offer 05:替换空格【题意简单,但不要小看~】

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

限制:

0 <= s 的长度 <= 10000

S:

如果想把这道题目做到极致,就不要用额外的辅助空间了!

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

    然后从后向前替换空格,也就是双指针法,

为什么要从后向前填充,从前向后填充不行么?

    从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素后移。

    其实很多数组填充类的问题,都可以预先给数组扩容带填充后的大小,然后再从后向前操作。

这么做有两个好处:

  1. 不用申请新数组。

  2. 从后向前填充元素,避免从前向后填充元素造成的每次添加元素都要将添加元素之后的所有元素向后移。

代码实现

法1、双指针法:

    public String replaceSpace(String s) {
        if (s == null || s.length() <= 0) return s;
        //1、通过StringBuilder扩充空间,空格数量2倍
        StringBuilder str = new StringBuilder();
        for (int i = 0; i < s.length(); ++i) {
            if (s.charAt(i) == ' ') {
                // str.append(" ");//踩坑:少空了一格
                str.append("  ");
            }
        }
        //若是没有空格直接返回
        if (str.length() == 0) {
            return s;
        }
        //2、有空格情况 定义两个指针
        int left = s.length() - 1;
        s += str.toString();
        int right = s.length() - 1;
        char[] ch = s.toCharArray();
        while (left >= 0) {
            if (ch[left] == ' ') {
                // s.charAt(right--) = '0';//private final char value[];
                // s.charAt(right--) = '2';
                // s.charAt(right--) = '%';

                ch[right--] = '0';
                ch[right--] = '2';
                // ch[right--] = '%';//
                ch[right] = '%';
            } else {
                // s.charAt(right) = s.charAt(left);
                ch[right] = ch[left];
            }
            --right;
            --left;
        }
        return new String(ch);
    }

法2、充分利用StringBuilder类:

    public String replaceSpace(String s) {
        if (s == null || s.length() == 0) return s;
        //选用 StringBuilder 单线程使用,比较快,选不选都行
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < s.length(); ++i) {
            if (s.charAt(i) == ' ') {
                sb.append("%20");
            } else {
                sb.append(s.charAt(i));
            }
        }
        return sb.toString();
    }

思考

表面上看起来很简单,但实际上有几处暗藏踩坑点:

  1. String类中的char[]:private final char value[],要修改内容的话只能先转成字符数组;
  2. 在Java中,要想对字符串扩容,不能像C++那样直接resize,常需要借助StringBuilder类等;
  3. 在扩容的时候,一定要想清楚需要相对原先扩多少

4.T151:翻转字符串里的单词【本日最顶之题】

T:给你一个字符串 s ,请你反转字符串中 单词 的顺序。

单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。

返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。

注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。

提示:

  • 1 <= s.length <= 104

  • s 包含英文大小写字母、数字和空格 ' '

  • s 中 至少存在一个 单词

进阶:如果字符串在你使用的编程语言中是一种可变数据类型,请尝试使用 O(1) 额外空间复杂度的 原地 解法。

S:

不能使用辅助空间之后,那么只能在原字符串上下功夫了。

        想一下,我们将整个字符串都反转过来,那么单词的顺序指定是倒序了,只不过单词本身也倒序了,那么再把单词反转一下,单词不就正过来了?

解题思路如下:

  • 移除多余空格(前导、尾随空格)

  • 将整个字符串反转

  • 将每个单词反转

代码实现

法1、Carl思路:

class Solution {
    public char[] removeExtraSpaces(char[] chars) {
        if (chars == null || chars.length <= 0) return chars;
        int slow = 0;
        for (int fast = 0; fast < chars.length; ++fast) {
            if (chars[fast] != ' ') {
                //说明不是第一个单词了
                if (slow != 0) {
                   chars[slow++] = ' '; 
                }
                //fast 遇到空格或遍历到字符串末尾,就证明遍历完一个单词了
                // while (fast == chars.length || chars[fast] == ' ') {
                while (fast < chars.length && chars[fast] != ' ') {
                    chars[slow++] = chars[fast++];
                }
            }
        }
        //数组需要“瘦身”
        char[] newChars = new char[slow];
        // System.arrayCopy(chars, 0, newChars, 0, slow);
        System.arraycopy(chars, 0, newChars, 0, slow);
        return newChars;
    }

    public String reverseWords(String s) {
        if (s == null || s.length() <= 0) return s;
        char[] chars = s.toCharArray();
        chars = removeExtraSpaces(chars);
        reverseString(chars, 0, chars.length - 1);
        reverseEachWord(chars);
        return new String(chars);//空间复杂度O(1)?
    }

    public void reverseEachWord(char[] chars) {
        int start = 0;//记录单词起始位
        for (int end = 0; end <= chars.length; ++end) {
            // if (chars[end] == ' ') {
            // if (chars[end] == ' ' || end == chars.length) {//注意点:索引越界
            if (end == chars.length || chars[end] == ' ') {
                reverseString(chars, start, end - 1);
                start = end + 1;
            }
        }
    }

    public void reverseString(char[] chars, int start, int end) {
        if (chars == null || chars.length <= 0) return;
        // for (int i = 0, j = end; i < j; ++i, --j) {//一不小心又写错了!
        for (int i = start, j = end; i < j; ++i, --j) {
            chars[i] ^= chars[j];
            chars[j] ^= chars[i];
            chars[i] ^= chars[j];
        }
    }
}

法2、借助StringBuilder类:

class Solution {
    public String reverseWords(String s) {
        if (s == null || s.length() <= 0) return s;
        // StringBuilder sb = new StringBuilder(s);
        StringBuilder sb = removeExtraSpaces(s);
        sb.reverse();
        reverseEachWord(sb);
        return sb.toString();
    }

    private StringBuilder removeExtraSpaces(String s) {
        int start = 0, end = s.length() - 1;
        while (s.charAt(start) == ' ') ++start;
        while (s.charAt(end) == ' ') --end;//去掉前导、尾随空格

        StringBuilder sb = new StringBuilder();
        // while (start <= end) {
        while (start <= end) {
            char c = s.charAt(start);
            // if (c != ' ' && s.charAt(end) != ' ') {
            if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') {
                sb.append(c);
            }
            ++start;
        }
        return sb;
    }
    private void reverseEachWord(StringBuilder sb) {
        int start = 0;
        for (int end = 0; end <= sb.length(); ++end) {
            if (end == sb.length() || sb.charAt(end) == ' ') {
                reverseString(sb, start, end - 1);
                start = end + 1;
            }
        }
    }
    private void reverseString(StringBuilder sb, int start, int end) {
        while (start < end) {
            char temp = sb.charAt(end);
            sb.setCharAt(end, sb.charAt(start));
            sb.setCharAt(start, temp);
            ++start;
            --end;
        }
    }
}

思考

个人方法1有几点思考:

  1. 在removeExtraSpaces(char[] chars)中,返回值已经不再是原来的数组,再有就是最后返回的字符串也是新new出来的,因此按照该思路用Java复现,有点怀疑这里能不能实现空间复杂度O(1),即原地修改的要求【迷】,应该还是不能的;

  2. 在reverseEachWord操作中,如果把判断指针所指是否为空字符与指针是否指向字符串末尾的下一位反序,也就是先判断指针所指是否为空字符,则会出现数组索引越界问题;反之,由于短路或的存在,当指针指向字符串末尾的下一位时,就不会再判断指针所指是否为空字符了

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

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

限制:

  • 1 <= k < s.length <= 10000

S:

代码实现

法1、朴素法:

class Solution {
    //按照Java版本所言,该解法的空间复杂度:O(1):用原始数组来进行反转操作
    public String reverseLeftWords(String s, int n) {
        char[] chars = s.toCharArray();
        reverseString(chars, 0, n);
        reverseString(chars, n, s.length());
        reverseString(chars, 0, s.length());
        return new String(chars);
    }
    public void reverseString(char[] chars, int start, int end) {
        for (int i = start, j = end - 1; i < j; ++i, --j) {
            chars[i] ^= chars[j];
            chars[j] ^= chars[i];
            chars[i] ^= chars[j];
        }
    }
}

法2:借助StringBuilder类

class Solution {
    public String reverseLeftWords(String s, int n) {
        if (s == null || s.length() <= 0 || n <= 0) return s;
        StringBuilder sb = new StringBuilder(s);
        // reverseString(sb, 0, sb.length());
        reverseString(sb, 0, n);
        reverseString(sb, n, sb.length());
        return sb.reverse().toString();
    }
    public void reverseString(StringBuilder sb, int start, int end) {
        end -= 1;//否则越界
        while (start < end) {
            char temp = sb.charAt(start);
            sb.setCharAt(start, sb.charAt(end));
            sb.setCharAt(end, temp);
            ++start;
            --end;
        }
    }
}

思考

原字符串转为字符数组,以此字符数组再new一个字符串,空间复杂度还是O(1),也就是还是原地修改?

——有点迷🤦‍

个人觉得法1注释的地方应该是错了,因为Java中String类是不能修改的(准确地说,指向是可以修改,但字符串本身的内容不能修改),并不符合“字符串在你使用的编程语言中是一种可变数据类型”一说。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值