Проблема перелета в одну сторону - PullRequest
54 голосов
/ 07 июня 2010

Вы совершаете рейс в одну сторону с непрямым рейсом, который включает миллиарды неизвестное очень большое количество переводов.

  • Вы не останавливаетесь дважды в одном и том же аэропорту.
  • У вас есть 1 билет на каждую часть вашего путешествия.
  • Каждый билет содержит src и dst airport.
  • Все ваши билеты отсортированы случайным образом.
  • Вы забыли исходный аэропорт вылета (самый первый рейс) и пункт назначения (последний день).

Разработайте алгоритм, чтобы реконструировать вашу поездку с минимальной большой - O сложностью.


Пытаясь решить эту проблему, я начал использовать симметричную разницу из двух наборов, Srcs и Dsts:

1) Сортировать все ключи src в массиве Srcs
2) Сортировка всех ключей dst в массиве Dsts
3) Создайте объединенный набор из обоих массивов, чтобы найти неповторяющиеся объекты - это ваш первый src и последний dst
4) Теперь, имея начальную точку, пройдитесь по обоим массивам, используя двоичный поиск.

Но я полагаю, должен быть другой, более эффективный метод.

Ответы [ 18 ]

29 голосов
/ 07 июня 2010

Создайте хеш-таблицу и добавьте каждый аэропорт в хеш-таблицу.

<key,value> = <airport, count>

Количество аэропортов увеличивается, если аэропорт является либо источником, либо пунктом назначения. Таким образом, для каждого аэропорта количество будет равно 2 (1 для src и 1 для dst), за исключением источника и пункта назначения вашей поездки, который будет иметь значение 1.

Вам нужно посмотреть на каждый билет хотя бы один раз. Таким образом, сложность O (n).

20 голосов
/ 20 июня 2010

Резюме: ниже приводится однопроходный алгоритм . (То есть не просто линейный, а каждый билет выглядит ровно один раз, что, разумеется, является оптимальным числом посещений на билет). Я поместил резюме, потому что есть много по-видимому эквивалентных решений, и было бы трудно определить, почему я добавил другое. :)

Мне на самом деле задавали этот вопрос в интервью. Концепция чрезвычайно проста: каждый тикет представляет собой одноэлементный список с концептуально двумя элементами, src и dst.

Мы индексируем каждый такой список в хеш-таблице, используя его первый и последний элементы в качестве ключей, поэтому мы можем найти в O (1), начинается или заканчивается ли список в определенном элементе (аэропорт). Для каждого тикета, когда мы видим, что он начинается там, где заканчивается другой список, просто свяжите списки (O (1)). Аналогично, если он заканчивается там, где начинается другой список, присоединяется другой список. Конечно, когда мы связываем два списка, мы в основном уничтожаем два и получаем один. (Сеть из N билетов будет построена после N-1 таких ссылок).

Необходимо соблюдать осторожность, чтобы поддерживать неизменность того, что ключи хеш-таблицы являются точно первым и последним элементами оставшихся списков.

В общем, O (N).

И да, я ответил это на месте:)

Редактировать Забыл добавить важный момент. Все упоминают две хеш-таблицы, но одна тоже делает свое дело, поскольку инвариант алгоритмов включает в себя то, что самое большее один список билетов начинается или начинается в любом отдельном городе (если их два, мы немедленно присоединитесь к спискам в этом городе и удалите этот город из хеш-таблицы). Асимптотически нет никакой разницы, просто так проще.

Редактировать 2 Также представляет интерес тот факт, что по сравнению с решениями, использующими 2 хэш-таблицы с N записями каждая , в этом решении используется одна хеш-таблица с не более N / 2 записи (что происходит, если мы видим билеты в порядке, скажем, 1-го, 3-го, 5-го и т. Д.). Таким образом, он также использует около половины памяти, кроме того, чтобы быть быстрее.

10 голосов
/ 07 июня 2010

Создайте две хеш-таблицы (или попытки), одну с ключом на src, а другую на dst. Выберите один билет наугад и найдите его dst в таблице src-hash. Повторяйте этот процесс для результата, пока не дойдете до конца (конечный пункт назначения). Теперь найдите его src в хеш-таблице с ключом dst. Повторяйте процесс для результата, пока не дойдете до начала.

