【问题标题】:intersection of two strings using Java HashSet使用 Java HashSet 的两个字符串的交集
【发布时间】:2011-08-01 02:28:49
【问题描述】:

我正在尝试通过在斯坦福课堂上做一些作业来学习 Java,但无法回答这个问题。

boolean stringIntersect(String a, String b, int len):给定 2 个字符串, 考虑其中长度为 len 的所有子字符串。如果返回真 有任何这样的子字符串出现在两个字符串中。计算 使用 HashSet 在 O(n) 时间内完成。

我不知道如何使用 Hashset 来做到这一点,因为您无法存储重复字符。所以stringIntersect(hoopla, loopla, 5) 应该返回 true。

谢谢!

编辑:非常感谢您的所有及时回复。查看解释和代码很有帮助。我想我不明白为什么将子字符串存储在哈希集中会使算法更有效。我最初有一个类似的解决方案:

public static boolean stringIntersect(String a, String b, int len) {
    assert (len>=1);
    if (len>a.length() || len>b.length()) return false;
    String s1=new String(),s2=new String();
    if (a.length()<b.length()){
        s1=a;
        s2=b;
    }
    else {
        s1=b;
        s2=a;
    }
    int index = 0;
    while (index<=s1.length()-len){
        if (s2.contains(s1.substring(index,index+len)))return true;
        index++;
    }
    return false;
}

【问题讨论】:

  • “无法存储重复字符”是什么意思?
  • 我错误地认为我应该将两个字符串存储为一组字符。例如,如果我想将 hoopla 存储为一组字符,那么我将无法存储两个“o”。但我意识到我不应该存储字符,而是存储子字符串。

标签: java string hashset


【解决方案1】:

我不确定我理解你所说的“你不能存储重复字符”是什么意思哈希集是Set,所以它可以做两件事:你可以给它增加价值,你可以给它增加价值, 你可以检查一个值是否已经在其中。在这种情况下,问题希望您通过在 HashSet 中存储字符串而不是字符来回答问题。在java中做到这一点:

Set<String> stringSet = new HashSet<String>();

试着把这个问题分成两部分: 1、生成一个字符串的所有长度为len的子串 2.用这个来解决问题。

第二部分的提示是: 第 1 步:对于第一个字符串,将子字符串输入到哈希集中 第 2 步:对于第二个字符串,检查哈希集中的值

注意(高级):这个问题没有明确说明。在哈希表中输入和检查字符串是 O 字符串的长度。对于长度为 n 的字符串 a,您有 O(n-k) 个长度为 k 的子字符串。所以对于string a 是一个长度为n 的字符串和字符串b 是一个长度为m 的字符串,你有O((n-k)*k+(m-k)*k) 这并不是n 的很大哦,因为你的k = n/2 的运行时间是O ((n/2)*(n/2)) = O(n^2)


编辑:那么如果你真的想在O(n)(或者O(n+m+k))中这样做呢?我的信念是,最初的作业要求类似于我上面描述的算法。但我们可以做得更好。更重要的是,我们可以做得更好,仍然让HashSet 成为我们算法的关键工具。我们的想法是使用“滚动哈希”来执行我们的搜索。维基百科描述了一对:http://en.wikipedia.org/wiki/Rolling_hash,但我们将实现自己的。

一个简单的解决方案是将字符散列的值异或在一起。这可以让我们向哈希 O(1) 添加一个新字符并删除一个 O(1),从而使计算下一个哈希变得微不足道。但是这个简单的算法不会起作用有两个原因

  1. 字符散列可能无法提供足够的熵。好的,我们不知道我们是否会遇到这个问题,但还是让我们解决它,只是为了好玩。
  2. 我们会将排列散列到相同的值...“abc”不应与“cba”具有相同的散列

为了解决第一个问题,我们可以使用人工智能的一个想法,即让钢铁来自Zobrist hashing。这个想法是为每个可能的字符分配一个更大长度的随机值。如果我们使用 ASCI,我们可以轻松地创建一个包含所有 ASCI 字符的数组,但是在使用 unicode 字符时会遇到问题。另一种方法是懒惰地分配值。

object LazyCharHash{
  private val map = HashMap.empty[Char,Int]
  private val r = new Random
  def lHash(c: Char): Int = {
    val d = map.get(c)
    d match {
      case None => {
        map.put(c,r.nextInt)
        lHash(c)
      }
      case Some(v) => v
    }
  }
}

