- 1问题: 把字符串压缩,比如aaabbbbc, 压缩后成为:a3b4c1。
分析:
这题很简单,我们只需要从头到尾遍历一遍字符串即可。首先设置一个计数器count, 每次“指针移位”的时候,判断当前字符是否与前一个字符相等,如果相等,count++, 指针继续下移,否则,我们需要对前面已经遍历的字符串进行处理,然后重新初始化count,直到字符串遍历结束。这题的关键是对最后一个字符的处理。
- public static String compress(char[] array) {
- if (array == null || array.length == 0 ) return null;
- int count = 1;
- StringBuilder sb = new StringBuilder(); //save the compressed string
- for (int index = 1; index < array.length; index++) {
- if (array[index] == array[index - 1]) {
- count++;
- } else {
- sb.append(array[index - 1]);
- sb.append(count);
- count = 1; //重新初始化count
- }
- }
- //important! add the last character to the stringbuilder.
- sb.append(array[array.length - 1]);
- sb.append(count);
- return sb.toString();
- }
对于一个已经排好序的数组,去除里面重复的元素,比如A = {1,2,2,2,3,3,4,4}, 去掉重复以后,就变成A = {1,2,3,4}.
原理非常简单,关键是写代码的时候注意“指针”位置,和如何比较重复,参见代码里的第一个for循环。
- public int[] uniqueArray(int[] array) {
- if (array.length == 0) return null;
- if (array.length == 1) return array;
- int pointer = 0;
- for (int i = 1; i < array.length; i++) {
- if (array[i] != array[pointer]) { // a[0]不用管,
- pointer++; //若a[1]!=a[0],则a[1]=a[1](pointer=1).同理a[2]!=a[1],则a[2]=a[2](pointer=2)
- array[pointer] = array[i];
- }
- }
- //copy the data to another array 返回一个新数组
- int[] uniArray = new int[pointer+1];
- for (int i = 0; i <= pointer; i++) {
- uniArray[i] = array[i];
- }
- return uniArray;
- }
- 3问题:在一个字符串中找到第一个只出现一次的字符(桶配需)
给定一个字符串,比如 A = “ABCDACD”, 找出第一个只出现一次的字符,在A中,第一个只出现一次的字符是‘B’。
分析:
为了判定某字符是否出现一次,我们可以从从头开始往下遍历,如果没有重复,则选取,否则抛弃。这样做的话复杂度为 O(n^2)。其实,对于判定是否存在或者存在的次数等问题,基本上都会与hastable有关,我们可以构建一个数组 array[256] (ASCII), 然后对字符串先进行处理,对于每个出现的字符,我们可以在相对应的位置+1。这样,我们再次从头开始对字符串进行遍历,如果发现某一字符个数为1,则返回该字符。
代码如下:
- //if there is no such character in A, return 0 (null)
- public char firstOnlyCharacter(String A) {
- int[] array = new int[256]; // 先构建一个整型数组
- //store the characters in A to array
- for (int i = 0; i < A.length(); i++) {
- array[A.charAt(i)] += 1; //将字符串中的字符的ASCii码作为上面构建数组的小标,若A为字母字符串,出现俩次A,则array[A]=2
- }
- //get the first charater with only one appearance in A
- for (int i = 0; i < A.length(); i++) {
- if (array[A.charAt(i)] == 1) return A.charAt(i); // 返回Array[“字母”]=1时对应的 “字母”
- }
- return 0;
- }
最简单的办法,就是先排序,然后把第K个值找出来,这样算法的复杂度为 O(nlgn).
其实还有一种更简单的方法,其平均复杂度 为 O(lgn),当然,最坏是 O(n^2). 这种算法就是利用了快排 quicksort 和二分查找 的做法。先把数组分成两个部分,左边一个部分比pivot小,另一边比pivot大。然后再观察pivot的位置,如果pivot的位置比 K大,那么那个第K个值就一定在pivot的左边,同理,可得其它情况。
- public void findKthLargest(int left, int right, int[] array, int k) {
- int tempK = partition(left, right, array);
- if (tempK == k-1){
- System.out.println(array[tempK]);
- } else if (tempK > k-1) {
- findKthLargest(left, tempK-1, array, k);
- } else {
- findKthLargest(tempK+1, right, array, k);
- }
- }
- // partition the array
- public int partition(int left, int right, int[] array) {
- Random rd = new Random();
- int pivot = rd.nextInt(right - left + 1) + left ;
- exchange(pivot, right, array);
- int startIndex = left;
- for (int tempIndex = left; tempIndex < right; tempIndex++) {
- if (array[tempIndex] < array[right]) {
- exchange(tempIndex, startIndex, array);
- startIndex++;
- }
- }
- exchange(right, startIndex, array);
- //the index of the pivot
- return startIndex;
- }
输入一个正数n,输出所有和为 n 的连续正数序列。例如:输入15,由于1+2+3+4+5 = 4+5+6 = 7+8=15,所以输出3个连续序列1-5、4-6和7-8。
分析:
首先,假设我们是从6开始判断的,因为 6 比 15 小, 而且由于要求序列是连续的,所以,下一次只能选择5,剩余的值是 15 - 6 = 9,因为5 比9 小,我们只能再继续下去,这次选择是 4, 而剩余的值是 9 - 5 = 4。因为剩余的值刚好等于当前值4,所以,我们在这里结束。
什么时候退出呢?只能在当前值比剩余值 大 或者 当前值已经比 1 小的情况下。
代码如下:
- public static void sequentialSum( int i, int remaining, LinkedList<Integer> list) {
- //exit
- if (i < 1 || i > remaining) return; // i为当前值,当前值小于1 或 当前值大于剩余值, ,退出
- //meet the requirement
- if (i == remaining) { // remaining为剩余值
- list.add(i);
- print(list);
- }
- //doesn't meet the requirement, but we should continue;
- else {
- list.add(i);
- sequentialSum(i - 1, remaining - i, list);
- }
- }
那么,我们从什么地点开始递归呢?我们可以从15开始递归,但是,如果我们稍微观察,连续序列的最大值不可能大于 (n/2 + 1), 这里 n = 15。 自己可以思考一下。
代码如下:
- public static void findAllSum(int value) {
- for (int j = value/2 + 1; j > 0; j--) {
- 循环过程 7 7
- //当value=15时,j=8, sequentialSum(8,15,List) sequentialSum(8-1,15-8,List) 相等,而打印
- //当value=15, j=7, sequentialSum(7,15,List) sequentialSum(7-1,15-7,List) sequentialSum(6-1,8-6,List) 当前值i大于剩余值remaing退出
- 6 8 5 2
- LinkedList<Integer> list = new LinkedList<Integer>();
- sequentialSum(j, value, list);
- }
- }
输入两个整数 n 和 sum,从数列1, 2, 3, ... , n 中 随意取几个数,使其和等于 sum,要求将其中所有的可能组合列出来.
比如n = 5, sum = 8, 那么所有的组合为:
3 5 或者 1 5 2 或者 1 4 3
思路:
对于一个给定的总和sum, 如果我们从数列的尾部开始,即从n开始,sum 要么等于 n 加上 剩余的1到 n - 1之间的某一个组合,或者 等于 1 到 n-1 之间的某一个组合。
递归的方程为:
findSum(sum, n) = n + findSum(sum - n, n -1) 或者 findSum (sum, n) = findSum (sum, n -1)
- public class FindSum {
- private static Stack<Integer> stack = new Stack<Integer>();
- public static void findSum(int sum, int n) {
- //找不到满足条件的情况
- if ( sum <= 0 || n <= 0) return;
- //sum 大于 n, 需要再次递归
- if (sum > n) {
- stack.push(n); //取 n
- findSum(sum - n, n - 1);// n加入,取 n = n-1, sum = sum-n
- stack.pop(); // 不取 n
- findSum(sum, n - 1); // n没有加入,取n = n-1,sum = sum
- } else {
- System.out.print(sum); // sum <= n ,直接输出n就可以满足了
- for (int i = 0; i < stack.size(); i ++)
- System.out.print(" "+ stack.get(i));
- System.out.println();
- //上面部分得到的是一种情况,但是还存在另一种情况,我们需要找出从 1 到 sum - 1 里面是否存在值,它们的和等于sum
- if (sum != 1) {
- findSum(sum, sum - 1);
- }
- }
- }
- public static void main(String[] args) {
- int sum = 8;
- int n = 5;
- findSum(sum,n);
- }
- }
- 7回文(对应的字符相等否)相关问题(字符串反转 (对应的字符交互))
7.1给一个数字,判断该数字是否是回文。比如 1221是回文,而123不是。
判断一个数字是否是回文,我们可以先把它转成字符串,然后根据回文“对称”的特性进行判断:查看第 x 个字符是否与第 n - x + 1 字符是否相等 (x 从1开始,n/2 结束, n 是指数字长度)。但是,这道题因为是数字,有一种更好的解法。即根据原始数字,通过数学方法构建出它的回文数字,如果原始数字大小等于回文数字大小,那么该数字是回文,否则不是。
代码如下:
// 回文判断-----法一public static Boolean is_huiwen(String s) {
char[] at = s.toCharArray();
int len = at.length;
for (int i = 0; i < len / 2; i++) {
return true;
}
return false;
}
return false;
// 回文判断-----法二
-
bool isPalindrome(int originalNumber) { - int palindrome = 0;
- int origin = originalNumber;
- // get the palindrome
- while(originalNumber != 0) {
- palindrome = palindrome * 10 + originalNumber % 10;
- originalNumber /= 10;
- }
- return palindrome == origin ;
- }
// 回文判断-----法三
代码如下:
方法1:
{
Stack s = new Stack();
for ( int i = 0 ; i < str.Length; i ++ )
{
s.Push(str.Substring(i, 1 ));
}
bool result = true ;
int index = 0 ;
while (s.Count > 0 )
{
string ch = s.Pop().ToString();
if (ch != str.Substring(index, 1 ))
{
result = false ;
break ;
}
index ++ ;
}
return result;
}
问题:
给你一个字符串,找出该字符串中对称的子字符串的最大长度。
思路:
首先,我们用字符数组 char[] array 来保持这个字符串,假设现在已经遍历到第 i 个字符,要找出以该字符为“中心”的最长对称字符串,我们需要用另两个指针分别向前和向后移动,直到指针到达字符串两端或者两个指针所指的字符不相等。因为对称子字符串有两种情况,所以需要写出两种情况下的代码:
1. 第 i 个字符是该对称字符串的真正的中心,也就是说该对称字符串以第 i 个字符对称, 比如: “aba”。(奇数个)代码里用 index 来代表 i.
- public static int maxLengthMiddle(char[] array, int index) {
- int length = 1; //最长的子字符串长度
- int j = 1; //前后移动的指针
- while ((array[index - j] == array[index + j]) && (index - j) >= 0 && array.length > (index + j)) {
- length += 2;
- j++;
- }
- return length;
- }
2. 第 i 个字符串是对称字符串的其中一个中心。比如“abba”。(偶数个)
- public static int maxLengthMirror(char[] array, int index) {
- int length = 0; //最长的子字符串长度
- int j = 0; //前后移动的指针
- while ((array[index - j] == array[index + j + 1]) && (index - j) >= 0 && array.length > (index + j + 1)){
- length += 2;
- j++;
- }
- return length;
- }
有了这样两个函数,我们只需要遍历字符串里所有的字符,就可以找出最大长度的对称子字符串了。
- public static int palindrain(char[] array) {
- if (array.length == 0) return 0;
- int maxLength = 0;
- for (int i = 0; i < array.length; i++) {
- int tempMaxLength = - 1;
- int length1 = maxLengthMiddle(array, i);
- int length2 = maxLengthMirror(array, i);
- tempMaxLength = (length1 > length2) ? length1 : length2;
- if (tempMaxLength > maxLength) {
- maxLength = tempMaxLength;
- }
- }
- return maxLength;
- }
有一种可以把复杂度降到O(N)的算法,但是这个算法要利用 suffix tree, 有兴趣的可以搜索一下。
7.3 O(n)最大回文子串算法
这里,我介绍一下O(n)回文串处理的一种方法。Manacher算法.
原文地址:
http://zhuhongcheng.wordpress.com/2009/08/02/a-simple-linear-time-algorithm-for-finding-longest-palindrome-sub-string/
其实原文说得是比较清楚的,只是英文的,我这里写一份中文的吧。
首先:大家都知道什么叫回文串吧,这个算法要解决的就是一个字符串中最长的回文子串有多长。这个算法可以在O(n)的时间复杂度内既线性时间复杂度的情况下,求出以每个字符为中心的最长回文有多长,
这个算法有一个很巧妙的地方,它把奇数的回文串和偶数的回文串统一起来考虑了。这一点一直是在做回文串问题中时比较烦的地方。这个算法还有一个很好的地方就是充分利用了字符匹配的特殊性,避免了大量不必要的重复匹配。
算法大致过程是这样。先在每两个相邻字符中间插入一个分隔符,当然这个分隔符要在原串中没有出现过。一般可以用‘#’分隔。这样就非常巧妙的将奇数长度回文串与偶数长度回文串统一起来考虑了(见下面的一个例子,回文串长度全为奇数了),然后用一个辅助数组P记录以每个字符为中心的最长回文串的信息。P[id]记录的是以字符str[id]为中心的最长回文串,当以str[id]为第一个字符,这个最长回文串向右延伸了P[id]个字符。
原串: w aa bwsw f d
新串: # w# a # a # b# w # s # w # f # d #
辅助数组P: 1 2 1 2 3 2 1 2 1 2 1 4 1 2 1 2 1 2 1
这里有一个很好的性质,P[id]-1就是该回文子串在原串中的长度(包括‘#’)。如果这里不是特别清楚,可以自己拿出纸来画一画,自己体会体会。当然这里可能每个人写法不尽相同,不过我想大致思路应该是一样的吧。
好,我们继续。现在的关键问题就在于怎么在O(n)时间复杂度内求出P数组了。只要把这个P数组求出来,最长回文子串就可以直接扫一遍得出来了。
由于这个算法是线性从前往后扫的。那么当我们准备求P[i]的时候,i以前的P[j]我们是已经得到了的。我们用mx记在i之前的回文串中,延伸至最右端的位置。同时用id这个变量记下取得这个最优mx时的id值。(注:为了防止字符比较的时候越界,我在这个加了‘#’的字符串之前还加了另一个特殊字符‘$’,故我的新串下标是从1开始的)
好,到这里,我们可以先贴一份代码了。
复制代码
|

另外,顺便附一份AC代码。
http://acm.hust.edu.cn:8080/judge/problem/viewSource.action?id=140283
8 输出字符串的所有组合,如abc,输出:a,b,c,ab,ac,bc,abc (2 的n次方减1)
方法1 给定一个字符串,比如ABC, 把所有的组合,即:A, B, C, AB, AC, BC, ABC, 都找出来。
思路:
假设我们想在长度为n的字符串中求m个字符的组合。我们先从头扫描字符串的第一个字符。针对第一个字符,我们有两种选择:一是把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选取m-1个字符;二是不把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选择m个字符。这两种选择都很容易用递归实现
。
字符串的排列:
- public static void combiantion(char chs[]){
- if(chs.length == 0) return ;
- Stack<Character> stack = new Stack<Character>();
- for(int i = 1; i <= chs.length; i++){
- combine(chs, 0, i, stack);
- }
- }
- //从字符数组中第begin个字符开始挑选number个字符加入list中
- public static void combine(char []chs, int begin, int number, Stack<Character> stack){
- if(number == 0){
- System.out.println(stack.toString());
- return ;
- }
- if(begin == chs.length){
- return;
- }
- stack.push(chs[begin]);
- //把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选取m-1个字
- combine(chs, begin + 1, number - 1, stack);
- stack.pop();
- //不把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选择m个字符。
- combine(chs, begin + 1, number, stack);
- }
方法2 利用001 ABC 中只有C出现
本题假定字符串中的所有字符都不重复。根据题意,如果字符串中有n个字符,那么总共要输出2^n – 1种组合。这也就意味着n不可能太大,否则的话,以现在CPU的运算速度,程序运行一次可能需要跑几百年、几千年,而且也没有那么大的硬盘来储存运行结果。因而,可以假设n小于一个常数(比如64)。
本题最简洁的方法,应该是采用递归法。遍历字符串,每个字符只能取或不取。取该字符的话,就把该字符放到结果字符串中,遍历完毕后,输出结果字符串。
n不是太小时,递归法效率很差(栈调用次数约为2^n,尾递归优化后也有2^(n-1))。注意到本题的特点,可以构照一个长度为n的01字符串(或二进制数)表示输出结果中最否包含某个字符,比如:"001"表示输出结果中不含字符a、b,只含c,即输出结果为c,而"101",表示输出结果为ac。原题就是要求输出"001"到"111"这2^n – 1个组合对应的字符串。
//迭代法
void all_combine(const char str[])
{
if (str == NULL || *str == 0) return;
const size_t max_len = 64;
size_t len = strlen(str);
if (len >= max_len ) {
puts("输入字符串太长。\n你愿意等我一辈子吗?");
return;
}
bool used[max_len] = {0}; //可以用一个64位无符号数表示used数组
char cache[max_len];
char *result = cache + len;
*result = 0;
while (true) {
size_t idx = 0;
while (used[idx]) { //模拟二进制加法,一共有2^len – 1个状态
used[idx] = false;
++result;
if (++idx == len) return;
}
used[idx] = true;
*--result = str[idx];
puts(result);
}
}
//递归解法
static void all_combine_recursive_impl(const char* str, char* result_begin, char* result_end)
{
if (*str == 0) {
*result_end = 0;
if (result_begin != result_end) puts(result_begin);
return;
}
all_combine_recursive_impl(str + 1, result_begin, result_end); //不取*str
*result_end = *str;
all_combine_recursive_impl(str + 1, result_begin, result_end + 1); //取*str
}
void all_combine_recursive(const char str[])
{
if (str == NULL) return;
const size_t max_len = 64;
size_t len = strlen(str);
if (len >= max_len ) {
puts("输入字符串太长。\n你愿意等我一辈子吗?");
return;
}
char result[max_len];
all_combine_recursive_impl(str, result, result);
}
9 关于字符串排列的问题
输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串abc,则输出由字符a、b、c所能排列出来的所有字符串abc、acb、bac、bca、cab和cba。
可以这样想:固定第一个字符a,求后面两个字符bc的排列。当两个字符bc的排列求好之后,我们把第一个字符a和后面的b交换,得到bac,接着我们固定第一个字符b,求后面两个字符ac的排列。现在是把c放到第一位置的时候了。记住前面我们已经把原先的第一个字符a和后面的b做了交换,为了保证这次c仍然是和原先处在第一位置的a交换,我们在拿c和第一个字符交换之前,先要把b和a交换回来。在交换b和a之后,再拿c和处在第一位置的a进行交换,得到cba。我们再次固定第一个字符c,求后面两个字符b、a的排列。这样写成递归程序如下:
- public class Permutation {
- public static void permutation(char[]ss,int i){
- if(ss==null||i<0 ||i>ss.length){
- return;
- }
- if(i==ss.length){
- System.out.println(new String(ss));
- }else{
- for(int j=i;j<ss.length;j++){
- char temp=ss[j];//交换前缀,使之产生下一个前缀
- ss[j]=ss[i];
- ss[i]=temp;
- permutation(ss,i+1);
- temp=ss[j]; //将前缀换回来,继续做上一个的前缀排列.
- ss[j]=ss[i];
- ss[i]=temp;
- }
- }
- }
- public static void main(String args[]){
- char []ss={'a','c','b','d'};
- permutation(ss,0);
- }
- }