现在的位置: 首页 > 综合 > 正文

如何求解字符串问题

2019年04月10日 ⁄ 综合 ⁄ 共 9969字 ⁄ 字号 评论关闭

字符串问题常用数据结构

1)哈希表
这个结果比较快且容易实现。哈希表查找速度快,但不能支持涉及到顺序的操作。
2)平衡树(红黑树)
平衡二叉查找树能够保持输入元素的有序性,所以能够高效地查找和按序输出元素类操作。
3)倒排序
在查找之前,预处理书的内容,通过建立一个哈希表(或查找树)来索引文章中的每个不同的单词,并存储每个单词的出现位置。这样生成的倒排索引能够快速第查到给定的单词。然后,可以通过交叉获得的单词列表查找词组。
4)后缀数组
初始化字符串中指向每个字符的指针数组,将它们排序。这样就得到了一个后缀数组。然后,能够扫描它,找到最近的字符串或使用二分查找单词或词组。
5)可以用栈和队列辅助(如删除重复字符和翻转字符问题)
常用的一些代码:

char temp;
...
if(temp>='0' && temp<='9'){ //保证字符为数字
  ...
} //end if

if( (temp>='A' && temp<='Z') || (temp>='a' && temp<='z') ){ //保证字符为A-Z或a-z字符
  ...
} //end if 

字符数据在内存中的存储形式

将一个字符常量放到一个字符变量中,实际是将该字符的相应的ASCII代码放到存储单元中。如‘a’的ASCII代码为十进制数97,c1='a',在内存中是以97的二进制01100001存储的。
字符数据以ASCII码存储,它的存储形式就与整数的存储形式类似。这样使字符型数据和整型数据之间可以通用。
在JAVA中一个字符类型占两个字节,它只能存放0~255范围内的整数。
A-Z的ASCII值范围65-90 (26个字符 65+26-1)
a-z 的ASCII值范围97-122

//C Language
//大小写字母转换:每一个小写字母比它相应的大写字母的ASCII码大32
void transfer(){
  char c;
  c='a';
  c=c-32;
  printf("%c",c); //A
  printf("%d",c); //65
}//end transfer()
//字符转换为ASCII码
int a=(int)'a';
//ASCII码转换为字符串
int c=97;
char b='0'+c;

设计算法判断一个字符串中字符都是唯一的。

算法1:检查每一个字符在字符串中的出现次数,时间复杂度为O(n^2),但是空间复杂度为O(2)。

	public static boolean isUniqueChar(String str){
		  //输入控制
		  if(str==null ||str=="") //空字符串长度为0
		     return true;
		  
		  //只有一个字符
		  int length=str.length();
		  if(length==1)
		     return true;
		  
		  //多于2个字符
		  char a,b;
		  for(int i=0;i<=length-2;i++){
			a=str.charAt(i);
		    for(int j=i+1;j<=length-1;j++){
			   //compare
			   b=str.charAt(j);
			   if(a==b)
			     return false;
			}//end for
		  }//end for
		  
		  return true;
		}//end isUnitqueChar()
//测试用例	
	public static void testCase() {
		//测试
		//字符串为null
		System.out.println("字符串为null:"+isUniqueChar(null));
		//字符串长度为0,空字符串
		System.out.println("空字符串:"+isUniqueChar(""));
		//字符串长度为1
		System.out.println("字符串长度为1:"+isUniqueChar("a"));
		//字符串中无重复字符
		System.out.println("字符串中无重复字符:"+isUniqueChar("abc"));
		//字符串中有重复字符
		System.out.println("字符串中有重复字符:"+isUniqueChar("abbc"));
    }

算法2:如果字符串中的内容可以破坏的话。我们可以将字符串中的字符排序(时间复杂度为O(nlogn)),然后遍历字符串中的某个字符相邻的字符是否相同(时间复杂度O(n))。但是要注意有些排序算法是需要额外的存储空间的。总的时间复杂度是O(n).

