【问题标题】:Implementing an efficent algorithm to find the intersection of two strings实现一个有效的算法来找到两个字符串的交集
【发布时间】:2009-07-07 05:11:30
【问题描述】:

实现一个算法,将两个字符串作为输入,并返回两者的交集,每个字母最多表示一次。

算法:(考虑使用的语言将是 c#)

  1. 将两个字符串都转换为字符数组
  2. 取较小的数组并为其生成一个哈希表,键为字符,值为0
  3. 现在循环遍历另一个数组并增加哈希表中的计数(如果其中存在该字符)。
  4. 现在取出哈希表中值大于 0 的所有字符。
  5. 这些是交集值。

这是一个 O(n) 的解决方案,但使用了额外的空间、2 个字符数组和一个哈希表

你们能想出比这更好的解决方案吗?

【问题讨论】:

  • 他已经提出了一种算法,并询问是否有人知道如何做得更好。
  • 嘿....见我上面的算法,我需要知道我们是否可以在不使用额外空间的情况下在 O(n) 时间内解决这个问题
  • 我不会 C#,所以我不知道,但这对于 Set(例如在 Java 或 Python 中找到)来说不是完美的吗?
  • 一组也是额外的空间。
  • 嘿TomatoSandwich,这只是对社区的贡献,以便其他任何人都可以将其用作他/她准备的参考......帮助他人是我的动机,我也在做每一个尝试提出初步解决方案,我认为提出问题没有任何害处,因为它可以帮助我也可以帮助他人

标签: c# algorithm


【解决方案1】:

这个怎么样...

var s1 = "aabbccccddd";
var s2 = "aabc";

var ans = s1.Intersect(s2);

【讨论】:

  • 虽然这是简单的方法,但 OP 要求一种算法直接执行此操作。
  • @Ahmad:如果你想要代码,就用Reflector看看Intersect是怎么实现的。 :)
  • @JP:是的,这个答案和 Reflector 最初都在我脑海中闪过 :)
  • 或者直接查一下:“枚举这个方法返回的对象时,Intersect先枚举,收集该序列的所有不同元素。然后再枚举,标记两个序列中出现的那些元素. 最后,标记的元素按照它们被收集的顺序产生。 - msdn.microsoft.com/en-us/library/bb460136.aspx
  • OP 声明他们更愿意“不使用额外空间”来执行此操作。 Intersect 方法在幕后创建和填充一个 Set(即,它使用额外的空间)。话虽如此,这是对原始算法的改进:它使用 Set 而不是 Hashtable,摆脱了 char 数组,并且完全删除了最终的“清理”循环。
【解决方案2】:

尚未对此进行测试,但这是我的想法:

  1. 对两个字符串进行快速排序,这样您就有了有序的字符序列
  2. 保留两个字符串的索引,比较每个字符串中的“下一个”字符,选择并输出第一个字符,增加该字符串的索引。
  3. 继续直到到达其中一个字符串的末尾,然后从其余字符串中提取唯一值。

不会使用额外的内存,只需要两个原始字符串、两个整数和一个输出字符串(或 StringBuilder)。作为额外的奖励,输出值也会被排序!

第 2 部分: 这就是我要写的(对 cme​​ts 感到抱歉,stackoverflow 的新手):

private static string intersect(string left, string right)
{
  StringBuilder theResult = new StringBuilder();

  string sortedLeft = Program.sort(left);
  string sortedRight = Program.sort(right);

  int leftIndex = 0;
  int rightIndex = 0;

  //  Work though the string with the "first last character".
  if (sortedLeft[sortedLeft.Length - 1] > sortedRight[sortedRight.Length - 1])
  {
    string temp = sortedLeft;
    sortedLeft = sortedRight;
    sortedRight = temp;
  }

  char lastChar = default(char);
  while (leftIndex < sortedLeft.Length)
  {
    char nextChar = (sortedLeft[leftIndex] <= sortedRight[rightIndex]) ? sortedLeft[leftIndex++] : sortedRight[rightIndex++];

    if (lastChar == nextChar) continue;

    theResult.Append(nextChar);
    lastChar = nextChar;
  }

  //  Add the remaining characters from the "right" string
  while (rightIndex < sortedRight.Length)
  {
    char nextChar = sortedRight[rightIndex++];
    if (lastChar == nextChar) continue;

    theResult.Append(nextChar);
    lastChar = nextChar;
  }
  theResult.Append(sortedRight, rightIndex, sortedRight.Length - rightIndex);

  return (theResult.ToString());
}

