字符串中的算法

1. KMP算法查询字符串

问题描述:

使用KMP算法查询子串的位置

代码:

package 字符串;

/**
 * @author: DreamCode
 * @file: KMP.java
 * @time: 2022年3月18日-下午7:28:08
 */
public class KMP {

	public static void main(String[] args) {
		String src = "babababcbabababb";
		int index = indexOf(src, "c");
		System.out.println(index);
	}

	private static int indexOf(String s, String p) {
		// TODO 在s字符串中查询p字符串
		// 初始化next数组
		if(p.length()==0) {
			return -1;
		}
		int[] next = get_next(p);
		int i=0,j=0;
		while(i<s.length()) {
			if(j==-1||s.charAt(i)==p.charAt(j)) {
				i++;
				j++;
			}else {
				j=next[j];
			}
			if(j==p.length()) {
				return i-j;
			}
		}
		return -1;
	}

	private static int[] get_next(String p) {
		// TODO 获取p字符串的next数组
		int len = p.length();
		int[] next = new int[len+1];
		next[0] = -1;  //第0个字符的最长匹配前缀后缀为-1
		if(len==0) {
			return next;
		}
		next[1] = 0;  //第1个字符的最长匹配前缀后缀为0
		int j=1;  //从第1个字符逐步递推next数组
		int k=next[j]; //k为记录最长的匹配前缀后缀长度的位置
		while(j<len) {
			if(k<0||p.charAt(k)==p.charAt(j)) { 
				next[++j]=++k;
			}else {
				k=next[k];
			}
		}
		return next;
	}

}

2. KMP_next数组的应用

题目描述:

For each prefix of a given string S with N characters (each character has an ASCII code between 97 and 126, inclusive), we want to know whether the prefix is a periodic string. That is, for each i (2 <= i <= N) we want to know the largest K > 1 (if there is one) such that the prefix of S with length i can be written as AK ,that is A concatenated K times, for some string A. Of course, we also want to know the period K.

input:

The input consists of several test cases. Each test case consists of two lines. The first one contains N (2 <= N <= 1 000 000) – the size of the string S.The second line contains the string S. The input file ends with a line, having the number zero on it.

output:

For each test case, output “Test case #” and the consecutive test case number on a single line; then, for each prefix with length i that has a period K > 1, output the prefix size i and the period K separated by a single space; the prefix sizes must be in increasing order. Print a blank line after each test case.

思路解析:

字符串呈现周期性:i%(i-k)==0,周期次数为i/(i-k);i为当前匹配的位置,k为前i-1位最长前缀后缀匹配长度(next[i])

package 字符串;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;

/**
 * @author: DreamCode
 * @file: Period.java
 * @time: 2022年3月19日-上午11:38:55
 */
public class next数组的应用_Period {

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		List<String> list = new LinkedList<>();
		while (true) {
			int n = scanner.nextInt();
			if (n == 0)
				break;
			String string = scanner.next();
			list.add(string);
		}
		for (int i = 0; i < list.size(); i++) {
			String string = list.get(i);
			System.out.println("Test case #" + (i + 1));
			int[] next = get_next(string);
			for (int j = 2; j < next.length; j++) {
				int k = next[j];  //k为当前匹配位置的next值
				int d = j - k;
				if (j % d == 0 && j / d > 1) { // 呈现周期性
					int count = j / d;
					System.out.println(j + " " + count);
				}
			}
			System.out.println();
		}

	}

	private static int[] get_next(String string) {
		// TODO 获取字符串的next数组
		if (string == null || string.length() == 0)
			return null;
		int len = string.length();
		int[] next = new int[len + 1];
		next[0] = -1;
		if (len == 1)
			return next;
		next[1] = 0;
		int i = 1;
		int k = next[i];
		while (i < len) {
			if (k == -1 || string.charAt(i) == string.charAt(k))
				next[++i] = ++k;
			else
				k = next[k];
		}
		return next;
	}

}

3. 单词翻转

问题描述:

将字符串按单词翻转,如here you are 翻转成are you here

问题描述:

先将整体字符串翻转,然后将每一个单词再做翻转

package 字符串;

/**
 * @author: DreamCode
 * @file: 单词翻转.java
 * @time: 2022年3月17日-下午12:50:11
 */
public class 单词翻转 {

	public static void main(String[] args) {
		String ans = reverse("here you are");
		System.out.println(ans);

	}