Для построения хеш-таблиц требуется O (n), а для построения списка - O (n), поэтому весь алгоритм равен O (n).

РЕДАКТИРОВАТЬ: вам нужно только создать одну хеш-таблицу, на самом деле. Допустим, вы создали хеш-таблицу с ключом src. Выберите один билет наугад и, как прежде, составьте список, который приведет к конечному пункту назначения. Затем выберите другой случайный билет из билетов, которые еще не были добавлены в список. Следуйте по пункту назначения, пока не достигнете билета, с которого изначально начали. Повторяйте этот процесс, пока не составите весь список. Это все еще O (n), так как в худшем случае вы выбираете билеты в обратном порядке.

Редактировать: поменял имена таблиц в моем алгоритме.

5 голосов
/ 07 июня 2010

В основном это график зависимостей, где каждый билет представляет узел, а аэропорт src и dst представляет направленные ссылки, поэтому используйте топологическую сортировку для определения порядка полета.

РЕДАКТИРОВАТЬ: Хотя это билет на самолет, и вы знаете, что на самом деле сделали маршрут, который вы могли бы физически выполнить, отсортировать по дате и времени вылета в UTC.

РЕДАКТИРОВАТЬ2: Предполагая, что в каждом аэропорту у вас есть билет на использование трехсимвольного кода, вы можете использовать алгоритм, описанный здесь ( Найти три числа, появившихся только один раз ), чтобы определить два уникальных аэропорта путем ксерокопирования всех аэропорты вместе.

РЕДАКТИРОВАТЬ3: Вот некоторые C ++, чтобы на самом деле решить эту проблему с помощью метода xor. Общий алгоритм выглядит следующим образом, предполагая уникальное кодирование из аэропорта в целое число (либо принимая трехбуквенный код аэропорта, либо кодируя местоположение аэропорта в целое число, используя широту и долготу):

Во-первых, XOR все коды аэропортов вместе. Это должно быть равно первоначальному исходному аэропорту XOR до конечного аэропорта назначения. Поскольку мы знаем, что начальный аэропорт и конечный аэропорт уникальны, это значение не должно быть равно нулю. Поскольку это не ноль, в этом значении будет установлен хотя бы один бит. Этот бит соответствует биту, который установлен в одном из аэропортов, а не установлен в другом; назовите это битом обозначения.

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

После того, как вы обработаете все билеты, выберите одно из ведер. Количество аэропортов отправителей, отправляемых в это ведро, должно быть на один больше или меньше количества аэропортов назначения, отправляемых в это ведро. Если количество исходных аэропортов меньше количества аэропортов назначения, это означает, что первоначальный исходный аэропорт (единственный уникальный исходный аэропорт) был отправлен в другое ведро. Это означает, что значение в текущем сегменте является идентификатором исходного аэропорта-источника! И наоборот, если количество аэропортов назначения меньше, чем количество аэропортов-источников, конечный аэропорт назначения был отправлен в другое ведро, поэтому текущее ведение является идентификатором аэропорта конечного назначения!

struct ticket
{
    int src;
    int dst;
};

int get_airport_bucket_index(
    int airport_code, 
    int discriminating_bit)
{
    return (airport_code & discriminating_bit)==discriminating_bit ? 1 : 0;
}

