代码随想录算法训练营第八天|344.反转字符串、541. 反转字符串II、卡码网:54.替换数字、151.翻转字符串里的单词、卡码网:55.右旋转字符串

344.反转字符串

题目链接:344.反转字符串

        很简单,就是双指针法,问题就是,卡哥题解里的,不用中间变量就能交换两个数字的方法:使用异或运算符解决两数交换问题。

代码:

    public void reverseString(char[] s) {
        // 双指针法
        int left = 0;
        int right = s.length - 1;
        while(left < right){         // 无论奇数偶数,最中间的字符不用交换
            s[left] ^= s[right];     // 此时s[l] = s[l]^s[r].........1
            s[right] ^= s[left];     // 此时s[r] = 1式^s[r] = s[r]^s[l]^s[r] = s[l]........2
            s[left] ^= s[right];     // 此时s[l] = s[l]^s[r] = 1式^2式 = s[l]^s[r]^s[l]
            // 整体移动指针
            left++;
            right--;
        }
    }

不使用中间变量来交换两数据(int型、char型、float型、double型)

        使用异或运算符来操作,利用异或的运算性质:

  1. 任意一个变量a与其自身进行异或运算,结果为0,即a^a=0
  2. 任意一个变量a与0进行异或运算,结果不变,即a^0=X
  3. 异或运算具有可结合性,即a^b^c=(a^b)^c=a^(b^c)
  4. 异或运算具有可交换性,即a^b=b^a

 所以,上述代码中交换就很好理解了:

            s[ l ] ^= s[ r ];     // 此时s[l] = s[l]^s[r].........1
            s[ r ] ^= s[ l ];     // 此时s[r] = 1式^s[r] = s[r]^s[l]^s[r] = s[l]........2
            s[ l ] ^= s[ r ];     // 此时s[l] = s[l]^s[r] = 1式^2式 = s[l]^s[r]^s[l]

541. 反转字符串II

题目链接:541. 反转字符串II

        这题关键在于首指针的移动,根据首指针,确定需要反转的尾指针(首指针 + k),如果尾指针大于字符串长度了,那就让尾指针等于length - 1即可。这题还有一个关键,可以巧妙的利用for循环的特性,借用代码随想录的一句话:

所以当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。

 即for循环里的i不是自增1,可以改成自增2k,满足每2k段处理k个段字符,每2k段处理k段字符。

代码:

public String reverseStr(String s, int k) {
        char[] ch = s.toCharArray();
        // 题目的意思其实概括为 每隔2k个反转前k个,尾数不够k个时候全部反转,所以起始位置的选取是个关键,可以直接利用for循环的结构来设计,就是改变一下i的增量就行
        for(int i = 0; i < ch.length; i += 2 * k){
            int start = i;
            // 这里是判断尾数够不够k个来取决end指针的位置
            int end = Math.min(ch.length - 1, start + k - 1);// 用while循环实现反转,反转包括结尾这个字符,所以要减1
            
            while(start < end){// 用异或运算反转 
                ch[start] ^= ch[end];
                ch[end] ^= ch[start];
                ch[start] ^= ch[end];
                start++;
                end--;
            }
        }
        return new String(ch);
    }

new String(ch):基于给定的字符数组 ch 创建一个新的 String 对象

速度要快于用使用String类的方法(String.valueOf(char[]))来将字符数组转化为String类型。

卡码网:54.替换数字

卡码网:54.替换数字

        我用的不是双指针思路,直接用“在线”的思想,不用开辟新空间,直接遇到数字在后面输出number即可,时间复杂度就是O(n),遍历一遍数组即可。

代码:

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		String str = "";
		Scanner sc = new Scanner(System.in);
		str = sc.next();
		for(int i = 0;i < str.length(); i++) {
			if(str.charAt(i) >= '0' && str.charAt(i) <= '9') {
				System.out.print("number");
			}else {
				System.out.print(str.charAt(i));
			}
		}
		
	}
}

但是一开始有想用StringBuffer做(后面想到了这个“在线”的做法,就不用了),但是对StringBuffer的常用方法不是很熟悉,下面总结一下:

StringBuffer常用方法

1. append(T t): 将参数t的字符串表示附加到此序列的末尾。

  • T可以是各种数据类型。

2. insert(int offset, T t):在指定位置插入参数t的字符串表示。

3. deleteCharAt(int index):删除指定位置的字符。

4. setCharAt(int index, char ch):替换指定位置的字符。

5. CharAt(int index):返回指定位置的字符。

6. reverse():反转此序列中的字符。

7. trimToSize():将此序列的中存储空间缩小到和字符串长度一样的长度,减少空间的浪费。

那String方法也总结一下吧:

String常用方法

1. String(char[] value): 根据字符数组创建一个字符串。

2. substring(int beginIndex, int endIndex): 返回从beginIndexendIndex(不包括)之间的子字符串。

3. replace(char oldChar, char newChar): 将此字符串中的所有指定字符替换为另一字符。

4. split(String str):根据str将字符串分割(返回的是一个字符串数组)。

 151.翻转字符串里的单词

题目链接:151.翻转字符串里的单词

        看了卡哥视频做的,可能是小脑萎缩了,脑子里想的是双指针法删除,结果做的时候还是用额外开辟空间的StringBuilder。。。这次是直接上卡哥代码吧,敲累了。

代码:

//用 char[] 来实现 String 的 removeExtraSpaces,reverse 操作
    public String reverseWords(String s) {
        char[] chars = s.toCharArray();
        //1.去除首尾以及中间多余空格
        chars = removeExtraSpaces(chars);
        //2.整个字符串反转
        reverse(chars, 0, chars.length - 1);
        //3.单词反转
        reverseEachWord(chars);
        return new String(chars);
    }

    //1.用 快慢指针 去除首尾以及中间多余空格,可参考数组元素移除的题解
    public char[] removeExtraSpaces(char[] chars) {
        int slow = 0;
        for (int fast = 0; fast < chars.length; fast++) {
            //先用 fast 移除所有空格
            if (chars[fast] != ' ') {
                //在用 slow 加空格。 除第一个单词外,单词末尾要加空格
                if (slow != 0)
                    chars[slow++] = ' ';
                //fast 遇到空格或遍历到字符串末尾,就证明遍历完一个单词了
                while (fast < chars.length && chars[fast] != ' ')
                    chars[slow++] = chars[fast++];
            }
        }
        //相当于 c++ 里的 resize()
        char[] newChars = new char[slow];
        System.arraycopy(chars, 0, newChars, 0, slow); 
        return newChars;
    }

    //双指针实现指定范围内字符串反转,可参考字符串反转题解
    public void reverse(char[] chars, int left, int right) {
        if (right >= chars.length) {
            System.out.println("set a wrong right");
            return;
        }
        while (left < right) {
            chars[left] ^= chars[right];
            chars[right] ^= chars[left];
            chars[left] ^= chars[right];
            left++;
            right--;
        }
    }

    //3.单词反转
    public void reverseEachWord(char[] chars) {
        int start = 0;
        //end <= s.length() 这里的 = ,是为了让 end 永远指向单词末尾后一个位置,这样 reverse 的实参更好设置
        for (int end = 0; end <= chars.length; end++) {
            // end 每次到单词末尾后的空格或串尾,开始反转单词
            if (end == chars.length || chars[end] == ' ') {
                reverse(chars, start, end - 1);
                start = end + 1;
            }
        }
    }

总结:

        虽然不是自己敲的(自己敲的能过,但是太拉了,还敲了很久。。),不过还是有收获的。

双指针法删除:

        快指针,是负责移动遍历原来的数组。慢指针,是负责定位需要保存的位置的。当快指针移动到不需要删除的位置时(满足条件的位置),就令慢指针所指数组位置等于快指针所指位置数据(这样,保证了不需要的数据被覆盖,唯一需要注意的是,数组大小需要压缩)。

双指针法删除空格:

        先看代码:

for (int fast = 0; fast < chars.length; fast++) {
            //先用 fast 移除所有空格
            if (chars[fast] != ' ') {
                //在用 slow 加空格。 除第一个单词外,单词末尾要加空格
                if (slow != 0)
                    chars[slow++] = ' ';
                //fast 遇到空格或遍历到字符串末尾,就证明遍历完一个单词了
                while (fast < chars.length && chars[fast] != ' ')
                    chars[slow++] = chars[fast++];
            }
        }

         这段代码的巧妙之处在于,for循环不断移动fast指针,而slow指针能及时更新单词末尾的空格,原因在于:每次遇到新单词就会进入if语句中,当第一次进入if语句时,显然,此时slow=0,就不加入空格,而while循环处理的是一个单词的遍历,当遇到字符串末尾或者遇到空格表示遍历完了,此时slow在这个单词的末尾,下次只有再进入这个if,直接让chars[slow++] = ' ';便能在单词末尾加上空格了。

压缩数组:

        利用System.arraycopy方法,将一个数组中的元素复制到另一个数组中。如:

System.arraycopy(chars, 0, newChars, 0, slow);

  • chars 是源数组,从该数组中复制元素。
  • 0 是源数组的起始位置,表示从该数组的第一个元素开始复制。
  • newChars 是目标数组,将复制的元素放到这个数组中。
  • 0 是目标数组的起始位置,表示从该数组的第一个元素开始放置复制的元素。
  • slow 表示要复制的元素个数。

简单地说,这行代码从 chars 数组的开始位置复制 slow 个元素到 newChars 数组的开始位置。从而达到“压缩”的目的(因为slow是最终满足条件的数组大小)。

代码的复用性:

        本题思路中,把多余空格删除之后,先反转整个字符串,再反转每个单词,而无论是反转整个字符串还是每个单词,核心代码都是“从字符串的起始位置start,反转到字符串的终止位置end

        所以反转整个字符串这样定义:

   public void reverse(char[] chars, int left, int right) 

 这样,在反转单词的时候,根据空格或者结尾,确定一个单词的leftright,直接调用即可。

卡码网:55.右旋转字符串

卡码网:55.右旋转字符串

        自己用了一个取巧的思路,即:把需要右旋的提前输出即可,我称之为“提前输出法”。与卡哥的“整体反转+局部反转”法不一样,我认为更好理解。

1. 提前输出法:

import java.util.Scanner;
public class Main {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int k = sc.nextInt();
		char[] charS = sc.next().toCharArray();
		for(int i = 0; i < charS.length; i++) {
			if(i >= charS.length - k) {// 如果是需要右旋的字符,提前输出即可
				System.out.print(charS[i]);
			}
		}
		for(int i = 0; i < charS.length - k; i++) {
			System.out.print(charS[i]);// 把剩下的字符输出
		}
		
	}
}

2. 整体反转+局部反转法:

        和151.翻转字符串里的单词思路类似,只需要实现一个reverse即可。


import java.util.Scanner;
public class Main {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int k = sc.nextInt();
		char[] charS = sc.next().toCharArray();
		reverse(charS,0,charS.length - 1);
		reverse(charS,0,k - 1);
		reverse(charS,k,charS.length - 1);
		System.out.print(charS);
	}
	private static void reverse(char[] charS, int start, int end) {
		while(start < end) {
			charS[start] ^= charS[end];
			charS[end] ^= charS[start];
			charS[start] ^= charS[end];
			start++;
			end--;
		}
	}
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值