算法学习【3】字符串相关含KMP算法

面试经常会考察String相关问题,现把相关题目整理如下:

1、返回逆序字符串,如,"abc"->"cba",要求空间复杂度为O(1)

     思路:首尾字符依次交换

public static char[] inverseOrder(char[] str, int st, int fh){
		int len = fh-st+1;
		char temp;
		for(int i=0;i<len/2;i++){
			temp = str[st+i];
			str[st+i]=str[fh-i];
			str[fh-i]=temp;
		}
		return str;
	}

2、逆序输出单词,如"i am a boy"->"boy a am i",要求空间复杂度为O(1)

     思路:首先将整个String逆序,然后每个单词再逆序,或者先将每个单词再逆序,再将整个String逆序

public static String inverseWords(String str){
		char[] s = str.toCharArray();
		int begin=0;
		for(int i=0;i<s.length;i++){
			if(s[i]==' '){
				inverseOrder(s,begin,i-1);
				begin = i+1;
			}
		}
		inverseOrder(s,begin,s.length-1);
		inverseOrder(s,0,s.length-1);
		return String.valueOf(s);
	}

     若不是要求额外空间复杂度,用栈Stack更好。


3、给定一个字符串str和一个int N,要求str[0:N]和str[N+i,str.length()-1]左右替换位置,要求额外空间复杂度为O(1)

    思路与2中逆序输出单词一样,先将左边str[0:N]逆序,再将右边str[N+i,str.length()-1]逆序,再将整个逆序。


4、判断字符串是否互为旋转词,字符串左边任意长度的子字符转放到右边形成的新字符串即旋转词,

    如"abcd"->"bcda"->"cdab"->"dabc"都互为旋转词

    要求时间复杂度为O(n)

    思路将第一个字符串连续写两次,即"abcdabcd",再判断第二个字符串是否在"abcdabcd"中,匹配的过程可以用"KMP"算法,示例程序暂不用

public static boolean isRotateWord(String w1, String w2){
		if(w1.length()!=w2.length())
			return false;
		String s = w1+w1;
		for(int i=0;i<w1.length();i++){
			String temp = s.substring(i,i+w1.length());
			if(temp.equals(w2)){
				return true;
			}
		}
		return false;
	}

5、KMP算法做字符串匹配

     在第四题中,要求时间复杂度为O(n),若直接用4的代码,肯定不符合。因为每一次temp.equals(w2)时,都要进行w2.length()次操作对比每个字符。

     经常需要判断一个长字符串中是否存在指定字符串,即字符匹配问题,一般采用KMP算法。

     KMP算法的理解可参考http://www.cnblogs.com/c-cloud/p/3224788.html

     这里以示例对KMP算法进行说明:

判断字符串String s = "123512012345"中是否存在子字符串String m = "1234",若存在则返回第一个字符索引,不存在返回-1。

1 2 3 5 1 2 0 1 2 3 4 5
1 2 3 4
  1 2 3 4
    1 2 3 4
      1 2 3 4
        1 2 3 4

若按4中的函数依次判断s的每个substring是否与m相等,过程如上。

第2行与第1行比较后,发现s[3]与m[3]不相等,因此将m后移,已经比较了m的前三位是相等,而由m自身特性可知1与第2位、第3位不相等,因此可以直接将m右移3位,而不用一位一位的移动。

KMP算法的核心就是利用m的自身特性,实现多位移动,利用的特性即 “相同前缀后缀的最大长度”。

m右移位数 = 比较后相同子字符串的位数 - 该相同子字符串对应的部分匹配值

可知第一次,比较后相同子字符串为"123",比较后相同子字符串的位数 = 3,"123"的部分匹配值为0,因此右移3位。

子字符串部分匹配值 = 该子字符串的前缀和后缀中的最长相同字符串位数


String s = "1231412312",m = "12312"为例说明:

