diff --git a/9 Algorithms Job Interview/1 字符串.md b/9 Algorithms Job Interview/1 字符串.md index 5cd0f41..d5cb9a0 100644 --- a/9 Algorithms Job Interview/1 字符串.md +++ b/9 Algorithms Job Interview/1 字符串.md @@ -2,106 +2,13 @@ 字符串常见的问题: -* 单词反转/移动/回文判断 +* 回文判断 * 字符(串)统计 * 字符串的压缩 -* 字符串的排列和组合 -* 字符串比较 -* 子串 - - -## 翻转句子中单词的顺序 - -题目:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。句子中单词以空格符隔开。为简单起见,标点符号和普通字母一样处理。 -例如输入“I am a student.”,则输出“student. a am I”。 - -``` -char *revert_by_word(char *source); -``` - -思路: - -* 原地逆序,字符串2边的字符逐个交换 , 再按单词逆序; -* 也可以先按单词逆序,再对整个句子逆序; - -针对不允许临时空间的情况,也就是字符交换不用临时空间,可以使用的方法有: - -1. 异或操作 -2. 也就是2个整数相互交换一个道理 - -``` -char a = 'a', b = 'b'; -a = a + b; -b = a - b; -a = a - b; -``` - -最终示例代码: - -``` -//反转 -void _reverse(char *start,char *end){ - if ((start == NULL) || (end == NULL)) return; - while(start < end){ - char tmp = *start; - *start = *end; - *end = tmp; - - start++, - end--; - } -} - -char *revert_by_word(char *source){ - char *end = source; - char *start = source; - if (source == NULL) return NULL; - - //end指针挪动到尾部 - while (*end != '\0') end++; - end--; - - //先全部反转 - _reverse(start,end); - - //按单词反转 - start=end=source; - while(*start != '\0'){ - if (*start == ' '){ - start++; - end++; - }else if(*end == ' ' || *end == '\0'){ - _reverse(start,end-1); - start = end; - }else{ - end++; - } - } - return source; -} -``` - -类似的题目还有: - -不开辟用于交换数据的临时空间,如何完成字符串的逆序 -用C语言实现一个revert函数,它的功能是将输入的字符串在原串上倒序后返回。 - - -## 左旋转字符串 - ->字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。 - -如把`字符串abcdef`左旋转2位得到`字符串cdefab` 。请实现字符串左旋转的函数。要求时间对长度为n的字符串操作的复杂度为O(n),辅助内存为O(1)。 - -``` -char *left_rotate(char *str,int offset){ - -} -``` - -思路: 我们可以abcdef分成两部分,ab和cdef,内部逆序以后,整体再次逆序,就可以得到想要的结果了。 -也就是跟上面的问题是同样的问题。 - +* 字符串的排列和组合 +* [子串查找](1.1%20字符串-查找.md) +* [字符删除](1.2%20字符串-删除.md) +* [字符串修改](1.3%20字符串-修改.md) 如单词反转/移动/ ## 判断字符串是否是回文 @@ -137,44 +44,6 @@ int is_huiwen(const char *source){ ``` -## 找到第一个只出现一次的字符 - -在一个字符串中找到第一个只出现一次的字符。如输入ahbaccdeff,则输出h。 - -``` -char char_first_appear_once(const char *source) -``` - -思路一: 蛮力统计, O(n^2)复杂度 -思路二: 使用hash表,2次扫描,第一次建立hash表 key为字符,value为出现次数;第二次扫描找到第一个value为1的key,时间复杂度O(n) - -hash表长度 256,字符直接作为key值。需要注意的是 char 的范围是 -128~127,unsigned char 才是0~255 - - -示例代码: - -``` -char char_first_appear_once(const unsigned char *source){ - int hash[256]={0}; - char *tmp = source; - if (tmp == NULL) return '\0'; - while(*tmp != '\0'){ - hash[*tmp]++; - tmp++; - } - - tmp = source; - while(*tmp != '\0'){ - if (hash[*tmp] == 1) return *tmp; - tmp++; - } - return '\0'; -} -``` - - -题目扩展:这里的字符换成整数,整数数量几十TB,海量数据处理,显然hash方法不可能,没有那么大得内容 - ## 统计文章里单词出现的次数 @@ -191,93 +60,6 @@ void statistics(FILE *fd) 如果是海量数据里面统计top-k次数的单词呢? -## 替换空格 - -实现一个函数,把每个空格替换成 "%20",如输入“we are happy”,则输出“we%20are%20happy” - -``` -char *replce_blank(char *source) -``` - -主要问题是一个字符替换成3个字符,替换后的字符串比原串长。 -如果想要在原串上直接修改,就不能顺序替换。且原串的空间应该足够大,能容纳替换变长以后的字符串。如果空间不够,就要新建一块空间来保存替换的结果了。这里假设空间足够 - -1. 第一遍扫描,统计空格个数 n, 替换后的字符串长度 = 原长度+2*n -2. 从后向前扫描字符串,挪动每个字符的位置。注意碰到空格的地方 - - -``` -char *replace_blank(char *source){ - int count = 0; - char *tail = source; - if (source == NULL) return NULL; - while(*tail != '\0'){ - if (*tail == ' ') count++; - tail++; - } - - while(count){ - if(*tail != ' '){ - *(tail+2*count) = *tail; - }else{ - *(tail+2*count) = '0'; - *(tail+2*count-1) = '2'; - *(tail+2*count-2) = '%'; - count--; - } - tail--; - } - - return source; -} -``` - - -## 小写字母排在大写字母的前面 - -有一个由大小写组成的字符串,现在需要对他进行修改,将其中的所有小写字母排在大写字母的前面(大写或小写字母之间不要求保持原来次序),如有可能尽量选择时间和空间效率高的算法 c语言函数原型: - -``` -void proc(char *str) -``` -分析: - -比如:HaJKPnobAACPc,要小写字母前面且不要求保存顺序,可以是:anobcHJKPAACP - -1. 小写字母 a~z 的 ASCII码值是 97~122,A~Z的 ASCII码值是 65~90;0~9 的ASCII码是 48~57 -2. 两边向中间扫描,左边大写右边小写就交换;如果都小写,头指针向前知道找到大写;如果都是大写,尾指针向后找小写; - - -示例代码 - -``` -char *proc(char *str){ - char *start = str; - char *end = str; - if (str == NULL) return NULL; - while(*end != '\0') end++; - end--; - - while(start < end){ - if (*start >= 'A' && *start <= 'Z'){//大写 - if (*end >= 'a' && *end <= 'z'){ - char tmp = *start; - *start = *end; - *end = tmp; - - start++; - } - end--; - }else{//小写 - if (*end >= 'A' && *end <= 'Z'){ - end--; - } - start++; - } - } - return str; -} -``` ## 实现字符串转整型的函数 @@ -305,246 +87,12 @@ int atoi(const char *str){ 详细过程也可以[参考这里](http://blog.csdn.net/v_july_v/article/details/9024123) -## 删除串中指定的字符 - -删除指定的字符以后,后面的字符都要向前移动一位。这种复杂度是O(N^2);那么有没有O(N)的方法呢? - -比如 "abcdeccba" 删除字符 "c"。使用2个指针,一前一后,比较前面的指针和删除字符: - -1. 不相等,两个指针一起跑,且前面的指针值拷贝到后面指针指向的空间 -2. 相等时,快指针向前一步 - -``` -char *delete_occurence_character(char *src , char target){ - char *front = src; - char *rear = src; - while(*front != '\0'){ - if (*front != target){ - *rear = *front; - rear++; - } - front++; - } - *rear = '\0'; - return src; -} -``` - - -## 在字符串中删除特定的字符 - -题目:输入两个字符串,从第一字符串中删除第二个字符串中所有的字符。例如,输入”They are students.”和”aeiou”,则删除之后的第一个字符串变成”Thy r stdnts.”。 - -这是上个题目的升级版本。 - -``` -char *delete_occurence_characterset(char *source,const char *del); -``` - -1. 蛮力法。 遍历字符串,每个字符去删除字符串集合中查找,有就删除 -2. 使用上面的方式,一次遍历 - - - -## 删除字符串中的数字并压缩字符串 - -如字符串”abc123de4fg56”处理后变为”abcdefg”。注意空间和效率。(下面的算法只需要一次遍历,不需要开辟新空间,时间复杂度为O(N)) -这道题跟上一道题也是一个意思。 - - -示例代码: - -``` -char *trim_number(char *source){ - char *start = source; - char *end = source; - if (source == NULL) return NULL; - while(*end != '\0'){ - if (*end < '0' || *end > '9' ){ - *start = *end; - start++; - } - end++; - } - *start = '\0'; - return source; -} -``` - - - - -## 字符串中找出连续最长的数字串 - -写一个函数,功能: - -在字符串中找出连续最长的数字串,并把这个串的长度返回,并把这个最长数字串赋值给其中一个函数参数outputstr所指内存。 - -例如:"abcd12345ed125ss123456789"的首地址传给intputstr后,函数将返回9,outputstr所指的值为123456789 - -它的原形是: - -``` -int longest_continuious_number(const char *input,char *output) -``` - -应该有3个指针,第一个指针指向一个当前最长数字串的第一个数字,第二个指针指向第二个数字串的第一个数字,第三个指针是遍历指针,且统计第二个数字串的长度;当统计出来的长度大于第一个数字串的长度,第一个指针指向第二个指针指向的数字,相反,第二个指针和第三个指针继续向后查找。 - - -1. 当end首次碰到数字时,且tmp=0,说明是首次出现数字,第二个指针移到该数字,继续遍历 -2. 如果数字后面还是数字,tmp!=0 就是第二个数字串,因此 tmp += 1; -3. 当end从数字到普通字符时,如果tmp > max ,就要修改max和第一个指针start ,并把tmp归为0 - - -``` -int longest_continuious_number(const char *input,char *output){ - int max = 0; - char *start= input; - char *mid = input; - char *end = input; - int tmp = 0; - if (input == NULL || output == NULL) return 0; - - while (*end != '\0'){ - if (*end < '0' || *end < '9'){//字母 - if(tmp > max){ - max = tmp; - start = mid; - } - tmp = 0; - }else{//数字 - if (tmp == 0){//发现数字 - mid = end; - } - tmp++; - } - end++; - } - - //修改已数字结尾的bug - if(tmp > max){ - max = tmp; - start = mid; - } - - //copy - int i=0; - while(i strstr(str1,str2) 判断str2是否是str1的子串。 - -``` -/* -@ret 有就返回第一次出现子串的地址,否则返回NULL -*/ -char *strstr(const char *source, const char *target){ - -} -``` ## 匹配兄弟字符串 +什么是兄弟字符串? + 如果两个字符串的字符一样,但是顺序不一样,被认为是兄弟字符串,问如何在迅速匹配兄弟字符串(如,bad和adb就是兄弟字符串)。 思路:判断各自素数乘积是否相等。更多方法请参见:http://blog.csdn.net/v_JULY_v/article/details/6347454。 @@ -592,17 +140,6 @@ int isBrother(const char *first,const char *secd){ 给定一个字符串的集合,格式如:{aaa bbb ccc}, {bbb ddd},{eee fff},{ggg},{ddd hhh}要求将其中交集不为空的集合合并,要求合并完成后的集合之间无交集,例如上例应输出{aaa bbb ccc ddd hhh},{eee fff}, {ggg}。 - - - -## 编写strcpy 函数 - -已知strcpy 函数的原型是: -``` -char *strcpy(char *strDest, const char *strSrc); -``` -其中strDest 是目的字符串,strSrc 是源字符串。不调用C++/C 的字符串库函数 - diff --git a/9 Algorithms Job Interview/1.1 字符串-查找.md b/9 Algorithms Job Interview/1.1 字符串-查找.md new file mode 100644 index 0000000..e9eb47d --- /dev/null +++ b/9 Algorithms Job Interview/1.1 字符串-查找.md @@ -0,0 +1,188 @@ +#字符串-查找 + +* 子串查找 + + + +## 找到第一个只出现一次的字符 + +在一个字符串中找到第一个只出现一次的字符。如输入ahbaccdeff,则输出h。 + +``` +char char_first_appear_once(const char *source) +``` + +思路一: 蛮力统计, O(n^2)复杂度 +思路二: 使用hash表,2次扫描,第一次建立hash表 key为字符,value为出现次数;第二次扫描找到第一个value为1的key,时间复杂度O(n) + +hash表长度 256,字符直接作为key值。需要注意的是 char 的范围是 -128~127,unsigned char 才是0~255 + + +示例代码: + +``` +char char_first_appear_once(const unsigned char *source){ + int hash[256]={0}; + char *tmp = source; + if (tmp == NULL) return '\0'; + while(*tmp != '\0'){ + hash[*tmp]++; + tmp++; + } + + tmp = source; + while(*tmp != '\0'){ + if (hash[*tmp] == 1) return *tmp; + tmp++; + } + return '\0'; +} +``` + + +题目扩展:这里的字符换成整数,整数数量几十TB,海量数据处理,显然hash方法不可能,没有那么大得内容 + + +## 请编写能直接实现strstr()函数功能的代码。 + +> strstr(str1,str2) 判断str2是否是str1的子串。 + +``` +/* +@ret 有就返回第一次出现子串的地址,否则返回NULL +*/ +char *strstr(const char *source, const char *target){ + +} +``` + + +### 字符串中找出连续最长的数字子串 + +写一个函数,功能: + +在字符串中找出连续最长的数字串,并把这个串的长度返回,并把这个最长数字串赋值给其中一个函数参数outputstr所指内存。 + +例如:"abcd12345ed125ss123456789"的首地址传给intputstr后,函数将返回9,outputstr所指的值为123456789 + +它的原形是: + +``` +int longest_continuious_number(const char *input,char *output) +``` + +应该有3个指针,第一个指针指向一个当前最长数字串的第一个数字,第二个指针指向第二个数字串的第一个数字,第三个指针是遍历指针,且统计第二个数字串的长度;当统计出来的长度大于第一个数字串的长度,第一个指针指向第二个指针指向的数字,相反,第二个指针和第三个指针继续向后查找。 + + +1. 当end首次碰到数字时,且tmp=0,说明是首次出现数字,第二个指针移到该数字,继续遍历 +2. 如果数字后面还是数字,tmp!=0 就是第二个数字串,因此 tmp += 1; +3. 当end从数字到普通字符时,如果tmp > max ,就要修改max和第一个指针start ,并把tmp归为0 + + +``` +int longest_continuious_number(const char *input,char *output){ + int max = 0; + char *start= input; + char *mid = input; + char *end = input; + int tmp = 0; + if (input == NULL || output == NULL) return 0; + + while (*end != '\0'){ + if (*end < '0' || *end < '9'){//字母 + if(tmp > max){ + max = tmp; + start = mid; + } + tmp = 0; + }else{//数字 + if (tmp == 0){//发现数字 + mid = end; + } + tmp++; + } + end++; + } + + //修改已数字结尾的bug + if(tmp > max){ + max = tmp; + start = mid; + } + + //copy + int i=0; + while(i '9' ){ + *start = *end; + start++; + } + end++; + } + *start = '\0'; + return source; +} +``` + + diff --git a/9 Algorithms Job Interview/1.3 字符串-修改.md b/9 Algorithms Job Interview/1.3 字符串-修改.md new file mode 100644 index 0000000..40ccc9e --- /dev/null +++ b/9 Algorithms Job Interview/1.3 字符串-修改.md @@ -0,0 +1,230 @@ +# 字符串-修改 + + + + + +## 翻转句子中单词的顺序 + +题目:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。句子中单词以空格符隔开。为简单起见,标点符号和普通字母一样处理。 +例如输入“I am a student.”,则输出“student. a am I”。 + +``` +char *revert_by_word(char *source); +``` + +思路: + +* 原地逆序,字符串2边的字符逐个交换 , 再按单词逆序; +* 也可以先按单词逆序,再对整个句子逆序; + +针对不允许临时空间的情况,也就是字符交换不用临时空间,可以使用的方法有: + +1. 异或操作 +2. 也就是2个整数相互交换一个道理 + +``` +char a = 'a', b = 'b'; +a = a + b; +b = a - b; +a = a - b; +``` + +最终示例代码: + +``` +//反转 +void _reverse(char *start,char *end){ + if ((start == NULL) || (end == NULL)) return; + while(start < end){ + char tmp = *start; + *start = *end; + *end = tmp; + + start++, + end--; + } +} + +char *revert_by_word(char *source){ + char *end = source; + char *start = source; + if (source == NULL) return NULL; + + //end指针挪动到尾部 + while (*end != '\0') end++; + end--; + + //先全部反转 + _reverse(start,end); + + //按单词反转 + start=end=source; + while(*start != '\0'){ + if (*start == ' '){ + start++; + end++; + }else if(*end == ' ' || *end == '\0'){ + _reverse(start,end-1); + start = end; + }else{ + end++; + } + } + return source; +} +``` + +类似的题目还有: + +不开辟用于交换数据的临时空间,如何完成字符串的逆序 +用C语言实现一个revert函数,它的功能是将输入的字符串在原串上倒序后返回。 + + +## 左旋转字符串 + +>字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。 + +如把`字符串abcdef`左旋转2位得到`字符串cdefab` 。请实现字符串左旋转的函数。要求时间对长度为n的字符串操作的复杂度为O(n),辅助内存为O(1)。 + +``` +char *left_rotate(char *str,int offset){ + +} +``` + +思路: 我们可以abcdef分成两部分,ab和cdef,内部逆序以后,整体再次逆序,就可以得到想要的结果了。 +也就是跟上面的问题是同样的问题。 + + + +## 替换空格 + +实现一个函数,把每个空格替换成 "%20",如输入“we are happy”,则输出“we%20are%20happy” + +``` +char *replce_blank(char *source) +``` + +主要问题是一个字符替换成3个字符,替换后的字符串比原串长。 +如果想要在原串上直接修改,就不能顺序替换。且原串的空间应该足够大,能容纳替换变长以后的字符串。如果空间不够,就要新建一块空间来保存替换的结果了。这里假设空间足够 + +1. 第一遍扫描,统计空格个数 n, 替换后的字符串长度 = 原长度+2*n +2. 从后向前扫描字符串,挪动每个字符的位置。注意碰到空格的地方 + + +``` +char *replace_blank(char *source){ + int count = 0; + char *tail = source; + if (source == NULL) return NULL; + while(*tail != '\0'){ + if (*tail == ' ') count++; + tail++; + } + + while(count){ + if(*tail != ' '){ + *(tail+2*count) = *tail; + }else{ + *(tail+2*count) = '0'; + *(tail+2*count-1) = '2'; + *(tail+2*count-2) = '%'; + count--; + } + tail--; + } + + return source; +} +``` + + + + +## 小写字母排在大写字母的前面 + +有一个由大小写组成的字符串,现在需要对他进行修改,将其中的所有小写字母排在大写字母的前面(大写或小写字母之间不要求保持原来次序),如有可能尽量选择时间和空间效率高的算法 c语言函数原型: + +``` +void proc(char *str) +``` +分析: + +比如:HaJKPnobAACPc,要小写字母前面且不要求保存顺序,可以是:anobcHJKPAACP + +1. 小写字母 a~z 的 ASCII码值是 97~122,A~Z的 ASCII码值是 65~90;0~9 的ASCII码是 48~57 +2. 两边向中间扫描,左边大写右边小写就交换;如果都小写,头指针向前知道找到大写;如果都是大写,尾指针向后找小写; + + +示例代码 + +``` +char *proc(char *str){ + char *start = str; + char *end = str; + if (str == NULL) return NULL; + while(*end != '\0') end++; + end--; + + while(start < end){ + if (*start >= 'A' && *start <= 'Z'){//大写 + if (*end >= 'a' && *end <= 'z'){ + char tmp = *start; + *start = *end; + *end = tmp; + + start++; + } + end--; + }else{//小写 + if (*end >= 'A' && *end <= 'Z'){ + end--; + } + start++; + } + } + return str; +} +``` + + + + + +## 字符串原地压缩 + +题目描述:“abeeeeegaaaffa" 压缩为 "abe5ag3f2a",请编程实现。 + +这道题需要注意: +1. 单个字符不压缩 +2. 注意考虑压缩后某个字符个数是多位数(超过10个) +3. 原地压缩最麻烦的地方就是数据移动 + + +这是使用2个指针,一前一后,如果不相等,都往前移动一位;如果相等,后一位变为数字2,且移动后面的指针一位,任然相等则数字加1,不相等 + + +``` +char *compress(const char *src,char *dest){ + +} +``` + +上面的压缩算法可以看到,压缩算法的`效率`验证依赖`给定字符串的特性`,如果'aaaaaaaa....aaa' 这样特征的字符串,使用上面的压缩算法,压缩率接近100%,相反,可能会的0%的压缩率。 + + + + + +## 编写strcpy 函数 + +已知strcpy 函数的原型是: +``` +char *strcpy(char *strDest, const char *strSrc); +``` +其中strDest 是目的字符串,strSrc 是源字符串。不调用C++/C 的字符串库函数 + + + +