пересечение двух строк с использованием Java HashSet

Я пытаюсь выучить Java, выполняя некоторые задания из Стэнфордского класса, и мне трудно ответить на этот вопрос.

boolean stringIntersect(String a, String b, int len): Учитывая 2 строки, рассмотрим все входящие в них подстроки длины len. Возвращает true, если есть такие подстроки, которые встречаются в обеих строках. Вычислите это за время O(n), используя HashSet.

Я не могу понять, как это сделать с помощью 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;
}

person java_student    schedule 01.08.2011    source источник
comment
Что вы подразумеваете под «не может хранить повторяющиеся символы»?   -  person user802421    schedule 01.08.2011
comment
Я ошибочно подумал, что должен хранить две строки как набор символов. Так, например, если бы я хотел сохранить шумиху в виде набора символов, я бы не смог сохранить обе ОС. Но я понимаю, что вместо этого я должен хранить не символы, а подстроки.   -  person java_student    schedule 01.08.2011


Ответы (3)


Я не уверен, что понимаю, что вы подразумеваете под «вы не можете хранить повторяющиеся символы». если в нем уже есть значение. В этом случае проблема требует, чтобы вы ответили на вопрос, сохраняя строки, а не символы, в HashSet. Чтобы сделать это в java:

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

Попробуйте разбить эту задачу на две части: 1. Сгенерируйте все подстроки длины len строки 2. Используйте это для решения задачи.

Подсказка для второй части: Шаг 1: Для первой строки введите подстроки в хэш-набор. Шаг 2: Для второй строки проверьте значения в хэш-наборе.

Примечание (Дополнительно): эта проблема плохо определена. Ввод и проверка строк в хеш-таблице — это O длины строки. Для строки a длины n у вас есть O (n-k) подстрок длины k. Таким образом, для string a строки длины n, а строки b строки длины m у вас есть O((n-k)*k+(m-k)*k), это не очень большое число n, так как ваше время работы для k = n/2 равно O((n/2)*( п/2)) = О (п ^ 2)


Редактировать: Так что, если вы действительно хотите сделать это в O(n) (или, возможно, O(n+m+k))? Я считаю, что исходное домашнее задание требовало чего-то вроде алгоритма, который я описал выше. Но мы можем сделать лучше. Более того, мы можем добиться большего успеха и по-прежнему сделать HashSet важнейшим инструментом для нашего алгоритма. Идея состоит в том, чтобы выполнить наш поиск, используя «Rolling Hash». Википедия описывает пару: http://en.wikipedia.org/wiki/Rolling_hash, но мы будем реализовывать наши собственные.

Простым решением было бы XOR значений хэшей символов вместе. Это может позволить нам добавить новый символ в хэш O(1) и удалить один O(1), что сделает вычисление следующего хэша тривиальным. Но этот простой алгоритм не сработает по двум причинам.

  1. Хэши символов могут не обеспечивать достаточную энтропию. Хорошо, мы не знаем, будет ли у нас эта проблема, но давайте все равно решим ее, просто для удовольствия.
  2. Мы будем хешировать перестановки до одного и того же значения ... "abc" не должен иметь тот же хэш, что и "cba"

Для решения первой проблемы мы можем использовать идею из ИИ, а именно позволяет сталь из хеширования Зобриста. Идея состоит в том, чтобы присвоить каждому возможному символу случайное значение большей длины. Если бы мы использовали 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
}

Хорошо, чтобы запустить хеш-функцию, нам просто нужно выполнить XOR для значения, связанного с символом, который мы больше не будем использовать. Для этого просто нужно сместить это значение на соответствующую сумму.

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), чтобы определить, равны ли две строки. Этот случайный подход работает довольно хорошо, и вы можете масштабировать размер случайных битовых массивов, чтобы он работал лучше, но он не гарантирует производительность.

person Philip JF    schedule 01.08.2011

В Hashset следует хранить не символы, а подстроки.

При рассмотрении строки «hoopla»: если вы сохраните подстроки «hoopl» и «oopla» в Hashset (линейная операция), то снова будет линейно найти, совпадает ли одна из подстрок «loopla».

person Jerome    schedule 01.08.2011

Я не знаю, как они думают, что вы должны использовать 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;

}

}
person Maurício Linhares    schedule 01.08.2011
comment
Он не просил решения. Он, вероятно, хочет закодировать его сам, так как это является целью упражнения. - person Jerome; 01.08.2011
comment
И? Он учится, объяснять алгоритм словами — это то же самое, что писать код, по крайней мере с кодом мы можем дать ему лучшие практики, и он может узнать, как должны работать эти классы. Он пришел сюда в поисках ответа, и любой ответ, который может помочь ему двигаться вперед, хорош. Если вы так не думаете, просто проголосуйте против, он тот, кто должен сказать, хорошо это или нет. - person Maurício Linhares; 01.08.2011
comment
Маурисио: он что-то не понял в проблеме (повторяющиеся символы в его вопросе). Лучшим ответом было бы найти то, что он не понял, и прояснить это, чтобы он мог продолжить и найти решение. Было бы не очень эффективно, если бы учителя переходили непосредственно к решению каждый раз, когда у учащегося возникает вопрос по постановке задачи. - person Jerome; 01.08.2011
comment
мой получил такое же лечение. Нашел некоторые обсуждения по этому поводу: мета. stackexchange.com/questions/87903/ Я понимаю его точку зрения. - person Paul Bellora; 01.08.2011
comment
Этот ответ также менее эффективен, чем ответ Хубилай-хана. Лучше просмотреть вторую строку и посмотреть, есть ли каждая подстрока в хеш-наборе, хотя посмотрите мой ответ, почему даже это не O (n) - person Philip JF; 01.08.2011