Я написал следующую программу, которая должна ответить на этот вопрос
Напишите эффективную функцию для поиска первого неповторенного символа в
строка. Например, первый неповторяющийся символ «итого»
«о» и первый неповторяющийся символ в «качели» это «р». обсуждать
эффективность вашего алгоритма.
Вот что я сделал:
#include <stdio.h>
#include <iostream>
#include <vector>
using namespace std;
class Node
{
public:
Node::Node(char ch)
{
c = ch;
next = NULL;
}
char c;
Node *next;
};
Node* addNode(Node *tail, char ch)
{
if(tail == NULL)
return new Node(ch);
else
{
Node *newN = new Node(ch);
tail->next = newN;
return newN;
}
}
void deleteNode(char ch, Node** head, Node**tail)
{
Node *prev = NULL;
Node *cur = *head;
while(cur!=NULL)
{
if(cur->c == ch)
{
// found cut it
if(prev == NULL)
{
// head cut off
if(*tail == *head)
{
// worst possible, just one element
delete *head;
*head = NULL;
return;
}
else
{
// Head cut off but not just first element
Node *tmp = *head;
*head = (*head)->next;
delete tmp;
return;
}
}
else
{
// delete normal node
if(*tail == cur)
{
// delete tail
Node *tmp = *tail;
*tail = prev;
delete tmp;
return;
}
else
{
// Normal node not tail
prev->next = cur->next;
delete cur;
return;
}
}
}
// no match keep searching
prev = cur;
cur = cur->next;
}
}
int main()
{
char str[] = "total";
char htable[26];
memset(htable, 0, sizeof(char)*26);
Node *head = NULL;
Node *tail = head;
for(unsigned int i=0;;i++)
{
if(str[i] == '\0')
break;
// check first match
char m = htable[str[i]-'a'];
switch(m)
{
case 0:
{
// first time, add it to linked list
htable[str[i]-'a']++;
tail = addNode(tail, str[i]);
if(head == NULL)
head = tail;
}break;
case 1:
{
// bam, cut it out
htable[str[i]-'a']++;
deleteNode(str[i], &head, &tail);
}break;
}
}
if(head != NULL)
printf("First char without repetition: %c", head->c);
else
printf("No char matched");
return 0;
}
и это работает (хотя я не освободил память в конце программы для связанного списка). Обычно я хэш-таблицу держу с 0, если символ еще не найден, с 1, если он был найден один раз (и он добавлен в связанный список в хвостовой позиции) и 2, если есть хотя бы два вхождения (и должен быть удален связанным списком).
Какова вычислительная сложность этой программы с записью big-O?
Так как этот алгоритм просто проходит один раз для каждого элемента, я думаю, что это O (n), хотя удаление значений в связанном списке (в худшем из возможных случаев) потребовало бы дополнительного O (k ^ 2), где k — длина алфавит используется. Что-то вроде O (n + k ^ 2) — мой выбор, и если строка очень длинная и алфавит ограничен, алгоритм становится очень эффективным.
Ну, да, на поверхности это выглядит O(N)
сложность, но вы ввели нежелательную неэффективность с помощью динамического распределения.
Однако, потому что вы звоните deleteNode
и что для поиска в списке, у вас больше нет O(N)
сложность.
Подумайте, что произойдет, если у вас есть строка:
abcdefghijklmnopqrstuvwxyzzyxwvutsrqponmlkjihgfedcb
Это имеет сложность примерно O(N*(N-1)/2)
потому что каждый deleteNode
вызов должен сканировать до конца оставшегося списка.
Все, что вам действительно нужно сделать, это отсканировать строку один раз и сохранить индекс каждого символа (если он еще не найден), а затем отсканировать этот массив на предмет наименьшего индекса. Если вам нравится, вы можете использовать индекс -1
для обозначения персонажа не встречалось. Это было бы сложностью O(2N)
Из описания алгоритма:
«Это должно быть как минимум O (n), потому что вы не знаете, будет ли повторяться символ, пока не прочитаете все символы.», Также смотрите: Найти первый неповторенный символ в строке.
В вашем алгоритме я не вижу сложности O (k ^ 2) для удаления элементов из связанного списка. Удаление элемента из связанного списка: O (n) + O (1) = O (n), где O (n) — поиск, O (1) — стирание, как только вы нашли узел.
Поскольку связанный список может содержать не более k элементов, удаление занимает O (k). Так как это внутри цикла for => O (n * k).
Поскольку k постоянна => O (n), сложность — однако, это может быть реализовано намного проще.
Опять посмотрим https://stackoverflow.com/a/2285561/592636 (используя два массива).