最长公共字符串的算法<1>
java中基本数据类型结构 " />

最长公共字符串是指在多个字符串中找到一个最长的字符串,使得它在每个字符串中都出现过。这个问题是非常经典且重要的问题,也是计算机科学中的一个典型问题,对于各种应用来说非常有用。

Java中的基本数据类型结构包括整型、浮点型、字符型、布尔型等,这些数据类型在解决算法问题中也是必不可少的。下面我们就来讲讲在Java中实现最长公共字符串的算法和基本数据类型的应用。

一、暴力法

最简单直接的算法是暴力枚举,即逐一比较每一对字符串中相同位置的字符是否相同,然后尝试匹配所有可能的子串找出最长的公共子串。

算法流程:

首先,我们需要找到所有字符串的最短字符串,并先假设它就是答案。

然后,我们逐一枚举最短字符串的所有子串,检查该子串是否在其他字符串中都存在。

如果该子串在其他字符串中都存在,则将它标记为公共子串。

对于所有标记为公共子串的子串,找到其中最长的一个子串就是最长公共子串。

该算法的时间复杂度为$O(mn^2)$,其中$m$为所有字符串的长度之和,$n$为字符串的数目。这个算法在处理海量数据时比较困难,但对于小规模数据和初学者来说十分友好,尤其是对于一些特殊情况下能够达到最优解。

Java代码:

```java

public static String longestCommonSubstring(String[] strs) {

int n = strs.length;

if (n == 0) return "";

String shortest = strs[0];

for (String str : strs) {

if (str.length() < shortest.length()) shortest = str;

}

int len = shortest.length();

for (int i = len; i > 0; i--) {

for (int j = 0; j <= len - i; j++) {

String substr = shortest.substring(j, j + i);

boolean flag = true;

for (int k = 0; k < n; k++) {

if (!strs[k].contains(substr)) {

flag = false;

break;

}

}

if (flag) return substr;

}

}

return "";

}

```

二、动态规划法

动态规划法是一种十分高效的算法,在处理最长公共子序列问题时也有很好的应用。这里我们可以使用两个在本题中非常关键的数据结构:标志数组$dp$和最长公共子串数组$len$。

其中,$dp[i][j]$表示当前考虑到的两个字符串中以第一个字符串第$i$个字符和第二个字符串第$j$个字符结尾的最长公共子串长度,最终的结果即为$max(len)$。

具体算法流程如下:

设第一个字符串的长度为$m$,第二个字符串的长度为$n$。

令数组$dp$全部初始化为0。

依次枚举第一个字符串中的每个字符$i$以及第二个字符串中的每个字符$j$。

若第一个字符串第$i$个字符等于第二个字符串第$j$个字符,则令$dp[i][j]=dp[i-1][j-1]+1$。

如果$dp[i][j]$大于最长公共子串的长度,则更新最长公共子串的长度。

返回$max(len)$即可。

该算法的时间复杂度为$O(mn)$。虽然可能需要比暴力枚举法更多的空间,但却是处理海量数据时的首选算法。

Java代码:

```java

public static String longestCommonSubstring(String[] strs) {

int n = strs.length;

if (n == 0) return "";

int m = strs[0].length();

int[][] dp = new int[m + 1][n + 1];

int len = 0, end = -1;

for (int i = 1; i <= m; i++) {

for (int j = 1; j <= n; j++) {

if (strs[j - 1].charAt(i - 1) == strs[0].charAt(i - 1)) {

dp[i][j] = dp[i - 1][j - 1] + 1;

if (dp[i][j] > len) {

len = dp[i][j];

end = i;

}

}

}

}

return len == 0 ? "" : strs[0].substring(end - len, end);

}

```

三、后缀数组法

后缀数组是一种非常优秀的数据结构,它可以在$O(n\log n)$的时间复杂度内构建出一个字符串的后缀数组,并能够在$O(m\log n)$的时间内完成最长公共子串的查找任务。

它的基本思想是将字符串的所有后缀按字典序排序,并记录它们的起始位置。这样一个字符串的最长公共子串,就是各个字符串后缀之间的最长公共前缀。

