Я пытаюсь решить задачу в LeetCode, и кажется, она почти решена. Вот что я сделал на данный момент: Скрипка и проблемная ссылка — 30. Подстрока с объединением всех слов. Ниже приведен фрагмент кода, который я сделал на данный момент:
static IList < int > FindSubstring(string s, string[] words) {
var list = new List < IList < string >> ();
DoPermutation(words, 0, words.Length - 1, list);
return TrackIndexes(s, list);
}
static IList < IList < string >> DoPermutation(string[] str, int start, int end, IList < IList < string >> list) {
if (start == end) {
list.Add(new List < string > (str));
} else {
for (var i = start; i <= end; i++) {
Swap(ref str[start], ref str[i]);
DoPermutation(str, start + 1, end, list);
Swap(ref str[start], ref str[i]);
}
}
return list;
}
static void Swap(ref string a, ref string b) {
var temp = a;
a = b;
b = temp;
}
static List < int > TrackIndexes(string pattern, IList < IList < string >> aLst) {
int count = 0;
List < string > strings;
List < int > indexes = new List < int > ();
foreach(var item in aLst) {
string str = "";
strings = new List < string > {
String.Concat(item)
};
foreach(var list in item) {
str += list;
if (str.Length == strings[0].Length && pattern.Contains(str)) {
indexes.AddRange(AllIndexesOf(pattern, str));
count += 1;
}
}
}
indexes.Sort();
return indexes.Distinct().ToList();
}
public static int[] AllIndexesOf(string str, string substr, bool ignoreCase = false)
{
if (string.IsNullOrWhiteSpace(str) || string.IsNullOrWhiteSpace(substr))
{
throw new ArgumentException("String or substring is not specified.");
}
var indexes = new List<int>();
int index = 0;
while ((index = str.IndexOf(substr, index, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) != -1)
{
indexes.Add(index++);
}
return indexes.ToArray();
}
У меня возникла проблема со следующим вводом:
s = "pjzkrkevzztxductzzxmxsvwjkxpvukmfjywwetvfnujhweiybwvvsrfequzkhossmootkmyxgjgfordrpapjuunmqnxxdrqrfgkrsjqbszgiqlcfnrpjlcwdrvbumtotzylshdvccdmsqoadfrpsvnwpizlwszrtyclhgilklydbmfhuywotjmktnwrfvizvnmfvvqfiokkdprznnnjycttprkxpuykhmpchiksyucbmtabiqkisgbhxngmhezrrqvayfsxauampdpxtafniiwfvdufhtwajrbkxtjzqjnfocdhekumttuqwovfjrgulhekcpjszyynadxhnttgmnxkduqmmyhzfnjhducesctufqbumxbamalqudeibljgbspeotkgvddcwgxidaiqcvgwykhbysjzlzfbupkqunuqtraxrlptivshhbihtsigtpipguhbhctcvubnhqipncyxfjebdnjyetnlnvmuxhzsdahkrscewabejifmxombiamxvauuitoltyymsarqcuuoezcbqpdaprxmsrickwpgwpsoplhugbikbkotzrtqkscekkgwjycfnvwfgdzogjzjvpcvixnsqsxacfwndzvrwrycwxrcismdhqapoojegggkocyrdtkzmiekhxoppctytvphjynrhtcvxcobxbcjjivtfjiwmduhzjokkbctweqtigwfhzorjlkpuuliaipbtfldinyetoybvugevwvhhhweejogrghllsouipabfafcxnhukcbtmxzshoyyufjhzadhrelweszbfgwpkzlwxkogyogutscvuhcllphshivnoteztpxsaoaacgxyaztuixhunrowzljqfqrahosheukhahhbiaxqzfmmwcjxountkevsvpbzjnilwpoermxrtlfroqoclexxisrdhvfsindffslyekrzwzqkpeocilatftymodgztjgybtyheqgcpwogdcjlnlesefgvimwbxcbzvaibspdjnrpqtyeilkcspknyylbwndvkffmzuriilxagyerjptbgeqgebiaqnvdubrtxibhvakcyotkfonmseszhczapxdlauexehhaireihxsplgdgmxfvaevrbadbwjbdrkfbbjjkgcztkcbwagtcnrtqryuqixtzhaakjlurnumzyovawrcjiwabuwretmdamfkxrgqgcdgbrdbnugzecbgyxxdqmisaqcyjkqrntxqmdrczxbebemcblftxplafnyoxqimkhcykwamvdsxjezkpgdpvopddptdfbprjustquhlazkjfluxrzopqdstulybnqvyknrchbphcarknnhhovweaqawdyxsqsqahkepluypwrzjegqtdoxfgzdkydeoxvrfhxusrujnmjzqrrlxglcmkiykldbiasnhrjbjekystzilrwkzhontwmehrfsrzfaqrbbxncphbzuuxeteshyrveamjsfiaharkcqxefghgceeixkdgkuboupxnwhnfigpkwnqdvzlydpidcljmflbccarbiegsmweklwngvygbqpescpeichmfidgsjmkvkofvkuehsmkkbocgejoiqcnafvuokelwuqsgkyoekaroptuvekfvmtxtqshcwsztkrzwrpabqrrhnlerxjojemcxel",
words = ["dhvf","sind","ffsl","yekr","zwzq","kpeo","cila","tfty","modg","ztjg","ybty","heqg","cpwo","gdcj","lnle","sefg","vimw","bxcb"]
Код, который у меня есть, создает список возможных перестановок в методе DoPermutation
, а для приведенных выше входных данных генерирует список строк, в которых возникает ошибка нехватки памяти. Я не уверен, можно ли что-нибудь изменить, используя имеющийся у меня фрагмент кода.
На данный момент мой код проходит 151 тест из 180. Можно ли в любом случае использовать существующий фрагмент кода, чтобы избавиться от исключения памяти, или мне придется переосмыслить проблему?
Цель: Основная цель кода — создать возможную перестановку для списка строк и сопоставить эту строку с шаблоном как подстроку.
Как я указал в своем комментарии к вашему предыдущему вопросу, который вы удалили, ваш текущий алгоритм создает огромное количество перестановок: 6,402,373,705,728,000
всего для 18 слов. Даже если вы избежите выделения памяти, для завершения выполнения потребуется несколько дней. Вам нужно найти что-то с меньшей алгоритмической сложностью.
Теперь ты заставил меня действительно решить головоломку для фанатов. Хотя ваше решение может работать при некоторой оптимизации, я думаю, вам следует отойти от текущей идеи и переосмыслить проблему. Попробуйте еще раз объяснить себе проблему.
Подсказка: использование слова «перестановка» — это своего рода приманка, которая заставит вас создать этот список. Попробуйте выразить проблему без этого употребления.
Я решил это, используя эти имена и сигнатуры методов:
public IList<int> FindSubstring(string s, string[] words)
Dictionary<string, int> ExtractWordOccurances(string candidateSubstring, int chopSize)
bool SubStringMeetsRequirements(Dictionary<string, int> extractedChunks, string[] words)
Это решение по-прежнему очень неэффективно и является лишь моим первым подходом, не учитывающим слишком много времени выполнения и использования памяти. Вероятно, есть еще более умные подходы.
Да, конечно, я бы не стал отвечать, если бы решение было неверным или если бы я не был в нем уверен. Хотя, как я уже говорил, это неэффективно.
Ваша проблема в том, что вы материализуете список всех возможных перестановок ваших слов. Однако в этом нет необходимости. Все, что вам нужно сделать, это перечислить возможные перестановки и проверить каждую из них на какое-то условие. в C# это можно сделать, реализовав метод IEnumerable<string []>
, который материализует только один строковый массив за раз.
Ниже приведен один из таких примеров, написанный для .NET 8/C# 12:
public static class EnumerableExtensions
{
///<summary>
///Enumerate the possible permutations of the incoming array.
///Note that the memory of the returned array is reused for the next permutation
///</summary>
public static IEnumerable<T []> EnumeratePermutations<T>(this T [] array)
{
if (array.Length == 0)
{
yield return array;
yield break;
}
// Mutate a copy rather than the original
var copy = array.ToArray();
static IEnumerable<T []> EnumeratePermutations(T [] array, int start)
{
if (start >= array.Length)
throw new ArgumentException("start >= array.Length");
for (int i = start; i < array.Length; i++)
{
Swap(ref array[start], ref array[i]);
yield return array;
Swap(ref array[start], ref array[i]);
}
}
var stack = new Stack<IEnumerator<T []>>();
try
{
stack.Push(EnumeratePermutations(copy, 0).GetEnumerator());
while (stack.Count != 0)
{
var enumerator = stack.Peek();
if (!enumerator.MoveNext())
stack.Pop().Dispose();
else if (stack.Count < copy.Length)
stack.Push(EnumeratePermutations(enumerator.Current, stack.Count).GetEnumerator());
else
yield return enumerator.Current;
}
}
finally
{
foreach (var enumerator in stack)
enumerator.Dispose();
}
}
static void Swap<T>(ref T a, ref T b)
{
var temp = a;
a = b;
b = temp;
}
}
Я решил реализовать это с помощью явного стека, а не с помощью рекурсии, из соображений производительности и во избежание потенциальных переполнений стека.
Затем вы можете изменить свой TrackIndexes()
, чтобы он принимал аргумент IEnumerable<string []> aLst
, и действовать, как раньше:
static List<int> TrackIndexes(string pattern, IEnumerable<string []> aLst) {
// TODO: write and test this yourself
}
При этом, хотя это решит вашу проблему нехватки памяти, оно не пройдет 30. Подстрока с объединением всех слов . Простое создание каждой перестановки с последующей проверкой того, содержится ли конкатенация в целевой строке, займет O(s.Length * Factorial(words.Length))
, что будет недостаточно быстро, чтобы пройти тест. Вам придется значительно сократить количество переборов, которые вы перечисляете, сделав несколько быстрых отклонений, или переключиться на альтернативный подход, как предложено Глубусом в их ответе.
Поскольку все строки в массиве words
имеют одинаковую длину, а максимальная длина (30
) довольно мала, мы можем попробовать каждый возможный класс эквивалентности с длиной одного слова в качестве модуля. Для каждого из них мы можем использовать скользящее окно, чтобы отслеживать наибольшую совпадающую подстроку в O(|s|)
(где |s|
обозначает длину s
).
Общая временная сложность: O(|w| * |s|)
(где w
представляет собой одно слово). Это также эквивалентно O(30 * |s|)
, поскольку максимальная длина одного слова составляет 30 символов.
public IList<int> FindSubstring(string s, string[] words) {
int wlen = words[0].Length;
var freq = words.GroupBy(x => x).ToDictionary(x => x.Key, x => x.Count());
var res = new List<int>();
for (int i = 0; i < wlen; i++) {
var curr = new Dictionary<string, int>();
for (int l = i, r = l; r + wlen <= s.Length; r += wlen) {
var w = s.Substring(r, wlen);
curr.TryGetValue(w, out var currCnt);
curr[w] = currCnt + 1;
for (; curr[w] > freq.GetValueOrDefault(w, 0); l += wlen)
--curr[s.Substring(l, wlen)];
if (r - l == (words.Length - 1) * wlen)
res.Add(l);
}
}
return res;
}
Вам действительно нужно создать полностью материализованные
List<List<string>>
перестановки? Или вы могли бы просто создать перечислимоеIEnumerable<List<string>>
, в котором одновременно материализуется только один список?