	private static String reverse(String string) {
		string = reverseString(string);
		String[] strArrayStrings = string.split("\\s");  //按照空格切割单词
		StringBuilder ansStringBuilder = new StringBuilder();
		for(String str:strArrayStrings) {
			str = reverseString(str);
			ansStringBuilder.append(str+" ");
		}
		return ansStringBuilder.deleteCharAt(ansStringBuilder.length()-1).toString();
	}

	private static String reverseString(String string) {  //翻转单词
		StringBuilder stringBuilder = new StringBuilder(string);
		return stringBuilder.reverse().toString();
	}

}

4. 后缀数组的应用_最长公共字串

题目描述:

给出一个字符串 S,考虑其所有重复子串(S 的连续子串,出现两次或多次,可能会有重叠)。 返回任何具有最长可能长度的重复子串。(如果 S不含重复子串,那么答案为 “”)。

思路解析:

采用后缀数组法,经过排序后,最长公共字串必然存在相邻的两个后缀字串中,求出最大的公共字串长度与index即可

package 字符串;

import java.util.Arrays;

/**
 * @author: DreamCode
 * @file: 后缀数组的应用_最长公共字串.java
 * @time: 2022年3月19日-下午3:56:45 
 */
public class 后缀数组的应用_最长公共字串 {

	public static void main(String[] args) {
		String string = "123232";
		String res = longestDupSubstring(string);
		System.out.println(res);

	}

	private static String longestDupSubstring(String string) {
		// TODO 求最长公共字串
		//构造后缀数组
		int len = string.length();
		String[] arr = new String[len];
		for(int i=0;i<len;i++) {
			String subString = string.substring(i);
			arr[i]=subString;
		}
		//对后缀数组进行排序
		Arrays.sort(arr);
		//寻找相邻两个字串的最长公共数组高度
		int maxLen = -1;
		String res="";
		for(int i=0;i<arr.length-1;i++) { //构造高度
			int commonLen = getCommonLen(arr[i],arr[i+1]);  //高度
			if(commonLen>maxLen) { //更新最长公共字串
				maxLen=commonLen;
				res=arr[i].substring(0,maxLen);
			}
		}
		return res;
	}

	private static int getCommonLen(String string, String string2) {
		// TODO 求两个字符串的公共字串的长度
		int count=0;
		int len = Math.min(string.length(), string2.length());
		for(int i=0;i<len;i++) {
			if(string.charAt(i)==string2.charAt(i)) {
				count++;
			}else {
				break;
			}
		}
		return count;
	}

}

5. 回文字符串

问题描述:

判断字符串是否为回文字符串

思路解析:

如果字符串(数字)翻转后和自己相同,则该字符串(数字)具有回文特性

package 字符串;

/**
 * @author: DreamCode
 * @file: 回文字符串.java
 * @time: 2022年3月17日-下午8:52:48
 */
public class 回文字符串 {

	public static void main(String[] args) {
		String testString = " 1 ";
		boolean ans = isPalindrome(testString);
		System.out.println(ans);
	}

	private static boolean isPalindrome(String testString) {
		if(testString=="") {
			return true;
		}
		return testString.equals(new StringBuffer(testString).reverse().toString());
	}

}

6. 判断两字符串的字符集是否相等

问题描述:

询问两个串是否由相同的字符集生成

思路解析:

采用Map索引记录出现的字符,然后进行对比

package 字符串;

import java.util.HashMap;
import java.util.Map;

/**
 * @author: DreamCode
 * @file: 判断两字符串的字符集是否相等.java
 * @time: 2022年3月17日-上午11:43:57
 */
public class 判断两字符串的字符集是否相等 {


	public static void main(String[] args) {
		boolean ans = isequal("abcdfv","afdbc");
		System.out.println(ans);
	}

	private static boolean isequal(String string, String string2) {
		// TODO 询问两个串是否由相同的字符集生成
		Map<Character, Integer> record = new HashMap<>();
		for(Character character:string.toCharArray()) {
			if(record.get(character)==null) {
				record.put(character, 1);
			}
		}
		for(Character character:string2.toCharArray()) {
			if(record.get(character)==null) {
				return false;
			}
		}
		return true;
	}

}

7. 旋转词

问题描述:

旋转词的意思是:1234 2341 3412 把左边的任意一部分移到右边去

思路解析:

如果a是b的一个旋转词,那么b+b一定包含a(比如12341234 包含2341,故2341是1234的一个旋转词)

package 字符串;

/**
 * @author: DreamCode
 * @file: 旋转词.java
 * @time: 2022年3月17日-下午12:14:39
 */
public class 旋转词 {

	public static void main(String[] args) {
		boolean ans = isRotate("defa","fabdde");
		System.out.println(ans);
	}

