面试算法代码知识梳理系列
面试算法知识梳理(1) – 排序算法
面试算法知识梳理(2) – 字符串算法第一部分
面试算法知识梳理(3) – 字符串算法第二部分
面试算法知识梳理(4) – 数组第一部分
面试算法知识梳理(5) – 数组第二部分
面试算法知识梳理(6) – 数组第三部分
面试算法知识梳理(7) – 数组第四部分
面试算法知识梳理(8) – 二分查找算法及其变型
面试算法知识梳理(9) – 链表算法第一部分
面试算法知识梳理(10) – 二叉查找树
面试算法知识梳理(11) – 二叉树算法第一部分
面试算法知识梳理(12) – 二叉树算法第二部分
面试算法知识梳理(13) – 二叉树算法第三部分
一、概要
本文介绍了有关字符串的算法第一部分的Java
代码实现,所有代码均可通过 在线编译器 直接运行,算法目录:
- 替换字符串中的空格
- 输入一个字符串,打印出该字符串的所有排列
- 第一个只出现一次的字符
- 翻转句子
- 计算字符串之间的最短距离
二、代码实现
2.1 替换字符串中的空格
问题描述
实现一个函数,将字符串p
中的所有空格都替换成为指定的字符串r
。
解决思路
- 遍历原始的字符串
p
,统计原先字符串中空格的个数spaceNum
。 - 创建一个新的数组
n
,用于存放替换后的字符串。由于原先字符串中空格也占了一个位置,因此新数组n
的长度为p.len + (r.len - 1) * spaceNum
。 - 对于
p
从后往前遍历,如果 遇到了空格,那么就将需要替换的字符串r
中的字符 从后往前 填入n
数组中;如果 遇到了非空格,那么就将p
中的字符填入n
数组中。
实现代码
class Untitled {
static char[] replaceSpace(char p[], char r[], int pLen, int rLen){
int spaceNum = 0;
int i;
for(i = 0; i < pLen; i++){
if(p[i] == ' ')
spaceNum += (rLen-1);
}
int nLen = pLen+spaceNum;
char n[] = new char[nLen+1];
i = nLen-1;
int j = pLen-1;
while(i >= 0 && j >= 0){
if (p[j] == ' ') {
for (int k = rLen-1; k >= 0; k--)
n[i--] = r[k];
} else {
n[i--] = p[j];
}
j--;
}
n[nLen] = 0;
return n;
}
public static void main(String[] args) {
char[] source = "I am sentence with space".toCharArray();
char[] replace = "%20".toCharArray();
char[] result = replaceSpace(source, replace, source.length, replace.length);
System.out.println(result);
}
}
运行结果
>> I%20am%20sentence%20with%20space
2.2 输入一个字符串,打印出该字符串的所有排列
问题描述
输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串abc
,则输出由字符a
、b
、c
所能排列出来的所有字符串abc
、acb
、bac
、bca
、cab
和cba
。
解决思路
这是一个 递归问题,求一个长度为n
的字符串的全排列的方法为:
-
n[0..n.len-1]
全排列的计算方法为:将n[0]
位置的字符分别和n[1..n.len-1]
的每一个字符串交换,n[0]
和交换后的n[1..n.len - 1]
的全排列进行组合。我们将字符串{s}
的全排列表示为{s}
,那么对于abc
来说,其全排列{abc}
,就等于是a + {bc}
、b + {ac}
,c + {ba}
。 - 以此类推,
n[1..n.len - 1]
的全排列,则是将n[1]
分别和n[2..n.len - 1]
的每一个字符串交换,再求出交换后的n[2..len - 1]
的全排列,递归结束的条件为n[i..n.len - 1]
只有一个字符,例如,bc
的全排列为b + {c}
、c + {b}
,而{c}
和{b}
的全排列只有一种,因此递归结束,这时候就可以打印出结果。
实现代码
class Untitled {
static void permutationStr(char p[], int depth, int length){
if (depth == length) {
System.out.println(p);
return;
}
char c;
for (int i = depth; i < length; i++){
c = p[depth]; p[depth] = p[i]; p[i] = c;
permutationStr(p, depth+1, length);
c = p[depth]; p[depth] = p[i]; p[i] = c;
}
}
public static void main(String[] args) {
char[] source = "abc".toCharArray();
permutationStr(source, 0, source.length);
}
}
运行结果
>> abc
>> acb
>> bac
>> bca
>> cba
>> cab
2.3 第一个只出现一次的字符
问题描述
在字符串中找出第一个只出现一次的字符。如输入abaccdeff
,则输出b
,要求时间复杂度为O(n)
。
解决思路
这里需要采用 以空间换时间 的思想,也就是创建一个足够大的数组c
,这里为256
,然后对原始的数组p
进行两次遍历:
- 第一次 从头开始 遍历
p
,以p
的值作为数组c
的下标,并将c
中对应位置的值加1
,也就是说c[Integer.valueOf(i)]
的值表示的是字符i
在p
中出现的次数。这和HashMap
的原理有些类似,只不过是将查找的key
值直接简化成为了value
的整型值。 - 第二次 从头开始 遍历
p
,查找数组c
对应位置该值是否为1
,如果为1
,那么就表示它是第一次只出现一次的字符。
实现代码
class Untitled {
static char firstNotRepeat(char p[], int len){
if (len == 1)
return p[0];
int c[] = new int[256];
int i;
char r = p[0];
for (i = 0; i < 256; i++)
c[i] = 0;
for (i = 0; i < len; i++)
c[p[i]] += 1;
for (i = 0; i < len; i++) {
if (c[p[i]] == 1) {
r = p[i];
break;
}
}
return r;
}
public static void main(String[] args) {
char[] source = "abaccdeff".toCharArray();
char c = firstNotRepeat(source, source.length);
System.out.println(c);
}
}
运行结果
>> b
2.4 翻转句子
问题描述
翻转句子中单词的顺序,但单词内字符的顺序不变,句子中单词以空格符隔开。例如I am a original string
翻转后的结果为string original a am I
。
解决思路
实现过程分为两步:
- 第一步,将整个句子中的所有字符都翻转
- 第二步,遍历翻转后的句子,对于句子内的每一个单词,将其字符再翻转一次,就能保证单词内字符的顺序不变。翻转单词的时候,通过
pStart
和pEnd
记录每次遇到单词的起止下标,并使用子方法reverseSub
对单词中的字符进行翻转。
实现代码
class Untitled {
static void reverseSub(char p[], int start, int end){
char c;
int i = start;
int j = end;
while(i < j){
c = p[i]; p[i] = p[j]; p[j] = c;
i++; j--;
}
}
static void reverseSentence(char p[], int length){
//首先翻转整个具体的所有字符。
reverseSub(p, 0, length-1);
int pStart = 0;
int pEnd = 0;
//从头开始遍历,寻找句子中的单词,pStart和pEnd分别表示单词的起止下标。
while(pStart < length && pEnd < length){
if(p[pStart] == ' '){
pStart++;
pEnd++;
} else if (p[pEnd] == ' ' || p[pEnd] == '\0') {
//翻转单词中的字符。
reverseSub(p, pStart, --pEnd);
pStart = ++pEnd;
} else {
pEnd++;
}
}
}
public static void main(String[] args) {
char[] source = "I am a original string".toCharArray();
System.out.println(source);
reverseSentence(source, source.length);
System.out.println(source);
}
}
运行结果为:
>> string original a am I
2.5 计算字符串之间的最短距离
问题描述
假设我们有两个字符串A
和B
,那么如果想要将字符串A
通过以下三种操作变换成B
:删除、新增和修改,操作步骤的次数就称为 字符串 A 和 B 之间的距离。
现在给定两个字符串,求这两个字符串之间的最短距离。
解决思路
首先,我们需要先明确一个前提条件:如果A
的长度为0
,那么A
和B
之间的距离就为B
的长度,反之对于B
也如此。
下面,我们在来看普通的情况,假如A[0]
和B[0]
相同,那么A
和B
之间的距离就为A[1..A.len-1]
和B[1..B.len-1]
之间的距离;假如A[0]
和B[0]
不相同,那么想要让A
和B
相同,执行的操作有以下几种:
- 删除
A
的第一个字符,然后计算A[1..A.len-1]
和B[0..B.len-1]
的距离 - 删除
B
的第一个字符,然后计算A[0..A.len-1]
和B[1..B.len-1]
的距离 - 修改
A
的第一个字符为B
的第一个字符,然后计算A[1..A.len-1]
和B[1..B.len-1]
的距离 - 修改
B
的第一个字符为A
的第一个字符,然后计算A[1..A.len-1]
和B[1..B.len-1]
的距离 - 增加
A
的第一个字符到B
第一个字符之前,然后计算A[1..A.len-1]
和B[0...B.len-1]
的距离 - 增加
B
的第一个字符到A
第一个字符之前,然后计算A[0...A,len-1]
和B[1..B.len-1]
的距离
对于以上这六种情况,其实最终都可以归纳为 经过一次操作,再加上剩下部分的操作次数,那么我们的接下来的工作就是 求出剩下部分的操作部分的最小值。对于上面的任意一种情况,经过划分后A
和B
的长度都会减少,那么最终必然会达到我们在一开始谈到的 前提条件:如果A
的长度为0
,那么A
和B
之间的距离就为B
的长度,反之对于B
也如此。
实现代码
class Untitled {
static int minValue(int t1, int t2, int t3){
if (t1 < t2) {
return t1 < t3 ? t1 : t3;
} else {
return t2 < t3 ? t2 : t3;
}
}
static int calStringDis(char p1[], char p2[], int p1Start, int p2Start, int p1Len, int p2Len){
if (p1Len == 0) {
if (p2Len == 0)
return 0;
else
return p2Len;
}
if (p2Len == 0) {
if (p1Len == 0)
return 0;
else
return p1Len;
}
if (p1[p1Start] == p2[p2Start])
//A和B的第一个字符相同。
return calStringDis(p1, p2, p1Start+1, p2Start+1, p1Len-1, p2Len-1);
else {
//(1) 删除B的第一个字符,或者将B的第一个字符放到A之前。
int t1 = calStringDis(p1, p2, p1Start, p2Start+1, p1Len, p2Len-1);
//(2) 删除A的第一个字符,或者将A的第一个字符放到B之前。
int t2 = calStringDis(p1, p2, p1Start+1, p2Start, p1Len-1, p2Len);
//(3) 修改A的第一个字符为B的第一个字符,或者修改B的第一个字符为A的第一个字符。
int t3 = calStringDis(p1, p2, p1Start+1, p2Start+1, p1Len-1, p2Len-1);
//计算以上三种情况的最小值,再加上这次操作的次数。
return minValue(t1, t2, t3) + 1;
}
}
public static void main(String[] args) {
char[] source = "abcde".toCharArray();
char[] other = "bcd".toCharArray();
System.out.println("" + calStringDis(source, other, 0, 0, source.length, other.length));
}
}
运行结果
>> 2
更多文章,欢迎访问我的 Android 知识梳理系列:
- Android 知识梳理目录:http://www.jianshu.com/p/fd82d18994ce
- 个人主页:http://lizejun.cn
- 个人知识总结目录:http://lizejun.cn/categories/