算法3:用hashmap统计每个字符出现的次数,然后遍历hashmap看是否有出现次数多余一次的字符。这种算法需要用到额外的数据结构hash表。

算法4:先假设字符串中的字符均为ASCII码(如果不是的可以增大存储空间,而算法的逻辑是相同的),那么每个字符用ASCII码表示为整数,即可用0-255的一个数组表示,如果某个字符在数组对应位置出现次数超过一成则表示该字符串中有重复字符出现。
时间复杂度O(n),空间复杂度O(n),n为字符串的长度

//有重复字符出现,返回false
public static boolean isUniqueChar(String str){
   //输入控制
   if(str==null)
     return true;

   boolean[] char_set=new boolean[256];
   for(int i=0;i<str.length-1;i++){
    int val=str.charAt(i);
	if(char_set[val])
	   return false; //如果有重复字符出现,则在数组对应位置上会提前将值设置为ture
	char_set[val]=true;
   }//end for
   return true;
}//end isUniqueChar()

删除重复字符问题

测试用例

//test cases
//字符串为null
//字符串为空""
//字符串没有重复字符abcd
//全重复字符aaaa
//连续重复字符aaaabbbcd
//非连续重复字符abcadbc

1)问题1:如果某个字符重复复出现,则删除该字符,如google变为le(删除重复字符g和o,变为gole)

算法1:用一个空的数组保存结果字符串,你将原字符串一个个的字符扫描,看每个字符是不是在结果字符串中,如果不在就加在结果字符串中,否则就跳过,扫描下一个字符。时间复杂度O(n*n),空间复杂度O(n)

public static String removeDuplicates(String str) {
		// 输入控制
		if (str == null || str == "")
			return str;

		char[] c = removeDuplicates(str.toCharArray());
		String strResult = new String(c);
		return strResult;
	}// end removeDuplicates()

	private static char[] removeDuplicates(char[] c) {
		int length = c.length;
		char[] result = new char[length]; // 临时存储空间
		int resultIndex = 0; // reuslt数组的指针

		result[resultIndex]=c[0];
		
		//c中字符a与result中比较,如果a在result中,跳过,否则加入reuslt中
		for(int i=0;i<length;i++){ //c 中字符
			char a=c[i];
			
			int j;
			for(j=0;j<=resultIndex;j++){ //reuslt中字符
				char b=result[j];
                if(a==b)
                	break;
			}//end for
			
			//条件符合的加入result
			if(j>resultIndex)
				result[++resultIndex]=a;
		}//end for
		
		return result;
	}// end removeDuplicates()

算法2:不需要额外存储空间,在原数组中处理,原数组也同时是结果数组,如果不在就加在结果字符串中,否则就跳过,扫描下一个字符。时间复杂度O(n*n),空间复杂度为0

	private static char[] removeDuplicates2(char[] c) {
		int length = c.length;
		int resultIndex=0; 
		
		//c中字符a与result中比较,如果a在result中,跳过,否则加入reuslt中
		for(int i=1;i<length;i++){ //c 中字符
			char a=c[i];
			
			int j;
			for(j=0;j<=resultIndex;j++){ //result中字符
				char b=c[j];
                if(a==b)
                	break;
			}//end for
			
			//条件符合的加入result
			if(j>resultIndex)
				c[++resultIndex]=a;
		}//end for
		
		//剩余字符处理
		if(resultIndex<length-1){
			for(int i=resultIndex+1;i<length;i++)
     			c[i]='0';
		}
		
		return c;
	}// end removeDuplicates()

2)问题2:如果某个字符连续重复复出现,则删除重复的字符,如google变为gogle(删除连续重复字符o)

