字符串常见算法题目
二叉树A是否包含子树B
对于两棵彼此独立的二叉树A和B,请编写一个高效算法,检查A中是否存在一棵子树与B树的拓扑结构完全相同。给定两棵二叉树的头结点A和B,请返回一个bool值,代表A中是否存在一棵同构于B的子树。
思路:将给出的两个二叉树序列化为字符串,判断它们之间是否存在包含关系
public class IdenticalTree {
public boolean chkIdentical(TreeNode A, TreeNode B) {
String a = SerialTree(A);
String b = SerialTree(B);
return a.contains(b);
}
//递归前序遍历,序列化二叉树
private String SerialTree(TreeNode root){
if(root == null)
return "#!";
String res = root.val+"!";
res = res + SerialTree(root.left);
res = res + SerialTree(root.right);
return res;
}
}
KMP算法:
(1)将两个字符串分为主串与模式串,分别定义两个光标 i、j
(2) j 从模式串第一个字符开始与主串 i 的字符依此判断是否匹配,如果匹配则 j 后移一个字符,然后接着匹配
(3)如果这个过程中有一个不匹配,则将 j 移回 nextArr(详见代码),重复(2)
public int getIndexOf(String s, String m) {
if (s == null || m == null || m.length() < 1 || s.length() < m.length()) {
return -1;
}
char[] ss = s.toCharArray();
char[] ms = m.toCharArray();
int[] nextArr = getNextArray(ms);
//子串部分匹配的长度,即为j需要移动到位置;记录j移动到每个位置后,当不匹配时需要回调的位置
int i = 0;
int j = 0;
while (i < ss.length && j < ms.length) {
if (ss[i] == ms[j]) {
i++;
j++;
} else if (nextArr[j] == -1) {
//根据当前j的位置,获取j需要调整到的位置,-1说明j还在0处,只需移动i
i++;
} else {//此时光标不在0,说明已经匹配一部分,且发现不匹配,则调整j
j = nextArr[j];
}
}
return mi == ms.length ? index - mi : -1;
}
public int[] getNextArray(char[] ms) {
if (ms.length == 1) {
return new int[] { -1 };
}
int[] nextArr = new int[ms.length];
nextArr[0] = -1;
nextArr[1] = 0;
int pos = 2;
int cn = 0;
while (pos < nextArr.length) {
if (ms[pos - 1] == ms[cn]) {
nextArr[pos++] = ++cn;
} else if (cn > 0) {
cn = nextArr[cn];
} else {
nextArr[pos++] = 0;
}
}
return nextArr;
}
变形词
对于两个字符串A和B,如果A和B中出现的字符种类相同且每种字符出现的次数相同,则A和B互为变形词,请设计一个高效算法,检查两给定串是否互为变形词。
给定两个字符串A和B及他们的长度,请返回一个bool值,代表他们是否互为变形词。
测试样例:
“abc”,3,“bca”,3
返回:true
思路:记录A每个字符出现的次数,遍历B每出现一次字符,次数减 1,当有次数为负数时,则说明不是变形词
public class Transform {
public boolean chkTransform(String A, int lena, String B, int lenb) {
if(A == null||B == null||lena!=lenb)
return false;
char[] a = A.toCharArray();
char[] b = B.toCharArray();
int[] map = new int[256];
for(int i=0;i<lena;i++){
map[a[i]]++;
}
for(int j=0;j<lenb;j++){
if(map[b[j]]-- == 0){//在等长的情况下,如果出现大于0的情况,则必定有地方小于0
return false;
}
}
return true;
}
}
旋转词
如果对于一个字符串A,将A的前面任意一部分挪到后边去形成的字符串称为A的旋转词。比如A=“12345”,A的旋转词有"12345",“23451”,“34512”,“45123"和"51234”。对于两个字符串A和B,请判断A和B是否互为旋转词。
给定两个字符串A和B及他们的长度lena,lenb,请返回一个bool值,代表他们是否互为旋转词。
思路:A = “12345” -> C = A + A = “1234512345” -> 判断C中是否包含C即可
import java.util.*;
public class Rotation {
public boolean chkRotation(String A, int lena, String B, int lenb) {
// write code here
if(A==null||B==null||lena!=lenb)
return false;
String ab = A+A;
return ab.contains(B);
}
}
句子逆序
对于一个字符串,请设计一个算法,只在字符串的单词间做逆序调整,也就是说,字符串由一些由空格分隔的部分组成,你需要将这些部分逆序。
给定一个原字符串A和他的长度,请返回逆序后的字符串。
测试样例:
“dog loves pig”,13
返回:“pig loves dog”
思路:先将整个句子字母逆序,再将每个单词逆序
public class Reverse {
public String reverseSentence(String A, int n) {
return invertedSentence(invertedSequence(A,0,n-1),n);
}
public String invertedSentence(String s,int n){
String res = s;
char[] temp = s.toCharArray();
int start = 0;
for(int i=0;i<n;i++){
if(temp[i]==' '){
res = invertedSequence(res,start,i-1);
start = i+1;
}
}
res = invertedSequence(res,start,n-1);//最后一个' '后还有一个单词
return res;
}
private String invertedSequence(String s,int a,int b){
char[] temp = s.toCharArray();
while(a<b){
char tmp = temp[a];
temp[a] = temp[b];
temp[b] = tmp;
a++;
b--;
}
return new String(temp);
}
}
字符串移位
对于一个字符串,请设计一个算法,将字符串的长度为len的前缀平移到字符串的最后。
给定一个字符串A和它的长度,同时给定len,请返回平移后的字符串。
测试样例:
“ABCDE”,5,3
返回:“DEABC”
思路:先将前缀逆序,再将字符串剩余部分逆序,最后整体逆序
public class Translation {
public String stringTranslation(String A, int n, int len) {
return invertedSequence(invertedSequence(invertedSequence(A,0,len-1),len,n-1),0,n-1);
}
private String invertedSequence(String s,int a,int b){
char[] temp = s.toCharArray();
while(a<b){
char tmp = temp[a];
temp[a] = temp[b];
temp[b] = tmp;
a++;
b--;
}
return new String(temp);
}
}
拼接最小字典序
对于一个给定的字符串数组,请找到一种拼接顺序,使所有小字符串拼接成的大字符串是所有可能的拼接中字典序最小的。
给定一个字符串数组strs,同时给定它的大小,请返回拼接成的串。
测试样例:
[“abc”,“de”],2
“abcde”
思路:
public String findSmallest(String[] strs, int n) {
for(int i = 1;i < strs.length;i++){
String temp = strs[i];
int j = i;
while(j>0 && (temp+strs[j-1]).compareTo(strs[j-1]+temp) < 0){
strs[j] = strs[j-1];
j--;
}
strs[j] = temp;
}
StringBuffer sb = new StringBuffer();
for(String str: strs){
sb.append(str);
}
return sb.toString();
}
空格替换
请编写一个方法,将字符串中的空格全部替换为“%20”。假定该字符串有足够的空间存放新增的字符,并且知道字符串的真实长度(小于等于1000),同时保证字符串由大小写的英文字母组成。
给定一个string iniString 为原始的串,以及串的长度 int len, 返回替换后的string。
测试样例:
"Mr John Smith”,13
返回:“Mr%20John%20Smith”
”Hello World”,12
返回:”Hello%20%20World”
思路:判断空格数,创建新的字符数组,对原来字符串的字符数组从后往前开始判断;不为空格则,将字符添加进新字符数组;为空格,则再新字符数组添加 ‘%’ ‘2’ ‘0’,三个字符(都是从后往前)
public class Replacement {
public String replaceSpace(String iniString, int length) {
char[] arrays = iniString.toCharArray();
int count = 0;
for(int i=0;i<length;i++){
if(arrays[i]==' ')
count++;
}
int index = length+2*count-1;
char[] res = new char[index+1];
for(int i=length-1;i>=0;i--){
if(arrays[i]==' '){
res[index--] = '0';
res[index--] = '2';
res[index--] = '%';
}else{
res[index--] = arrays[i];
}
}
return new String(res);
}
}
合法括号判断
对于一个字符串,请设计一个算法,判断其是否为一个合法的括号串。
给定一个字符串A和它的长度n,请返回一个bool值代表它是否为一个合法的括号串。
测试样例:
" ( ( ) ( ) ) " , 6
返回:true
测试样例:
" ( ) a ( ) ( ) " , 7
返回:false
思路:定义一个变量 num,循环遍历字符串的字符数组,如果存在字符 ‘(’ 则加一,存在 ‘)’ 则减一,循环过程中如果出现num小于零说明次数 ‘)’ 已经比 ‘(’ 多,则直接返回 false,否则继续遍历;最后判断 num 是否为 0 ,是则返回 true,不是则返回 false
public class Parenthesis {
public boolean chkParenthesis(String A, int n) {
char[] temp = A.toCharArray();
int num = 0;
for(int i=0;i<n;i++){
if(temp[i]=='(')
num++;
if(temp[i]==')')
num--;
if(num<0)
return false;
}
if(num==0)
return true;
return false;
}
}
最长无重复字符子串
对于一个字符串,请设计一个高效算法,找到字符串的最长无重复字符的子串长度。
给定一个字符串A及它的长度n,请返回它的最长无重复字符子串长度。保证A中字符全部为小写英文字符,且长度小于等于500。
测试样例:
“aabcb”,5
返回:3
思路:运用滑动窗口思想,i 为窗口右侧,pre 为窗口左侧,即使子串 s[pre,i] 的长度最长
(1)定义一个map用以保存每个字符出现的位置并初始化为 -1,int[] map
(2)遍历字符串的字符数组,判断每个字符是否在map中已经出现过,没有则将当前位置 i 存入相应的位置,有则则保留最大的pre(map[temp[i]]),并更新map中的位置
(3)保留最长的子串(i-pre+1)
public class DistinctSubstring {
public int longestSubstring(String A, int n) {
int pre=0,res = 0;//
char[] temp = A.toCharArray();
int[] map = new int[256];//用于记录字符出现的位置
for(int i=0;i<256;i++){//初始化map
map[i] = -1;
}
for(int i=0;i<n;i++){
if(map[temp[i]]==-1){
map[temp[i]] = i;//该字符未出现过,则记录当前字符的位置
}else{
pre = Math.max(map[temp[i]]+1,pre);
//如果出现过,则比较其下一个位置与其它字符下一个位置的长度
map[temp[i]] = i;//更新字符位置
}
res = Math.max(i-pre+1,res);//保留最长的子串
}
return res;
}
}