void find_trip_endpoints(const ticket *tickets, size_t ticket_count, int *out_src, int *out_dst)
{
    int xor_residual= 0;

    for (const ticket *current_ticket= tickets, *end_ticket= tickets + ticket_count; current_ticket!=end_ticket; ++current_ticket)
    {
        xor_residual^= current_ticket->src;
        xor_residual^= current_ticket->dst;
    }

    // now xor_residual will be equal to the starting airport xor ending airport
    // since starting airport!=ending airport, they have at least one bit that is not in common
    // 

    int discriminating_bit= xor_residual & (-xor_residual);

    assert(discriminating_bit!=0);

    int airport_codes[2]= { xor_residual, xor_residual };
    int src_count[2]= { 0, 0 };
    int dst_count[2]= { 0, 0 };

    for (const ticket *current_ticket= tickets, *end_ticket= tickets + ticket_count; current_ticket!=end_ticket; ++current_ticket)
    {
        int src_index= get_airport_bucket_index(current_ticket->src, discriminating_bit);

        airport_codes[src_index]^= current_ticket->src;
        src_count[src_index]+= 1;

        int dst_index= get_airport_bucket_index(current_ticket->dst, discriminating_bit);
        airport_codes[dst_index]^= current_ticket->dst;
        dst_count[dst_index]+= 1;
    }

    assert((airport_codes[0]^airport_codes[1])==xor_residual);
    assert(abs(src_count[0]-dst_count[0])==1); // all airports with the bit set/unset will be accounted for as well as either the source or destination
    assert(abs(src_count[1]-dst_count[1])==1);
    assert((src_count[0]-dst_count[0])==-(src_count[1]-dst_count[1]));

    int src_index= src_count[0]-dst_count[0]<0 ? 0 : 1; 
    // if src < dst, that means we put more dst into the source bucket than dst, which means the initial source went into the other bucket, which means it should be equal to this bucket!

    assert(get_airport_bucket_index(airport_codes[src_index], discriminating_bit)!=src_index);

    *out_src= airport_codes[src_index];
    *out_dst= airport_codes[!src_index];

    return;
}

int main()
{
    ticket test0[]= { { 1, 2 } };
    ticket test1[]= { { 1, 2 }, { 2, 3 } };
    ticket test2[]= { { 1, 2 }, { 2, 3 }, { 3, 4 } };
    ticket test3[]= { { 2, 3 }, { 3, 4 }, { 1, 2 } };
    ticket test4[]= { { 2, 1 }, { 3, 2 }, { 4, 3 } };
    ticket test5[]= { { 1, 3 }, { 3, 5 }, { 5, 2 } };

    int initial_src, final_dst;

    find_trip_endpoints(test0, sizeof(test0)/sizeof(*test0), &initial_src, &final_dst);
    assert(initial_src==1);
    assert(final_dst==2);

    find_trip_endpoints(test1, sizeof(test1)/sizeof(*test1), &initial_src, &final_dst);
    assert(initial_src==1);
    assert(final_dst==3);

    find_trip_endpoints(test2, sizeof(test2)/sizeof(*test2), &initial_src, &final_dst);
    assert(initial_src==1);
    assert(final_dst==4);

    find_trip_endpoints(test3, sizeof(test3)/sizeof(*test3), &initial_src, &final_dst);
    assert(initial_src==1);
    assert(final_dst==4);

    find_trip_endpoints(test4, sizeof(test4)/sizeof(*test4), &initial_src, &final_dst);
    assert(initial_src==4);
    assert(final_dst==1);

    find_trip_endpoints(test5, sizeof(test5)/sizeof(*test5), &initial_src, &final_dst);
    assert(initial_src==1);
    assert(final_dst==2);

    return 0;
}
3 голосов
/ 18 июня 2010

Создайте две структуры данных:

Route
{
  start
  end
  list of flights where flight[n].dest = flight[n+1].src
}

List of Routes

А потом:

foreach (flight in random set)
{
  added to route = false;
  foreach (route in list of routes)
  {
    if (flight.src = route.end)
    {
      if (!added_to_route)
      {
        add flight to end of route
        added to route = true
      }
      else
      {
        merge routes
        next flight
      }
    }
    if (flight.dest = route.start)
    {
      if (!added_to_route)
      {
        add flight to start of route
        added to route = true
      }
      else
      {
        merge routes
        next flight
      }
    }
  }
  if (!added to route)
  {
    create route
  }
}
2 голосов
/ 08 июня 2010

Хеш-таблица не будет работать для больших размеров (таких как миллиарды в первоначальном вопросе); Любой, кто работал с ними, знает, что они хороши только для маленьких сетов. Вместо этого вы можете использовать двоичное дерево поиска, которое даст вам сложность O (n log n).

Самый простой способ - два прохода: первый добавляет их все в дерево, проиндексированное src. Второй обходит дерево и собирает узлы в массив.

Можем ли мы сделать лучше? Мы можем, если мы действительно хотим: мы можем сделать это за один проход. Представлять каждый тикет как узел в списке избранного. Первоначально каждый узел имеет нулевые значения для указателя next . Для каждого тикета введите его src и dest в индексе. Если есть столкновение, это означает, что у нас уже есть соседний билет; соедините узлы и удалите совпадение из индекса. Когда вы закончите, вы сделаете только один проход, у вас будет пустой индекс и связанный список всех заявок по порядку.

