Максимально возможный прямоугольник букв

Напишите программу, чтобы найти максимально возможный прямоугольник букв, чтобы каждая строка формировала слово (слева направо), и каждый столбец формирует слово (сверху вниз).

Я нашел этот интересный вопрос. Это не домашнее задание, хотя это может звучать как таковое. (Я не в школе). Я делаю это для удовольствия.

Пример

Из cat, car, ape, api, rep, tip мы получаем следующий прямоугольник (который является квадратом):

c a r
a p e
t i p

Моя первоначальная идея - создать какое-то дерево префикса, чтобы я мог получить все слова, начинающиеся с определенной строки. Это было бы полезно, если у нас уже есть 2 или более слова (либо чтение сверху вниз, либо слева направо), и нам нужно найти следующее слово для добавления.

Любые другие идеи?

Edit

Можно ли это сделать с помощью кубоида (3D-прямоугольник)?

Что делать, если для диагоналей нужны действительные слова (идея credit: user645466); как оптимизировать его алгоритм?

Ответ 1

Пусть D - словарь. Исправьте m и n. Мы можем сформулировать задачу нахождения m & times; n rectangle как проблема удовлетворения ограничений (CSP).

x i, 1 & hellip; x i, n & isin; D     & theall; я & isin; {1, & hellip;, m}
x 1, j & hellip; x m, j & isin; D     & theall; j & isin; {1, & hellip;, n}
x i, j & isin; {A, & hellip;, Z}     & theall; я & isin; {1, & hellip;, m}, & all; j & isin; {1, & hellip;, n}

Очень общий подход для решения CSP заключается в объединении обратного отслеживания с распространением ограничений. Как бы то ни было, обратное отслеживание означает, что мы выбираем переменную, угадываем ее значение и рекурсивно решаем подзадачу с меньшим числом переменных, а распространение ограничений означает попытку уменьшить количество возможностей для каждой переменной (возможно, до нуля, а это значит, что нет решения).

В качестве примера мы можем начать 3 & times; 3, выбрав x 1,1= Q.

Q??
???
???

С английским словарем единственная возможность для x 1,2 и x 2,1 - U (перед словами "Scrabble" ).

Искусство решения CSP - это балансирование между обратным трассированием и распространением ограничений. Если мы вообще не распространяем ограничений, мы просто используем грубую силу. Если мы отлично распространяем ограничения, то нам не нужно отступать, но алгоритм распространения, который сам по себе разрешает NP-жесткую проблему, вероятно, довольно дорого работает.

В этой задаче работа с большим словарем при каждом обратном трафике node станет дорогим, если у нас не будет хорошей поддержки структуры данных. Я опишу подход, который быстро использует trie или DAWG, чтобы вычислить набор букв через который префикс распространяется на полное слово.

При каждом обратном следе node набор переменных, которые мы назначили, представляет собой таблицу Юнга. Другими словами, переменная не назначается до тех пор, пока не будут назначены переменные, расположенные выше и слева. На приведенной ниже диаграмме . обозначает назначенную переменную, а * и ? обозначают неназначенные переменные.

.....*
...*??
...*??
..*???
*?????

Переменные, отмеченные *, являются кандидатами для следующего присваивания значения. Преимущество, заключающееся в том, что несколько вариантов выбора, а выбор фиксированной переменной каждый раз заключается в том, что некоторые переменные порядки могут быть намного лучше других.

Для каждого * сделайте два поиска в trie/DAWG, один для горизонтального и один для вертикали, и вычислите пересечение наборов букв, которые могут появиться дальше. Одна классическая стратегия состоит в том, чтобы выбрать переменную с наименьшими возможностями в надежде, что мы быстрее достигнем противоречия. Мне нравится эта стратегия, потому что она чернослива естественно, когда есть переменная с нулевыми возможностями и распространяется естественным образом, когда есть переменная с одной. Кэшируя результаты, мы можем очень быстро оценить каждую node.

Ответ 2

С учетом словаря слов заданной длины создайте два новых словаря: Первый содержит все однобуквенные префиксы слов (все буквы, которые могут быть первой буквой слова данной длины), а вторая содержит все двухбуквенные префиксы слов (все последовательности двух букв, которые могут быть первыми двумя буквами слова данной длины). Вы также можете делать тройные префиксы, но вам, вероятно, не нужно будет выходить за рамки этого.

  • Выберите слово из словаря, назовите его X. Это будет первая строка матрицы.

  • Убедитесь, что X[1], X[2], ..., X[N] - все допустимые однобуквенные префиксы, используя тот удобный список, который вы сделали. Если да, переходите к шагу 3; в противном случае вернитесь к шагу 1.

  • Выберите слово из словаря, назовите его Y. Это будет вторая строка матрицы.

  • Убедитесь, что X[1] Y[1], X[2] Y[2],..., X[N] Y[N] - все допустимые двухбуквенные префиксы, используя тот удобный список, который вы сделали. Если да, переходите к шагу 5; в противном случае вернитесь к шагу 3. Если это последнее слово в словаре, перейдите к шагу 1.

    ...

    2 (N-1). Выберите слово из словаря, назовите его Z. Это будет N-я строка матрицы.

    2N. Убедитесь, что X[1] Y[1] ... Z[1], X[2] Y[2] ... Z[2],..., X[N] Y[N] ... Z[N] - все слова в словаре. Если они, поздравляю, ты это сделал! В противном случае вернитесь к шагу 2 (N-1). Если это последнее слово в словаре, перейдите к шагу 2 (n-3).

Логика состоит в том, чтобы создать прямоугольник слов по одной строке за раз, выбрав слова для строк, а затем проверив, чтобы столбец мог быть дополнен словами. Это будет происходить намного быстрее, чем добавление одной буквы за раз.

Ответ 3

Создайте сумку [] для слова той же длины = индекс, затем создайте массив Tries, один Trie для wordList каждой длины

   Rectangle makeRectangle(length, height, rectangle)
    {
        if ( length == rectangle.length()) check if complete and return;
        checkIfPartialComplete - check columns for valid prefixes
        for ( i from 1 to grouplist[i-1])
        {
            newRectangle = append word[i] to rectangle 
            return makeRectangle(l,h, rectangle with appended word) if not equal to null
        }
    }


boolean checkPartial(int l, Trie trie)
{
    for ( int i =0 ; i < l; i++)
    {
        String col = getColumn(i);
        if (!trie.contains(col))
        {
            return false;
        }
    }
    return true;
}
boolean checkComplete(int l, Bag<String> lengthGroup)
{
    for ( int i=0; i < l ; i++)
    {
        String col = getColumn(i);
        if (!lengthGroup.contains(col))
        {
            return false;
        }
    }
    return true;
}