算法1:用一个队列作为辅助。
入队:连续重复的字符只入队一次,如google,入队后队列中的元素为gogle。
出队:将队列中的元素出队,即为处理结果。
时间复杂度:往队列中插入一个元素的时间复杂度为O(1),最坏情况下需要将n个元素入队,则时间复杂度为O(n)。
空间复杂度:空间复杂度为队列的大小,其大小具体有队列的实现由关。
算法2:还可以按问题1中的算法进行,不过删除规则变为:只删除连续重复出现的字符。

字符串替换

题目:实现一个函数,把字符串中的每个空格替换成“%20”。例如输入“We are happy.”,则输出"We%20are%20happy."

思路:空格为一个字符,替换后“%20”为3个字符

1)计算出替换后字符串的长度

2)原字符串从后向前替换

//c language
void replace(char[] str,int length){
   //输入控制
   if(str==null && length<0)
      return ;
	  
   int spaceCount=0; //计算要替换字符的数目
   int newLength;   //和替换后字符串的长度
   
   for(int i=0;i<length;i++){ 
     if(str[i]==' ')
        spaceCount++;
   }//end for
   
   newLength=length+spaceCount*2;
   
   //替换
   for(int i=length-1;i>=0;i--){
     if(str[i]==' '){
	   str[newLength-1]='0';
	   str[newLength-1]='2';
	   str[newLength-1]='%';
	   newLength=newLength-3;
	 }//end if
	 else{
	   str[newLength-1]=str[i];
	   newLength=newLength-1;
	 }//end else
   }//end for
}//end replace()

字符串和整数转换问题

字符串转换为整数

要实现的函数 public int strToInt(String str);

//没有考虑特殊情况的代码---------------------------------------------------------------
public int strToInt(String str){
   char[] strArray=str.toCharArray();
   int number=0;
   for(int i=0;i<strArray.length;i++){
     number=number*10+(int)strArray[i];
   }//end for
}//end strToInt()

测试用例设计

1)功能测试 

int a=stringToInt("123");

2)边界测试

转换后的整数超出最大整数和最小整数范围,发生溢出

3)负面测试

int a=stringToInt(null);        //字符串为null

int a=stringToInt("");    //字符串为“”

int a=stringToInt("+");   //只有一个加号的字符串

int a=stringToInt("-");    //只有一个减号的字符串

int a=stringToInt("-12a3");
   //字符串中有非数字字符

想一想,还有其他情况会使程序崩溃吗?

程序代码如下:

//正确代码--------------------
public int strToInt(String str) throw Exception {
   //input control
   if(str==null || str==""){
	  throw new Exception("input can not be null or empty string!");
   }//end if
   
   char[] strArray=str.toCharArray();
   int strLength=strArray.length;
   //input only have one + or - char
   if(strLength==1){
       char firstChar=strArray[0];
	   if(firstChar=='+' or firstChar=='-')
	      throw new Exception("input is invalid number");
   }//end if
   
   //开始算法
   int numer=0;  //store result
   //flag positive and negitive number, if it's false present positive nubmer,otherwise negitive number
   boolean minus=false;  
   //标识字符串中数字开始的位置,若有正负号,则从1开始,否则从0开始
   int index=0;          
   if(strArray[0]=='+')
      index++;
   else if(strArray[0]=='-'){
      index++;
	  minus=true;
   }//end else
    
   number=strToIntCore(strArray,index);
      
   //处理正负号数据
   if(minus)
      number=0-number;
	
   return number; 
}//end strToInt()

//字符串数字部分转换为整数
private int strToIntCore(char[] strArray,int index) throw Exception{
   int number=0;
   
   for( ;index<strArray.length;index++){
     if(strArray[index]>='0' && strArray[index]<='9'){   //非字符数字控制
	    number=number*10+strArray[index];
		
		if(number>Integer.MAX_VALUE){   //Integer.MAX_VALUE为正整数最大值
	        throw new Exception("input is out of int bound");
		}//end if
	 }//end if
	 else{
	    throw new Exception("input is a invalid number");
	 }//end else
   }//end for
}//end strToIntCore()

整数转为字符串