我希望这更有意义。

【讨论】:

  • 你可以比快速排序做得更好,因为知道数据是字符,至少对于非常大的数据集。如果您正在处理非常大的字符串,您可以通过创建一个 255 字符数组来进行库排序并获得 O(n) 性能。
  • 我没有得到“为两个字符串保留索引,比较每个字符串中的“下一个”字符,选择并输出第一个字符,增加该字符串的索引。”你能再解释一下吗
  • 我的意思是这样的: char lastChar = default(char); while (leftIndex = sortedRight.Length) || (sortedLeft[leftIndex]
  • unknown:“通过基本上创建一个 255 字符数组......” AFAIK,C# 字符串是 UTF16,所以计数排序并不容易实现。对于字节大小的字符,bool[255](甚至 8 个整数)将是解决原始问题的有用解决方案,并且开销最小。
【解决方案3】:

您不需要 2 个字符数组。 System.String 数据类型有一个内置的按位置索引器,它从该位置返回字符,因此您可以从 0 循环到 (String.Length - 1)。如果您对速度比优化存储空间更感兴趣,那么您可以为其中一个字符串创建一个 HashSet,然后再创建一个包含最终结果的 HashSet。然后遍历第二个字符串,针对第一个 HashSet 测试每个字符,如果存在则添加第二个 HashSet。到最后,你已经有了一个包含所有交集的 HashSet,并且省去了在 Hashtable 中寻找非零值的过程。

编辑:我在所有 cmets 关于根本不想使用任何内置容器的问题上输入了这个

【讨论】:

  • 听起来不错..但我们可能仍想遍历哈希集以获取这些字符并将其转换为字符串。
【解决方案4】:

我会这样做。它仍然是 O(N),它不使用哈希表,而是使用一个长度为 26 的 int 数组。(理想情况下)

  1. 制作一个包含 26 个整数的数组,每个元素代表一个字母。初始化为 0。
  2. 遍历第一个字符串,遇到字母时递减一个。
  3. 遍历第二个字符串并获取与您遇到的任何字母相对应的索引处的绝对值。 (编辑:感谢 cmets 中的 swagner)
  4. 返回所有值大于0的索引对应的所有字母。

仍然是 O(N) 并且只有 26 个整数的额外空间。

当然,如果您不仅限于小写或大写字符,您的数组大小可能需要更改。

【讨论】:

  • 继续上例,在第一次通过之后 c[a] = -2 然后在 s2 的通过期间,我们将翻转第一次出现 'a' 的符号,我们不会翻转符号'a' 第二次出现,因为它已经是 +ve........
  • 而不是在步骤 3 中“翻转符号”,使用 Math.Abs​​ 将始终保持数字为正,即使在偶数出现的重复字符计数中 s2。
  • 这里我们可以使用 bool 数组代替整数数组,每个索引都是 char ascii 值,因此我们需要大小为 256 的 bool 数组。
  • 这对 OP 来说可能不是问题,但为了满足所有可能的 unicode 字符,您的数组需要很多更大,这意味着使用 HashSet 或类似的可能需要更少的空间。
  • 布尔数组在这里不起作用,因为你需要一个值来表示你从未见过的字母,一个值来表示你在第一个字符串中看到的值,另一个值来表示字母同时。所以字节可能是最节省空间的。但你必须确保在递减时不会溢出。
【解决方案5】:

"每个字母最多代表一次"

我假设这意味着您只需要知道交叉点,而不是它们发生了多少次。如果是这样,那么您可以通过使用yield 来精简您的算法。无需存储计数并继续迭代第二个字符串以查找其他匹配项,您可以在此处产生交集并继续从第一个字符串开始下一个可能的匹配项。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多