具体算法流程如下:

首先,生成原字符串的所有后缀,并构建出这些后缀的后缀数组。

然后,遍历每一个相邻的后缀,计算它们之间的最长公共前缀。

如果该最长公共前缀的长度大于当前结果的长度,则更新最长公共子串的长度和其起始位置。

返回最终的最长公共子串。

该算法的时间复杂度为$O(n\log^2 n)$,在海量数据的处理中效率非常高。

Java代码:

```java

public static String longestCommonSubstring(String[] strs) {

int n = strs.length;

if (n == 0) return "";

if (n == 1) return strs[0];

for (int i = 0; i < n; i++) {

if (strs[i].length() == 0) return "";

}

SuffixArray sa = new SuffixArray();

sa.build(strs[0], strs);

String ans = "";

int ansLen = 0;

for (int i = 1; i <= strs[0].length(); i++) {

for (int j = i; j <= strs[0].length(); j++) {

int cnt = 1;

boolean flag = true;

for (int k = 1; k < n; k++) {

int len = sa.lcp(i, j, sa.height[k]);

if (len == 0) {

flag = false;

break;

} else {

cnt++;

}

}

if (flag && cnt == n && ansLen < j - i) {

ans = strs[0].substring(sa.sa[i] - 1, sa.sa[i] - 1 + j - i);

ansLen = j - i;

}

}

}

return ans;

}

class SuffixArray {

int[] sa;

int[] height;

void build(String s, String[] t) {

int n = s.length(), m = t.length;

int[] x = new int[n + m], y = new int[n + m];

int[] c = new int[m + 1];

sa = new int[n + 1];

height = new int[n + 1];

for (int i = 0; i < n; i++) {

x[i] = s.charAt(i);

}

for (int i = 0; i < m; i++) {

x[n + i] = (int) t[i].charAt(0);

}

for (int i = 0; i < n + m; i++) {

c[x[i]]++;

}

for (int i = 1; i <= m; i++) {

c[i] += c[i - 1];

}

for (int i = n + m - 1; i >= 0; i--) {

c[x[i]]--;

sa[c[x[i]]] = i;

}

for (int k = 1; k <= n; k *= 2) {

int p = 0;

for (int i = n; i > n - k; i--) {

y[p++] = i;

}

for (int i = 0; i < n; i++) {

if (sa[i] >= k) y[p++] = sa[i] - k;

}

for (int i = n + m - 1; i >= 0; i--) {

c[x[y[i]]]--;

sa[c[x[y[i]]]] = y[i];

}

int[] t1 = x;

x = y;

y = t1;

p = 1;

x[sa[0]] = 0;

for (int i = 1; i < n; i++) {

if (y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k]) {

x[sa[i]] = p - 1;

} else {

x[sa[i]] = p++;

}

}

if (p >= n) break;

m = p;

}

int k = 0;

for (int i = 0; i < n; i++) {

if (k > 0) k--;

if (x[i] == 0) continue;

int j = sa[x[i] - 1];

while (i + k < n && j + k < n && s.charAt(i + k) == s.charAt(j + k)) k++;

height[x[i]] = k;

}

}

int lcp(int i, int j, int k) {

if (i == j) return height[i];

if (i > j) {

int tmp = i;

i = j;

j = tmp;

}

int ans = height[i + 1];

for (int p = i + 2; p <= j; p++) {

if (height[p] < ans) ans = height[p];

}

if (k < ans) ans = k;

return ans;

}

}

```

最后,以上三种算法都能够在Java中十分方便地使用Java的基本数据类型来实现,包括整型、浮点类型、字符类型等。在实际应用中,我们可以根据具体的问题来选择不同的算法和数据类型,得到最优的运行效果。

壹涵网络我们是一家专注于网站建设、企业营销、网站关键词排名、AI内容生成、新媒体营销和短视频营销等业务的公司。我们拥有一支优秀的团队,专门致力于为客户提供优质的服务。

我们致力于为客户提供一站式的互联网营销服务,帮助客户在激烈的市场竞争中获得更大的优势和发展机会!

点赞(103) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
发表
评论
返回
顶部