找到仅出现一次的第一个元素

时间:2013-07-09 08:50:33

标签: algorithm

这是Google的一次采访难题。

问题是找到数组中只出现一次的第一个元素。

例如,abaaacdgadgf被给出。我们需要输出b

简单的解决方案似乎是首先使用哈希表计算每个元素,然后再次循环以获取第一个元素。它将使用2个循环。

是否可以仅使用1个循环获得结果?

我试图搞清楚,但似乎不可能。

5 个答案:

答案 0 :(得分:4)

哈希表指向链表中的项目。添加项目时,将创建哈希表条目,并将指针添加到列表的尾部。找到副本后,可以从列表中删除该项目。

仅出现一次的第一个元素将是列表中的第一个项目。

这段代码有些凌乱,因为大部分代码都是链表实现。

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

typedef struct stLISTITEM
{
    char data;
    struct stLISTITEM* previous;
    struct stLISTITEM* next;
} LISTITEM;

char firstCharThatOccursOnce(const char* s) {
    char ret;
    LISTITEM* head;
    LISTITEM* tail;
    LISTITEM* table[CHAR_MAX + 1] = {NULL}; /* Just pretend this is a hash table please */
    LISTITEM* cur;
    int i;

    head = malloc(sizeof(*head));
    tail = malloc(sizeof(*tail));

    head->next = tail;
    tail->previous = head;
    tail->data = '\0'; /* If all characters are repeated then return NULL character */

    for (; *s; s++) {
        cur = table[*s];

        if (cur == NULL) {
            /* Item hasn't been seen before */

            cur = malloc(sizeof(*cur));
            cur->data = *s;

            /* Add it to the end of the list */
            tail->previous->next = cur;
            cur->previous = tail->previous;
            tail->previous = cur;
            cur->next = tail;

            /* Add it to the table */
            table[*s] = cur;
        }
        else if (cur->next == NULL) {
            /* Seen it before, but already removed */
        }
        else {
            /* Seen it before, remove from list */
            cur->previous->next = cur->next;
            cur->next->previous = cur->previous;

            cur->next = NULL;
            cur->previous = NULL;
        }
    }

    ret = head->next->data;

    for (i = 0; i <= CHAR_MAX; i++) {
        free(table[i]);
    }

    free(head);
    free(tail);

    return ret;
}

int main(int argc, char const *argv[])
{
    char result = firstCharThatOccursOnce("abaaacdgadgf");

    printf("'%c' (%i)\n", result, result);

    return 0;
}

答案 1 :(得分:2)

这是我的解决方案:

每个'char'有4个可能的统计数据:

  • 1:从未见过。
  • 2:见过一个
  • 3:由于多次出现而被淘汰。
  • 4:合格

我创建了一个大小为26的数组(对于每个'char')来存储字符的统计信息 合格的元素放在双链表的末尾。

扫描输入数据并根据需要进行所有更新。 然后从头到尾扫描列表。第一个非'消除(状态3)' 是你的回答。

complexity : n+(26x3) where n = length(dataset)

答案 2 :(得分:2)

是。在哈希表中,不是维护计数,而是维护遇到该元素的第一个索引。还维护一个排序的所有unique-so-far元素集,键入该索引。然后,只需查找已排序集中剩余的最小密钥。

encountered = dict()
unique = sorted_set()

for i in range(len(A)):
    elem = A[i]
    if elem in encountered:
        first_index = encountered[elem]
        del unique[first_index]
    else:
        unique[i] = elem
        encountered[elem] = i

min_index = unique.keys()[0]
first_unique_elem = A[min_index]

答案 3 :(得分:1)

我没有阅读其他答案只是因为我想自己试一试。
让我们迭代地改进我们的解决方案。
我们在时间和空间复杂性方面的分析将要求我们首先明确说明一些事情:

N = length of string 
M = numbers of characters in alphabet

暴力算法是遍历字符串,对于字符串的每个元素,我们向右搜索,看它是否有重复。
时间复杂度:O(N 2
空间复杂度:O(1)

我们可以做得更好吗? 当然,我们可以遍历字符串并对字符的出现进行多次计数。通过字符串进行另一次遍历以找到计数为1的第一个字符。
时间复杂度:O(N + M)
空间复杂度:O(M)

为什么这是O(N + M)?
因为我们需要首先将count数组的元素初始化为0。所以即使输入是“a”,我们也需要初始化M个元素的count数组。

我们可以做得更好吗? 首先让我们告诉采访者这个任务是Omega(N),因为我们必须至少看一次字符串的每个元素。通过查看“aaaaaaz”的输入实例来实现这一点。 因此,我们的目标并不是要提高时间复杂度,只需通过字符串进行遍历一次就可以更好地延长实际运行时间。
这确实是可能的。

for(int i=0;i<N;i++)
{
  if(visited[i]==-2)continue;
  if(visited[i]==-1)visited[i]=i;continue;
  visited[i]=-1;
}
int F=N;
char res=' ';
for(int i=0;i<M;i++)
{
  if(visited[i]>=0)
  {
    F=min(F,visited[i]);
    res=A[visited[i]];
  }
}
return res;

时间复杂度:O(N + M)
空间复杂度:O(M)

我们可以做得更好吗?

我们可以在O(N)中这样做吗? 我仍在考虑在真正的O(N).IF中实现这一点的方法。我找到了解决方案,我将更新这个答案。

答案 4 :(得分:1)

您可以使用trie代替哈希表。如果输入数据与哈希函数共谋,哈希表将获得二次性能。特里不受此影响。

至于另一个循环,我不会太担心它。这是渐进式的复杂性。无论你通过消除循环赢得什么,你可能会失去其他代码的复杂性。