//---------------------------- 
public String intToStr(int number){
   //negitive number flag
   boolean minus=false;
   //the convert result
   String str="";
   
   //hanlde negitive number to positive 
   if(number <0){
       minus=true;
	   number=0-number;
   }//end if
   
   //convert positive number to string
   while(number){
      int singleNum=number % 10;
	  str=str+singleNum;
	  number=number/10;
   }//end while
   
   //if number is negitive, convert to it 
   if(minus){
      str="-"+str;
   }//end if
   
   return str;
}//end intToStr()

5 字符串解决大数问题

http://blog.csdn.net/zhongyangzhong/article/details/9165083

6 字符串匹配

算法一:KMP算法

kmp算法时间复杂度为O(m+n),思路如下:

移动位数 = 已匹配的字符数 - 对应的部分匹配值
"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。
"部分匹配值"就是"前缀"和"后缀"的最长的连续共有元素的长度,即最长前缀和最长后缀的最长公共子串。

http://blog.csdn.net/zhongyangzhong/article/details/9174155

算法二:后缀树

如字符串a匹配字符串b,可先生成b的后缀树,然后查询a是否在b的后缀树中。

【面试题】

Google 面试题 - 两个字符串的匹配问题 http://www.cnblogs.com/pylemon/archive/2011/09/06/2168942.html

腾讯面试题:假设两个字符串中所含有的字符和个数都相同我们就叫这两个字符串匹配, 比如:abcda和adabc,由于出现的字符个数都是相同,只是顺序不同, 所以这两个字符串是匹配的。要求高效!

解法一:用哈希表统计字符及出行次数,然后比较2个哈希表是否一样

解法二:假定字符串中都是ASCII字符。如下用一个数组来计数,前者加,后者减,全部为0则匹配。

        static bool IsMatch(string s1, string s2)
        {
            if (s1 == null && s2 == null) return true;
            if (s1 == null || s2 == null) return false;

            if (s1.Length != s2.Length) return false;

            int[] check = new int[128];

            foreach (char c in s1)
            {
                check[(int)c]++;
            }
            foreach (char c in s2)
            {
                check[(int)c]--;
            }
            foreach (int i in check)
            {
                if (i != 0) return false;
            }

            return true;
        }

8 用哈希表解决字符串问题

如果需要判断多个字符是不是在某个字符串里出现过或者统计多个字符在某个字符串中出现的次数,我们可以考虑给予数组创建一个简单的哈希表。这样可以用很小的空间消耗换来时间效率的提升。



9 翻转字符串

原地翻转字符串函数,如abc,翻转结果为cba

对地

JAVA语言版本

//原地翻转字符串函数
private char[] reverse(char[] arr,int pBegin,int pEnd){		
	while(pBegin<pEnd){
	    char temp=arr[pBegin];
		arr[pBegin]=arr[pEnd];
		arr[pEnd]=temp;
		
		pBegin--;
		pEnd--;
	}//end while
	
	return arr;
}//end reverse()

C语言版本

void reverse(char *pBegin, char *pEnd){
    if(pBegin==null || pEnd==null)
	   return;
	
	while(pBegin < pEnd){
	    char temp=*pBegin;
		*pBegin=*pEnd;
		*pEnd=temp;
		
		pBegin++,pEnd--;
	}//end while
}//end reverse()

问题1:翻转句子,如abc def efg,翻转后结果为efg def abc

算法1:利用上述函数先翻转整个句子,然后翻转句中的每个单词。

char* reverseSentence(char *pData){
   if(pData==null)
      return null;
    
	char *pBegin=pData;
	char *pEnd=pData;
	while(*pEnd!='\0')
	   pEnd++;
	pEnd--;
	
	//翻转整个句子
	reverse(pBegin,pEnd);
	
	//翻转句子中的每个单词
	pBegin=pEnd=pData;
	while(*pBegin!='\0'){
	   if(*pBegin==' '){  //空格判断单词
	      pBegin++;
		  pEnd++;
	   }//end if
	   else if(*pEnd==' ' || *pEnd=='\0'){
	      reverse(pBegin,--pEnd);
		  pBegin=++pEnd;
	   }//end if
	   else{
	      pEnd++;
	   }//end else
	}//end while
	return pData;
}//end reverseSentence()