首先创建m各子字符串的部分匹配值表(前缀不包括最后一位、后缀不包括第一位

“1”:前缀、后缀都为空,部分匹配值=0

“12”:前缀:1,后缀:2,部分匹配值=0

“123”:前缀:1、12,后缀:3、23,部分匹配值=0

“1231”:前缀:1、12、123,后缀:1、31、231,最长相同字符串为“1”,部分匹配值=1

1 2 3 1 4 1 2 3 1 2
1 2 3 1 2
      1 2 3 1 2
        1 2 3 1 2
          1 2 3 1 2
第1次比较:比较后相同子字符串“1231”的位数 = 4,相同子字符串对应的部分匹配值 = 1,m右移位数 = 4 - 1 = 3

第2次比较:比较后相同子字符串“1”的位数 = 1,相同子字符串对应的部分匹配值 = 0,m右移位数 = 1

第3次比较:第一位不相等,后移1位

public static int getIndexOf(String s, String m) {
        if (s.length() < m.length()) {
            return -1;
        }
        char[] ss = s.toCharArray();
        char[] ms = m.toCharArray();
        int[] next;
        
        if(ms.length == 1)
        	next = new int[]{-1};
        else{
	        next = new int[ms.length];
	        next[0] = -1;
	        next[1] = 0;
	        int pos = 2;
	        int cn = 0;
	        while (pos < next.length) {
	            if (ms[pos - 1] == ms[cn]) {
	                next[pos++] = ++cn;
	            } else if (cn > 0) {
	                cn = next[cn];
	            } else {
	                next[pos++] = 0;
	            }
	        }
        }
        int si = 0;
        int mi = 0;
        while (si < ss.length && mi < ms.length) {
            if (ss[si] == ms[mi]) {
                si++;
                mi++;
            } else if (next[mi] == -1) {
                si++;
            } else {
                mi = next[mi];
            }
        }
        return mi == ms.length ? si - mi : -1;
    }


6、给定一个字符串数组,如{"abc", "def"},将该字符串数组中的字符串拼接起来,如"abcdef"或"defabc",使得拼接的字符串在字典中顺序最小。

     要求时间复杂度为O(nlogn)

     例如{"b", "ba"},结果为"bab"

     思路:先拼接再比较


7、判断字符串是否互为变形词。若两个字符串中出现的字符种类相同且每种字符出现的次数相同,则互为变形词。

      思路1:用HashMap表对两个字符串统计不同字符个数

      思路2:使用固定长度的数组来实现,定义成256或65536的数组来统计

import java.util.*;
 
public class Main {
    public boolean chkTransform(String str1, int a, String str2, int b) {
        if (str1 == null || str2 == null || str1.length() != str2.length()) {
            return false;
        }
        char[] chas1 = str1.toCharArray();
        char[] chas2 = str2.toCharArray();
        int[] map = new int[256];
        for (int i = 0; i < chas1.length; i++) {
            map[chas1[i]]++;
        }
        for (int i = 0; i < chas2.length; i++) {
            if (map[chas2[i]]-- == 0) {
                return false;
            }
        }
        return true;
    }
}

8、找出字符串的 最长不重复子串,输出最长不重复子串长度,要求时间复杂度为O(n)。

      这里不重复子串的意思是子字符串没有相同字符,面试时一时理解错误,哎。

      可以参考http://blog.csdn.net/simmerlee/article/details/40953695

      思路:字符在a~z之间,定义一个数组A[26]存放字符最新索引。要求时间复杂度为O(n)即进行一次循环,循环变量即字符索引,在循环里利用两个变量,一个变量begin表示“当前不重复子字符串的起始索引”,一个变量count统计当前不重复子字符串的长度。遇到第一次出现的字符,更新数组A[26]中字符最新索引号,count++;遇到重复字符,判断该字符前一次出现位置是否大于begin,若大于begin,则本次不重复子字符串统计结束,更新begin、count、A,若小于begin,则继续更新A、count++。用Java写的程序如下:

public static String lenOfUnreSubstr(String str){
		int[] A = new int[26]; //字符在a~z之间,数组存放最新索引号
		for(int i=0;i<26;i++)
			A[i] = -1;
		
		int maxbegin = 0;   //最长不重复子字符串的起始索引
		int maxlen= 0;      //最长不重复子字符串的长度
		int count = 0;      //当前不重复子字符串的长度
		int begin = 0;      //当前不重复子字符串的起始索引
		int temp;
		for(int i=0;i<str.length();i++){
			temp = str.charAt(i)-'a';
			if(A[temp]==-1){              //第一次出现的字符,存放索引号
				A[temp] = i;
				count++;
			}else{                        //遇到重复字符
				if(maxlen<count){
					maxbegin = begin;
					maxlen = count;
				}
				if(A[temp]>=begin){       //该重复字符的前一出现在当前不重复子字符串内部
					begin = A[temp]+1;
					count = i-begin+1;
					A[temp] = i;
				}else{                    //该重复字符的前一不在当前不重复子字符串内部
					A[temp] = i;
					count++;
				}
				//System.out.println(i+" "+begin+" "+A[temp]+" "+maxlen);
			}
		}
		if(maxlen<count){
			maxlen = count;               //若最后一串不重复子字符串最长
		}
		
		return str.substring(maxbegin, maxbegin+maxlen);
	}
运行结果
abcbec:abc

adabcbec:dabc

abadadabbc:bad

ffdeefghff:efgh

abcbecghijkl:abcbecghi


9、给定一个字符串,输出最长的重复子串

      可以参考http://dsqiu.iteye.com/blog/1701324

      可以利用KMP算法中,局部匹配值表的最大值就是对应的重复子串。


10、判断一个二叉树是否为另一个二叉树的一部分

        思路:将大的二叉树序列化按LNR、NLR等方式转化成字符串,然后用上面判断KMP算法判断是否为子字符串。


11、大整数数相乘

        可以参考http://www.tuicool.com/articles/zy6vim

       思路:分治思想以及乘法规律AB * CD = AC (AD+BC) BD


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值