Этот метод значительно быстрее: только один проход, а не два; и хранилище значительно меньше (наихудший случай: n / 2; лучший случай: 1; типичный случай: sqrt (n)), достаточно, чтобы вы могли реально использовать хеш вместо двоичного дерева поиска.

2 голосов
/ 08 июня 2010

положить в два хэша: to_end = src -> des; to_beg = des -> src

Выберите любой аэропорт в качестве отправной точки S.

while(to_end[S] != null)
   S = to_end[S];

S теперь ваш конечный пункт назначения. Повторите с другой картой, чтобы найти отправную точку.

Без правильной проверки это выглядит как O (N), при условии, что у вас есть достойная реализация хэш-таблицы.

1 голос
/ 25 июня 2010

Это простой случай матрицы конечного автомата. Извините за псевдокод в стиле C #, но было проще выразить идею с помощью объектов.

Сначала построим матрицу магистрали. Прочитайте мое описание того, что такое матрица магистрали (не беспокойтесь с ответом FSM, просто объяснение матрицы магистрали) на Какие существуют стратегии для тестирования больших конечных автоматов? .

Однако описанные вами ограничения делают случай простым конечным автоматом. Это самый простой конечный автомат из всех возможных.

Для простого случая 5 аэропортов,
вершинные узлы = src / точки входа,
узлы горизонта = DST / точки выхода.

   A1 A2 A3 A4 A5
A1        x
A2  x
A3           x
A4              x
A5     x

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

Чтобы получить путь машины, вы должны отсортировать матрицу в

   A1 A2 A3 A4 A5
A2  x
A1        x
A3           x
A4              x
A5     x

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

   A1 A2 A3 A4 A5
A2  x
A5     x
A1        x
A3           x
A4              x

где заказанные пары - это список билетов:

a2: a1, a5: a2, a1: a3, a3: a4, a4: a5.

или в более формальной записи,

<a2,a1>, <a5,a2>, <a1,a3>, <a3,a4>, <a4,a5>.

Хммм .. заказал пары, а? Чувствуете ли вы намек на рекурсию в Лиспе?

<a2,<a1,<a3,<a4,a5>>>>

Есть два режима работы машины,

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

Полагаю, ваш вопрос касается реконструкции поездки. Таким образом, вы выбираете один билет за другим случайным образом из этой стопки билетов.

Мы предполагаем, что стопка билетов имеет неопределенный размер.

     tak mnx cda 
bom    0
daj       0
phi           0

Где значение 0 обозначает неупорядоченные билеты. Давайте определим неупорядоченный тикет как тикет, в котором его dst не совпадает с src другого тикета.

Следующий следующий тикет обнаруживает, что mnx (dst) = kul (src) совпадают.

     tak mnx cda kul
bom    0
daj       1
phi           0
mnx               0

В любой момент, когда вы выбираете следующий билет, есть вероятность, что он соединяет два последовательных аэропорта. Если это произойдет, вы создадите узел кластера из этих двух узлов:

<bom,tak>, <daj,<mnx,kul>>

и матрица уменьшена,

     tak cda kul
bom    0
daj          L1
phi       0

, где

L1 = <daj,<mnx,kul>>

, который является подсписком основного списка.

Продолжайте выбирать следующие случайные билеты.

     tak cda kul svn xml phi
bom    0
daj          L1
phi       0
olm               0
jdk                   0
klm                       0

Сопоставить либо existing.dst с новым.src
или existing.src для new.dst:

     tak cda kul svn xml
bom    0
daj          L1
olm               0
jdk                   0
klm      L2


<bom,tak>, <daj,<mnx,kul>>, <<klm,phi>, cda>

Вышеуказанное топологическое упражнение предназначено только для визуального понимания. Ниже приведено алгоритмическое решение.

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

Как видите, возможно, это лучше всего сделать с Лиспом.

Однако, как упражнение связанных списков и карт ...

Создайте следующие структуры:

class Ticket:MapEntry<src, Vector<dst> >{
  src, dst
  Vector<dst> dstVec; // sublist of mergers