算法2:翻转可以用栈“先进后出”的性质,完成翻转

翻转句子,以单词为对象入栈,如abc def efg

入栈:abc,def,efg

出栈:efg def abc (出栈后在各单词间加入空格)

问题2:翻转字符串前n个字符到字符串末尾

算法1:将前n个字符放到临时空间中,将原字符串中剩余字符前移n位,再将临时空间中的字符串复制到字符串末尾。空间复杂度为O(n)
算法2:将字符串看作2部分ab,翻转(转置)a得到a',翻转(转置)b得到b',然后翻转a'b'得到(a'b')'=ba。

//算法2代码
char* leftRotateString(char* pStr,int n){ //n为翻转字符串前n个字符
    if(pStr!=null){
	    int nLength=static_cast<int> (strlen(pStr));
		if(nLength>0 && n>0 && n<nLength){
		   char* pFirstStart=pStr;
		   char* pFirstEnd=pSttr+n-1;
		   char* pSecondeStart=pStr+n;
		   char* pSecondEnd=pStr+nLength=1;
		   
		   //翻转字符串的前面n个字符
		   reverse(pFirstStart,pFirstEnd);
		   //翻转字符串的后面部分
		   reverse(pSecondeStart,pSecondEnd);
		   //翻转整个字符
		   reverse(pFirstStart,pSecondEnd);
		}//end if
	}//end if
}//end leftRotateString()

10 签名

签名是用某种规则等价定义各个元素(对元素进行预处理),这样相同规则下的元素具有相同的签名,而其他元素则没有。
2.1 变位词
给定一本英语单词词典,请找出所有的变位词集。例如,因为“pots”、"stop"、"tops"相互之间都是由一个词的各个字母改变序列而构成的,因此这些词相互之间就是变位词。
思路:只要考虑如何求解某个单词中各个字母的所有置换(即全排列),注定是失败的算法。因为n个字母组成的单词,有n!个变位词。
算法:对字典中的每个单词都进行签名(即将每个单词中的字母排序后的结果为该单词的签名),这样同一变位词类中的单词会具有相同的签名,然后统计具有相同签名的单词。这样将原来的变位词问题转换为两个子问题:选择一个签名;统计具有相同签名的单词(这里可以对具有签名的字典按照签名进行排序,然后用二分查找找到字典中某个单词首次出现和最后出现的位置,然后输出从首次出现位置到最后出现位置的单词)。
2.2 转置矩阵
给一个m*n的矩阵,要求实现矩阵的转置。
算法:
1)签名:预先记录矩阵中元素所在的行号和列号(为每个元素签名标识),如2*2矩阵在一维数组中的记录如下(obj1<1,1>,obj2<1,2>,obj3<2,1>,obj4<2,2>)。
2)排序:因为转置以前矩阵是按以下规则排序的:先按行排序,行号相同时,按列排序。
因此转置矩阵需要先按列排序,列号相同时按行排序,上述排序结如下(obj1<1,1>,obj3<2,1>,obj2<1,2>,obj4<2,2>)
3)删除元素的行号和列号

11字符串相似度算法

字符串相似算法用来描述两段文字之间的“相似度”,即它们的雷同程度,从而能够用来辨别抄袭。主要的算法有基于编辑距离(Edit
Distance)的LD算法(时间复杂度为O(mn))和最长公共子序列算法(时间复杂度为O(mn))见博客“动态规划(5)-字符串相似度算法”。

12 最大连续子串问题

见博客 动态规划(4)最大连续子串问题

抱歉!评论已关闭.