反转字符串
去哪儿面试的最后一道题,开始我写了全数遍历的实现代码,然后面试官要求使用递归来实现,但是我一个算法战五渣早就忘记递归是个什么鬼了,然后面试就GG了。
对于这道题目的解答方法有多种,先把它们全部列出来吧:
- 全数遍历;
- 半数遍历;
- 递归实现;
- 使用栈来实现;
全数遍历(我的答案)
刚开始,我写的是最近的从尾到头的遍历的形式:
public class StringReverseExample {
public static void main(String[] args) {
String str = "abcdefg";
StringBuilder sb = new StringBuilder(str.length());
for (int i = str.length() - 1; i >= 0; i--) {
sb.append(str.charAt(i));
}
System.out.println(sb.toString());
}
}
这个毫无技术含量,当过一天程序员的人应该都是可以写出来的。。。然后面试官就说用递归写一下吧,然后我思考了三分钟之后就交白卷投降了。。。那么,用递归究竟怎么实现呢?以下内容来自网络,我只是做了下个人整理。
递归实现
递归的思想就是把反转长度为 n 的字符串 str 的任务划分为反转长度为 n - 1 的字符串 subStr1(其中 subStr1 = str.substring(1);),然后再加上有 str 第一个字符构成的字符串 subStr2(即 subStr2 = str.charAt(0);),具体实现如下:
public class StringReverseRecursive {
public static void main(String[] args) {
String str = "abcdefg";
String reverseStr = recursiveReverse(str);
System.out.println(reverseStr);
}
public static String recursiveReverse(String str) {
if (str.length() <= 1) {
return str;
}
return recursiveReverse(str.substring(1)) + str.charAt(0);
}
}
那么,使用递归这种算法究竟有什么好处呢?这个问题我还尚未想到,网上搜索的话,基本上都是说算法的实现,然而并没有进行时间复杂度以及空间复杂度的分析。我个人也没想出递归算法跟上面的从尾到头遍历相比有啥好处。。。
半数遍历
半数遍历-JDK类库的实现
JDK 类库里面 StringBuilder 类有一个 reverse
方法,里面有反转的实现,其实也就是类似于半数遍历而已,下面直接贴代码:
public class StringReverseByInBuild {
public static void main(String[] args) {
String str = "abcdefg";
StringBuilder sb = new StringBuilder(str);
sb.reverse();
String destStr = sb.toString();
System.out.println(destStr);
}
}
然后是 StringBuilder.reverse
的内部实现:
public AbstractStringBuilder reverse() {
boolean hasSurrogates = false;
int n = count - 1;
for (int j = (n-1) >> 1; j >= 0; j--) {
int k = n - j;
char cj = value[j];
char ck = value[k];
value[j] = ck;
value[k] = cj;
if (Character.isSurrogate(cj) ||
Character.isSurrogate(ck)) {
hasSurrogates = true;
}
}
if (hasSurrogates) {
reverseAllValidSurrogatePairs();
}
return this;
}
半数遍历-直白的实现
这种遍历算法也是容易理解,下面直接贴实现代码:
public class StringHalfTraverseReverseExample {
public static void main(String[] args) {
String str = "abcdefgh";
char[] chars = new char[str.length()];
for (int i = 0; i < Math.ceil(str.length() / 2f); i++) {
chars[i] = str.charAt(str.length() -1 - i);
chars[str.length() - 1 - i] = str.charAt(i);
}
String destStr = new String(chars);
System.out.println(destStr);
}
}
半数遍历-使用异或
这种方法就真的是涨姿势了,数学没学好玩算法就是蛋疼啊,没办法只能以后多补补了。下面先说说异或的基本性质:
1. 交换律:A^B = B^A
2. 结合律:A^(B^C) = (A^B)^C
3. 恒等率:A^0 = A
4. 归零率:A^A = 0
5. 自反:A^B^B = A^0 = A
然后是代码实现:
public class StringReverseByXor {
public static void main(String[] args) {
String str = "abcdefgh";
char[] chars = str.toCharArray();
int halfCeil = (int) Math.floor(str.length() / 2f);
for (int i = 0, j = str.length() - 1; i < halfCeil; i++, j--) {
chars[i] ^= chars[j];
chars[j] ^= chars[i];
chars[i] ^= chars[j];
}
String destStr = new String(chars);
System.out.println(destStr);
}
}
半数遍历-使用指针
Java 里面并没有指针,不过使用指针的思路就是:进行半数遍历,然后交换对称位置上的值,当然了交换过程需要一个临时变量,在 Java 中不用指针的实现大概如下:
public class StringReverseByExchange {
public static void main(String[] args) {
String str = "abcdefg";
char[] chars = str.toCharArray();
for (int i = 0; i < Math.ceil(chars.length / 2f); i++) {
char temp = chars[i];
chars[i] = chars[chars.length - 1 - i];
chars[chars.length - 1 - i] = temp;
}
String destStr = new String(chars);
System.out.println(destStr);
}
}
使用栈
栈这种数据接口有先进后出的特性,所以可以用它来轻松的实现字符串的反转,不过这么使用栈真的好吗?只是徒增了空间复杂度和时间复杂度吧!
public class StringReverseByStack {
public static void main(String[] args) {
String str = "abcdefg";
Stack<Character> stack = new Stack<>();
for (int i = 0; i < str.length(); i++) {
stack.push(str.charAt(i));
}
char[] chars = new char[str.length()];
for (int i = 0; i < str.length(); i++) {
chars[i] = stack.pop();
}
String destStr = new String(chars);
System.out.println(destStr);
}
}