• 生命形式问题


    一 问题描述

    你可能想知道为什么大多数外星生命形式与人类相似,不同的是表面特征,如身高,颜色,皱纹,耳朵,眉毛等。少数人没有人类的相似之处:这些通常具有几何形状或无定形形状,如立方体,浮油或灰尘云。

    答案在星际迷航一一下一代的第 146 集中给出,名为 The Chase。事实证明,绝大多数象限的生命形式最终都是一个普通 DNA 的大片段。

    鉴于表示为字母串的几种生命形式的 DNA 序列,您将找到由超过一半的字符串共享的最长子字符串。

    二 输入和输出

    1 输入

    标准输入包含几个测试用例。每个测试用例始于 1<=N<=100,生命形式的数目。接下来 n 行,每行包含一串小写字母,代表生命形式的 DNA 序列。每个 DNA 序列含有至少一个且不超过 1000 个字母。在最后一个测试用例之后包含0的行。

    2 输出

    对于每个测试用例,输出超过一半生命形式共享的最长字符串。如果有很多,则按字母顺序输出所有这些。如果没有至少一个字母的解决方案,则输出"?"。在测试用例之间留一空行。

    三 输入和输出样例

    1 输入样例

    3

    abcdefg

    bcdefgh

    cdefghi

    3

    xxx

    yyy

    zzz

    0

    2 输出样例

    bcdefg

    cdefgh

    ?

    四 分析和设计

    1 分析

    本问题包括多个字符串,首先用一个原字符串中没有的字符将每个字符串连接起来,求解重复 N/2 次的最长子串。可采用后缀数组及二分法求解。

    2 设计

    a 将 N 个字符串连接起来,中间用特殊字符间隔。

    b 求解 sa 数组。

    c 求解 rank 数组和 height 数组。

    d 使用二分法求解,对特定的长度 mid,判断是否满足 height[j] >=mid 的字符串个数是否大于等于 N/2,求出最大 mid 后,输出长度大于等于 mid 的所有的子串。

    3 示例

    abcdefg#bcdefgh#cdefghi

    后缀排序后的前几个序列如下:

    五 代码

    1. package com.platform.modules.alg.alglib.poj3294;
    2. public class Poj3294 {
    3. // 100 个字符串中间添加了 100 个,每个字符串最大 1000
    4. private int maxn = 100500;
    5. int belong[] = new int[maxn];
    6. int r[] = new int[maxn];
    7. int sa[] = new int[maxn];
    8. String s = "";
    9. // 名称数组
    10. int rank[] = new int[maxn];
    11. // 高度数组
    12. int height[] = new int[maxn];
    13. int n, k;
    14. int wa[] = new int[maxn];
    15. int wb[] = new int[maxn];
    16. int wv[] = new int[maxn];
    17. // c[]用于基数排序统计
    18. int c[] = new int[maxn];
    19. boolean cmp(int r[], int a, int b, int l) {
    20. return (r[a] == r[b]) && (r[a + l] == r[b + l]);
    21. }
    22. // 交换两个数组的内容 https://blog.csdn.net/m0_56793367/article/details/115599564
    23. public static void swap(int[] array1, int[] array2) {
    24. int tmp = 0;
    25. for (int i = 0; i < array1.length; i++) {
    26. tmp = array1[i];
    27. array1[i] = array2[i];
    28. array2[i] = tmp;
    29. }
    30. }
    31. void da(int r[], int sa[], int n, int m) {
    32. int i, k, p;
    33. int x[] = wa;
    34. int y[] = wb;
    35. for (i = 0; i < m; i++) // 基数排序
    36. c[i] = 0;
    37. for (i = 0; i < n; i++)
    38. c[x[i] = r[i]]++;
    39. for (i = 1; i < m; i++)
    40. c[i] += c[i - 1];
    41. for (i = n - 1; i >= 0; i--)
    42. sa[--c[x[i]]] = i;
    43. for (k = 1; k <= n; k <<= 1) {
    44. // 直接利用 sa 排序第二关键字
    45. p = 0;
    46. for (i = n - k; i < n; i++)
    47. y[p++] = i; // 补零的位置下标排在最前面
    48. for (i = 0; i < n; i++)
    49. if (sa[i] >= k)
    50. y[p++] = sa[i] - k;
    51. // 基数排序第一关键字
    52. for (i = 0; i < n; i++)
    53. wv[i] = x[y[i]]; // 将第二关键字排序结果转换为名次,进行排序
    54. for (i = 0; i < m; i++)
    55. c[i] = 0;
    56. for (i = 0; i < n; i++)
    57. c[wv[i]]++;
    58. for (i = 1; i < m; i++)
    59. c[i] += c[i - 1];
    60. for (i = n - 1; i >= 0; i--)
    61. sa[--c[wv[i]]] = y[i];
    62. // 根据 sa 和 x 数组,重新计算新的x数组
    63. swap(x, y); // y 数组已经没有用,更新x需要使用x本身数据,因此放入y使用
    64. p = 1;
    65. x[sa[0]] = 0;
    66. for (i = 1; i < n; i++)
    67. x[sa[i]] = cmp(y, sa[i - 1], sa[i], k) ? p - 1 : p++;
    68. if (p >= n)//排序结束
    69. break;
    70. m = p;
    71. }
    72. }
    73. void calheight(int r[], int sa[], int n) {
    74. int i, j, k = 0;
    75. for (i = 1; i <= n; i++)
    76. rank[sa[i]] = i;
    77. for (i = 0; i < n; i++) {
    78. if (k > 0)
    79. k--;
    80. j = sa[rank[i] - 1];
    81. while (r[i + k] == r[j + k])
    82. k++;
    83. height[rank[i]] = k;
    84. }
    85. }
    86. boolean check(int mid, int n, int m, boolean flag) {
    87. int i = 2;
    88. boolean vis[];
    89. while (true) {
    90. vis = new boolean[105]; // 清零
    91. while (i <= n && height[i] < mid)
    92. i++;
    93. if (i > n)
    94. break;
    95. vis[belong[sa[i - 1]]] = true;//标记该字符串
    96. while (i <= n && height[i] >= mid) {
    97. vis[belong[sa[i]]] = true;//如果是自己的子串,只标记一次
    98. i++;
    99. }
    100. int cnt = 0;
    101. for (int j = 1; j <= m; j++)
    102. if (vis[j])
    103. cnt++;
    104. if (cnt > m / 2) {
    105. if (!flag)
    106. return true; // 立即返回1,否则输出,继续运行输出所有满足条件的子串
    107. else {
    108. for (int j = sa[i - 1], t = 0; t < mid; j++, t++)
    109. output += s.charAt(j);
    110. output += "\n";
    111. }
    112. }
    113. }
    114. return false;
    115. }
    116. void solve(int L, int R) {
    117. int res = -1, mid;
    118. while (L <= R) {
    119. mid = (L + R) >> 1;
    120. if (check(mid, n, k, false)) {
    121. res = mid;
    122. L = mid + 1;
    123. } else
    124. R = mid - 1;
    125. }
    126. if (res == -1)
    127. output += "?\n";
    128. else
    129. check(res, n, k, true);//输出
    130. }
    131. public String output = "";
    132. public String cal(String input) {
    133. int len = 0;
    134. String[] line = input.split("\n");
    135. int count = 0;
    136. while (true) {
    137. s = "";
    138. k = Integer.parseInt(line[count++]);
    139. if (k == 0) {
    140. return output;
    141. }
    142. n = 0;
    143. for (int i = 1; i <= k; i++) { // 将 k 个字符串连接起来,中间用特殊字符间隔
    144. String str = line[count];
    145. count++;
    146. this.s += str;
    147. if (i == 1)
    148. len = this.s.length();
    149. for (int j = 0; j < str.length(); j++) {
    150. belong[n] = i; // 标记属于第 i 个字符串
    151. r[n] = str.charAt(j) - 'a' + 1;//a-z 97-122
    152. n++;
    153. }
    154. this.s += "#"; // 35
    155. r[n] = 100 + i;
    156. n++;
    157. }
    158. n--;
    159. r[n] = 0; // 末尾补一个比所有值都小的数
    160. da(r, sa, n + 1, 300);
    161. calheight(r, sa, n);
    162. solve(1, len);
    163. output += "\n";
    164. }
    165. }
    166. }

     六 测试

     

  • 相关阅读:
    SpringBoot+Vue实现前后端分离OA办公管理系统
    Vue3响应系统的实现(一)
    每日leetcode[删除排序链表中的重复元素]
    Nginx生产环境平滑升级
    大数据之Stream流
    Pytorch 神经网络搭建步骤
    因为一次bug的教训,我决定手撕Nacos源码(先撕客户端源码)
    MybatisPlus 配置多数据源
    OpenCV 04(通道分离与合并 | 绘制图形)
    [Mac] 安装paddle-pipelines出现 ERROR: Failed building wheel for lmdb
  • 原文地址:https://blog.csdn.net/chengqiuming/article/details/127434035