  //constructor
  Ticket(src,dst){
    this.src=src;
    this.dst=dst;
    this.dstVec.append(dst);
  }
}

class TicketHash<x>{
  x -> TicketMapEntry;

  void add(Ticket t){
    super.put(t.x, t);
  }
}

Так что эффективно,

TicketHash<src>{
  src -> TicketMapEntry;

  void add(Ticket t){
    super.put(t.src, t);
  }
}

TicketHash<dst>{
  dst -> TicketMapEntry;

  void add(Ticket t){
    super.put(t.dst, t);
  }
}    

TicketHash<dst> mapbyDst = hash of map entries(dst->Ticket), key=dst
TicketHash<src> mapbySrc = hash of map entries(src->Ticket), key=src

Когда билет случайно выбирается из колоды,

void pickTicket(Ticket t){
  // does t.dst exist in mapbyDst?
  // i.e. attempt to match src of next ticket to dst of an existent ticket.
  Ticket zt = dstExists(t);

  // check if the merged ticket also matches the other end.
  if(zt!=null)
    t = zt;

  // attempt to match dst of next ticket to src of an existent ticket.
  if (srcExists(t)!=null) return;

  // otherwise if unmatched either way, add the new ticket
  else {
    // Add t.dst to list of existing dst
    mapbyDst.add(t); 
    mapbySrc.add(t);
  }
}

Проверка на наличие dst:

Ticket dstExists(Ticket t){
  // find existing ticket whose dst matches t.src
  Ticket zt = mapbyDst.getEntry(t.src);

  if (zt==null) return false; //no match

  // an ordered pair is matched...

  //Merge new ticket into existent ticket
  //retain existent ticket and discard new ticket.
  Ticket xt = mapbySrc.getEntry(t.src);

  //append sublist of new ticket to sublist of existent ticket
  xt.srcVec.join(t.srcVec); // join the two linked lists.

  // remove the matched dst ticket from mapbyDst
  mapbyDst.remove(zt);
  // replace it with the merged ticket from mapbySrc
  mapbyDst.add(zt);

  return zt;
}

Ticket srcExists(Ticket t){
  // find existing ticket whose dst matches t.src
  Ticket zt = mapbySrc.getEntry(t.dst);

  if (zt==null) return false; //no match

  // an ordered pair is matched...

  //Merge new ticket into existent ticket
  //retain existent ticket and discard new ticket.
  Ticket xt = mapbyDst.getEntry(t.dst);

  //append sublist of new ticket to sublist of existent ticket
  xt.srcVec.join(t.srcVec); // join the two linked lists.

  // remove the matched dst ticket from mapbyDst
  mapbySrc.remove(zt);
  // replace it with the merged ticket from mapbySrc
  mapbySrc.add(zt);

  return zt;
}

Проверка существующего источника:

Ticket srcExists(Ticket t){
  // find existing ticket whose src matches t.dst
  Ticket zt = mapbySrc.getEntry(t.dst);

  if (zt == null) return null;

  // if an ordered pair is matched

  // remove the dst from mapbyDst
  mapbySrc.remove(zt);

  //Merge new ticket into existent ticket
  //reinsert existent ticket and discard new ticket.
  mapbySrc.getEntry(zt);

  //append sublist of new ticket to sublist of existent ticket
  zt.srcVec.append(t.srcVec);
  return zt;
}

У меня такое чувство, что вышеизложенное содержит некоторые опечатки, но концепция должна быть правильной. Любая найденная опечатка, кто-то может помочь исправить это, plsss.

1 голос
/ 07 июня 2010

Каждый аэропорт node. Каждый билет edge. Составьте матрицу смежности для представления графа. Это можно сделать как битовое поле для сжатия краев. Ваша отправная точка будет узлом, который не имеет пути к нему (его столбец будет пустым). Когда вы знаете это, вы просто следуете путям, которые существуют.

Также вы можете построить структуру, индексируемую по аэропорту. Для каждого найденного вами билета это src и dst. Если какой-либо из них не найден, вам нужно добавить новые аэропорты в свой список. Когда каждый найден, вы устанавливаете указатель выхода из аэропорта отправления, чтобы он указывал на пункт назначения, а указатель прибытия в пункт назначения, указывающий на аэропорт отправления. Когда у вас нет билетов, вы должны просмотреть весь список, чтобы определить, у кого нет пути к нему.