	private static boolean isRotate(String string2, String string) {
		if(string.length()!=string2.length()) {
			return false;
		}
		String string3 = string+string;
		if(string3.contains(string2)) {
			return true;
		}
		return false;
	}

}

8. 移除字符串中连续出现的K个0

问题描述:

移除字符串中连续出现的K个0

思路解析:

可以用扫描字符数组的解法,但是用正则表达式更为快捷。[]代表需要匹配的字符,{}代表需要匹配的字符出现的次数。\s匹配空格

package 字符串;

/**
 * @author: DreamCode
 * @file: 移除字符串中连续出现的K个0.java
 * @time: 2022年3月17日-下午8:39:33
 */
public class 移除字符串中连续出现的K个0 {

	public static void main(String[] args) {
		String testString="A00000B00";
		int k=2;
		String ansString = removeKZero(testString, k);
		System.out.println(ansString);
	}

	private static String removeKZero(String testString, int k) {
		String regexString = "0{"+ k + "}";
		return testString.replaceAll(regexString,"");
	}

}

9. 最短摘要

问题描述:

Alibaba笔试题:给定一段产品的英文描述,包含M个英文字母,每个英文单词以空格分隔,无其他标点符号;再给定N个英文单词关键字,请说明思路并编程实现方法,目标是找出此产品描述中包含N个关键字(每个关键词至少出现一次)的长度最短的子串,作为产品简介输出。

思路解析:

尺取法,每一次满足约束条件,判断尺子的长度,若更小则更新答案。

package 字符串;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: DreamCode
 * @file: 最短摘要_尺取法.java
 * @time: 2022年3月17日-下午9:26:14
 */
public class 最短摘要_尺取法 {
	public static void main(String[] args) {
		String[] strArrays = { "a", "b", "c", "seed", "h", "e", "f", "c", "c", "seed", "e", "f", "seed", "c" };
		String[] keyArray = { "c","seed","c","c" };
		solve(strArrays, keyArray);

	}

	private static void solve(String[] strArrays, String[] keyArray) {
		int begin = -1;
		int end = -1;
		int len = Integer.MAX_VALUE;
		int p = -1;
		for (int i = 0; i < strArrays.length; i++) {
			if (find(strArrays[i], keyArray) != -1) {  // 判断当前字符是否是key,当前字符串为关键字,找到尺子左边界
				int j;
				if (p != -1) { // 尺取法,若上一次有取到,则下一次的j为p
					j = p;
				} else {
					j = i + 1; // 第一次的情况处理(第一个尺子)
				}
				for (; j < strArrays.length; j++) { // 寻找尺子右边界
					if (find(strArrays[j], keyArray) != -1) { // 右指针字符为关键字
						if (containtAll(strArrays, keyArray, i, j)) { // 当前包含所有关键字
							if (j - i + 1 < len) { // 当前为最短串,进行更新
								len = j - i + 1;
								begin = i;
								end = j;
							}
							break;
						}
					}
				}
			}
		}
		print(strArrays, begin, end);
	}

	private static void print(String[] strArrays, int begin, int end) {
		System.out.println(begin+" "+end);
		// TODO 打印最短字串
		for (int i = begin; i <= end; i++) {
			System.out.print(strArrays[i] + " ");
		}

	}

	private static boolean containtAll(String[] strArrays, String[] keyArray, int begin, int end) {
		// TODO 判断在strArrays的i到j的位置是否包含所有的keyArray
		Map<String, Integer> map = new HashMap<>();
		for (int i = 0; i < keyArray.length; i++) { // 构建key的hash表
			if (map.get(keyArray[i]) == null) {
				map.put(keyArray[i], 1);
			} else {
				map.put(keyArray[i], map.get(keyArray[i]) + 1);
			}
		}
		Map<String, Integer> mapRecord = new HashMap<>();
		for (int i = begin; i <= end; i++) { // 构建str的hash表
			if (mapRecord.get(strArrays[i]) == null) {
				mapRecord.put(strArrays[i], 1);
			} else {
				mapRecord.put(strArrays[i], mapRecord.get(strArrays[i]) + 1);
			}
		}
		// 判断两个hash表的内容是否满足要求
		for (Map.Entry<String, Integer> e : map.entrySet()) {
			if (mapRecord.get(e.getKey()) == null || mapRecord.get(e.getKey()) != e.getValue()) {
				return false;
			}
		}
		return true;
	}

	private static int find(String string, String[] keyArray) {
		for (int i = 0; i < keyArray.length; i++) {
			if (string.equals(keyArray[i])) {
				return i;
			}
		}
		return -1;
	}

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦码城

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值