字符串包含

问题描述:给定两个字符串str1和str2,字符串中只包含字母(区分大小写)和数字,并且字符可以重复。现在让你判断str2中的字符是否都来源于str1中的字符(假设str1的长度为m,str2的长度为n)?限制时间复杂度为O(m+n),空间复杂度为O(1)。

分析:采用暴力法可以很容易地实现要求,但是效率比较低,需要拿str2中的每一位去和str1中的每一位进行比较,因此时间复杂度为O(mn),不符合题目要求,空间复杂度为O(1)。

        因此需要寻找新的解法。这里我提供三种解法,第一种解法利用哈希表,第二中解法利用素数,第三种解法利用编码(在穷举子集合问题中我已经详细介绍过了,读者可以查看我的博客中的“穷举子集合”这篇博客,这里不再解释)。

       第一种解法(利用哈希表):

                        可以建立一个长度为62的哈希表,用布尔数组模拟即可,因为布尔类型占用字节少,可以节省空间。

                        假设这个数组为hash[0]~hash[61],一开始全部初始化为false,表示不存在这个字符,其中下标0~25对用A~Z,26~51对应a~z,52~61对应0~9。

                        将str1中的每个字符哈希到对应的数组元素中,并将元素赋值为true。

                        可以先假设str2中的字符全部来自于str1中,可以设置标志变量实现。

                        将str2中的每个字符也哈希到对应的位置,如果该位置元素为true,那么接着往后比较,如果比较到某一位,哈希表中某元素为false,那么直接返回false即可。

                        如果执行到最后都没有返回false,那么说明str2中的字符全部来自于str1中,返回true即可。

                        这种解法就是利用哈希表实现,需要浪费一点空间,但是可以满足题目要求,并且时间复杂度为O(m+n),空间复杂度为O(1)。

        第二种解法(利用素数):

                       我们可以让字符串str1中的每个字符与一个素数对应,从2开始,往后类推,A对应2,B对应3,C对应5,......。遍历第一个字符串str1,把每个字符对应素数相乘。最终会得到一个整数。

                       利用上面字母和素数的对应关系,对应第二个字符串中的字符,然后轮询,用每个字符对应的素数除前面得到的整数。

                       如果结果有余数,说明该字符不是来源于str1中,直接返回false。

                       如果整个过程中没有余数,则说明第二个字符串是第一个的子集了(判断是不是真子集,可以比较两个字符串对应的素数乘积,若相等则不是真子集)。

                       算法的时间复杂度为O(m+n),最好的情况为O(n)(遍历短的字符串的第一个数,与长字符串素数的乘积相除,即出现余数,便可退出程序,返回false),空间复杂度为O(1),符合题目要求。

                       但是该方法存在一个致命的问题,就是数据可能会溢出,因为当字符串很长时,所得的成积会很大,极有可能会溢出,所以这种方法慎用。

          第三种解法(利用编码):

                      可以将所有可能出现的字符进行编码,一共可能出现62个字符,可以用62位对其进行编码,1表示对应的位出现,0表示对应的位不出现。

                      62位的编码,可以使用8个字节实现,大多数编程语言中都有长整型类型,所定义的是占用8个字节,我们可以使用它的低62位。

                      假设A~Z对应低0~25位, a~z对应低26~51位, 0~9对应低52~61位。

                      可以扫描str1中的每个字符,进行置位。

                      可以先假设str2中的字符全部来自于str1中,再去找假设不成立的条件。

                      再扫描str2中的每个字符,判断标志位是否为0,若为0,说明该字符在str1中不存在,直接返回false。直到最后,如果还没有返回false,那么就返回true。

                      算法的时间复杂度为O(m+n),空间复杂度为O(1),符合题目要求。

          三种解法比较:

                      解法1占用空间大,但是计算比较简单,编程也简单。

                      解法2占用空间小,但是计算比较复杂,需要去寻找素数,编程复杂,最大的问题是可能会溢出。

                      解法3占用空间小,但是计算比较复杂,用到了位运算,编程相对简单。需要去找一个满足编码位数的类型变量。

                      综上所述:三种方法各有利弊,读者可以根据自己的应用场合以及自己的编程习惯选用适当的方法,当然还可能存在其他的解法。

          注意无论是哪种解法,我们要学会每种解法的思想,而不是只会应用到这个题目中,比如在我之前的博客中,有一个问题是穷举子集合,当时我就用到了字符编码,这里我同样用到了字符编码。

          总之学算法,一定要学算法思想,而不是某一道题。

          鉴于哈希表和素数这两种解法,大家都曾经用过,或者写过类似的程序,这里就不再写详细的代码了。

          由于位运算大家用的不多,这里我写出了解法3详细的Java代码实现,都是通用的算法语句,读者可以很容易转换为其他语言。具体的Java代码如下:

 1 import java.util.*;
 2  class Test {
 3       public static boolean StringContain(String str1,String str2){
 4           long code=0;                                            //用来编码的变量,8个字节,共64位,我们只用低62位
 5           for(int i=0;i<str1.length();i++)                        //扫描str1字符串,为对应的字符置位
 6           {
 7               if(str1.charAt(i)>='A' && str1.charAt(i)<='Z')      //A~Z
 8                    code |= (1 << (str1.charAt(i) - 'A'));
 9               if(str1.charAt(i)>='a' && str1.charAt(i)<='z')      //a~z
10                   code |= (1 << ((str1.charAt(i) - 'a')+26));
11               if(str1.charAt(i)>='0' && str1.charAt(i)<='9')      //0~9
12                   code |= (1 << ((str1.charAt(i) - '0')+52));
13           }
14           
15           for(int i=0;i<str2.length();i++)                         //扫描str2字符串,对应的字符进行判断
16           {
17               if(str2.charAt(i)>='A' && str2.charAt(i)<='Z')
18                   if ((code & (1 << (str2.charAt(i) - 'A'))) == 0)
19                       return false;
20               if(str2.charAt(i)>='a' && str2.charAt(i)<='z')
21                   if ((code & (1 << ((str2.charAt(i) - 'a')+26))) == 0)
22                       return false;
23               if(str2.charAt(i)>='0' && str2.charAt(i)<='9')
24                   if ((code & (1 << ((str2.charAt(i) - '0')+52))) == 0)
25                       return false;
26           }
27           
28           return true;
29       }
30     }
31  
32 public class Main {
33     public static void main(String[] args) {
34       String str1=new String("a3ABCD");
35       String str2=new String("BADa3");
36       System.out.println("str1="+str1);
37       System.out.println("str2="+str2);
38       System.out.println("是否包含(true/false):"+Test.StringContain(str1,str2));
39       
40     }
41 
42 }
View Code

输出结果为:

str1=a3ABCD
str2=BADa3
是否包含(true/false):true

          

                            

posted @ 2016-04-24 15:05  成功=坚持+努力+目标  阅读(451)  评论(0编辑  收藏  举报