Другим способом было бы иметь список мини-поездок переменной длины, которые вы соединяете вместе, когда встречаете каждый билет. Каждый раз, когда вы добавляете билет, вы видите, совпадают ли концы любой существующей мини-поездки с src или dest вашего билета. Если нет, то ваш текущий билет становится его собственной мини-поездкой и добавляется в список. Если это так, то новый билет прикрепляется к концу (ам) существующей поездки, с которой он совпадает, возможно, объединяя две существующие мини-поездки, и в этом случае список мини-поездок сокращается на одну.

0 голосов
/ 25 июня 2010

Предпосылки

Прежде всего, создайте некоторую структуру подтрипов, которая содержит часть вашего маршрута.

Например, если ваша полная поездка составляет a-b-c-d-e-f-g, подтрип может быть b-c-d, то есть подключенным подпутем вашей поездки.

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

Как мы увидим позже, нужно хранить не каждый город, а только начало и конец каждой подтрипы.

Построение подтрипов

Теперь возьмите билеты только один за другим. Мы предполагаем, что билет перейдет от x до y (представлен (x,y)). Проверьте, не является ли x концом некоторого подтрипа s (поскольку каждый город посещается только один раз, он уже не может быть концом другого подтрипа). Если x - это начало, просто добавьте текущий билет (x,y) в конце подпункта s. Если нет подтрипа, заканчивающегося x, проверьте, существует ли подтрип t, начинающийся с y. Если это так, добавьте (x,y) в начале t. Если также нет такого подтрипа t, просто создайте новый подтрип, содержащий просто (x,y).

Работа с подтрипами должна быть сделана с использованием некоторых специальных «трюков».

  • Создание нового подтрипа s, содержащего (x,y), должно добавить x к хеш-таблице для "начинающих городов подтрипов" и добавить y к хеш-таблице для "завершающих городов подтрипов".
  • Добавление нового билета (x,y) в начале подтрипа s=(y,...), должно удалить y из хеш-таблицы начинающих городов и вместо этого добавить x в хеш-таблицу начинающих городов.
  • Добавление нового билета (x,y) в конце подтрипа s=(...,x), должно удалить x из хеш-таблицы конечных городов и вместо этого добавить y в хеш-таблицу конечных городов.

С этой структурой, подтипы, соответствующие городу, могут быть сделаны в амортизированной O (1).

После того, как это сделано для всех билетов, у нас есть несколько подтрипов. Обратите внимание на то, что у нас есть не более (n-1)/2 = O(n) таких подтрипов после процедуры.

Конкатенация субтрипов

Теперь мы просто рассмотрим подтрипы один за другим. Если у нас есть подстрока s=(x,...,y), мы просто смотрим в нашей хэш-таблице конечных городов, если есть подстрока t=(...,x), заканчивающаяся x. Если это так, мы объединяем t и s в новый подтрип. Если нет, то мы знаем, что s является нашей первой подтрипой; затем мы посмотрим, есть ли другая подпункт u=(y,...), начинающийся с y. Если это так, мы объединяем s и u. Мы делаем это до тех пор, пока не останется только одна подпутушка (тогда эта подполочка - это наше первоначальное путешествие).

Надеюсь, я ничего не пропустил, но этот алгоритм должен работать:

  • Построение всех подтрипов (самое большее O(n)) может быть выполнено в O(n), если мы реализуем добавление билетов в подпоток в O(1). Это не должно быть проблемой, если у нас есть хорошая структура указателей или что-то в этом роде (реализация подтрипов в виде связанных списков). Также изменение двух значений в хеш-таблице (амортизируется) O(1). Таким образом, эта фаза потребляет O(n) времени.
  • Конкатенация подтипов до тех пор, пока не останется только одна, также может быть выполнена в O(n). Чтобы увидеть это, нам просто нужно взглянуть на то, что делается на втором этапе: поиск в Hashtable, который нужно амортизировать O(1), и конкатенация подстрок, которую можно выполнить в O(1) с конкатенацией указателей или чем-то еще.

Таким образом, весь алгоритм занимает время O(n), которое может быть оптимальным O -обязанным, поскольку по крайней мере каждый билет может должен быть просмотрен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...