使用Java HashSet的两个字符串的交集

时间:2011-08-01 02:28:50

标签: java string hashset

我正在尝试通过从斯坦福大学的课程中完成一些作业来学习Java,但我在回答这个问题时遇到了麻烦。

  

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

我无法弄清楚如何使用Hashset来执行此操作,因为您无法存储重复的字符。所以stringIntersect(hoopla, loopla, 5)应该返回true。

谢谢!

编辑:非常感谢您的所有快速回复。看到解释和代码很有帮助。我想我不明白为什么在hashset中存储子字符串会使算法更有效率。我最初的解决方案如下:

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;
}

3 个答案:

答案 0 :(得分:5)

我不确定我明白你的意思是“你不能存储重复的字符”一个哈希集是Set,所以它可以做两件事:你可以为它添加值,你可以添加值它,你可以检查一个值是否已经存在。在这种情况下,问题是希望您通过在HashSet中存储字符串而不是字符来回答问题。要在java中执行此操作:

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

尝试将此问题分为两部分: 1.生成字符串长度len的所有子字符串 2.用它来解决问题。

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

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


编辑:那么如果您真的想在O(n)(或者O(n+m+k))中执行此操作,该怎么办?我的信念是,原来的作业要求的是像我上面描述的算法。但我们可以做得更好。更重要的是,我们可以做得更好,仍然使HashSet成为我们算法的关键工具。我们的想法是使用“Rolling Hash”执行搜索。维基百科描述了一对:http://en.wikipedia.org/wiki/Rolling_hash,但我们将实现自己的。

一个简单的解决方案是将角色哈希值合并到一起。这可以允许我们向哈希O(1)添加新的字符并删除一个O(1),从而使计算下一个哈希值变得微不足道。但是这个简单的算法不会有两个原因

  1. 角色哈希可能无法提供足够的熵。好吧,我们不知道我们是否会遇到这个问题,但无论如何都要解决它,只是为了好玩。
  2. 我们会将排列值哈希到相同的值...“abc”不应该与“cba”具有相同的哈希值
  3. 为了解决第一个问题,我们可以使用AI中的一个想法,即从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。翻译起来并不难。

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

    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)吗?嗯,重要的是什么意思。大哦,大欧米茄,大The,都是界限的指标。它们可以作为算法最坏情况的指标,最佳情况或其他。在这种情况下,这些修改会提供预期的 O(n)性能,但这仅在我们避免哈希冲突时才有效。仍然需要O(n)来判断两个字符串是否相等。这种随机方法非常有效,您可以扩展随机位数组的大小以使其更好地工作,但它没有保证性能。

答案 1 :(得分:1)

您不应将字符存储在Hashset中,而应存储在子字符串中。

当考虑字符串“hoopla”时:如果在Hashset中存储子字符串“hoopl”和“oopla”(线性操作),那么它再次是线性的,以查找“loopla”的一个子字符串是否匹配。

答案 2 :(得分:-1)

我不知道他们怎么认为你应该使用 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;

}

}