这是Scala 代码。 Scala 往往不如 Java 冗长,但仍然允许我使用 Java 集合,因此我将使用命令式风格的 Scala。翻译起来并不难。

第二个问题也可以解决。首先,我们不使用纯异或,而是将异或与移位结合起来,因此哈希函数现在是:

def fullHash(s: String) = {
  var h = 0
  for(i <- 0 until s.length){
    h = h >>> 1
    h = h ^ LazyCharHash.lHash(s.charAt(i))
  }
  h
}

当然,使用fullHash 不会带来性能优势。这只是一个规范

我们需要一种使用哈希函数将值存储在HashSet 中的方法(我保证我们会使用它)。我们可以创建一个包装类:

class HString(hash: Int, string: String){
  def getHash = hash
  def getString = string
  override def equals(otherHString: Any): Boolean = {
    otherHString match {
      case other: HString => (hash == other.getHash) && (string == other.getString)
      case _ => false
    }
  }
  override def hashCode = hash
}

好的,要使散列函数滚动,我们只需对与我们将不再使用的字符关联的值进行异或。为此,只需将该值移动适当的量即可。

def stringIntersect(a: String, b: String, len: Int): Boolean = {
  val stringSet = new HashSet[HString]()
  var h = 0
  for(i <- 0 until len){
    h = h >>> 1
    h = h ^ LazyCharHash.lHash(a.charAt(i))
  }
  stringSet.add(new HString(h,a.substring(0,len)))
  for(i <- len until a.length){
    h = h >>> 1
    h = h ^ (LazyCharHash.lHash(a.charAt(i - len)) >>> (len))
    h = h ^ LazyCharHash.lHash(a.charAt(i))
    stringSet.add(new HString(h,a.substring(i - len + 1,i + 1)))
  }
  ...

您可以自己弄清楚如何完成此代码。

这是O(n)?嗯,重要的是什么意思。大哦,大欧米茄,大西塔,都是界限的度量。它们可以作为算法最坏情况、最佳情况或其他情况的指标。在这种情况下,这些修改提供了预期 O(n) 的性能,但这仅在我们避免哈希冲突时才成立。仍然需要O(n) 来判断两个字符串是否相等。这种随机方法效果很好,您可以扩大随机位数组的大小以使其更好地工作,但它并不能保证性能。

【讨论】:

    【解决方案2】:

    你不应该在哈希集中存储字符,而是子字符串。

    在考虑字符串“hoopla”时:如果将子字符串“hoopl”和“oopla”存储在哈希集中(线性运算),那么再次线性查找“loopla”的子字符串之一是否匹配。

    【讨论】:

      【解决方案3】:

      我不知道他们如何看待您应该使用 HashSet,但我最终做了这样的解决方案:

      public class StringComparator {
      
        public static boolean compare( String a, String b, int len ) {
      
          Set<String> pieces = new HashSet<String>();
      
          for ( int x = 0; (x + len) <= b.length(); x++ ) {
              pieces.add( a.substring( x, x + len  ) );
          }
      
          for ( String piece : pieces ) {
              if ( b.contains(piece) ) {
                  return true;
              }
          }
      
          return false;
      
      }
      
      }
      

      【讨论】:

      • 他没有寻求解决方案。他可能想自己编写代码,因为这是练习的目的。
      • 然后呢?他在学习,用文字解释算法就像写代码一样,至少我们可以用代码给他最好的实践,他可以学习这些类应该如何工作。他来这里是为了寻找答案,任何可以帮助他前进的答案都是一个不错的答案。如果你不这么认为,那就投反对票吧,他是应该说这是否好坏的人。
      • Mauricio:他对这个问题有一些不明白的地方(他的问题中的“重复字符”部分)。最好的答案是找到他不明白的地方,并澄清它,以便他可以继续并找到解决方案。如果每次学生对问题陈述有疑问时,教师都直接提出解决方案,那将不是很有效。
      • 我的得到了同样的待遇。找到了一些关于这个的讨论:meta.stackexchange.com/questions/87903/…我明白他的意思。
      • 这个答案也比忽必烈的效率低。最好遍历第二个字符串并查看每个子字符串是否在哈希集中,尽管请参阅我的答案以了解为什么这甚至不是 O(n)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多