异或(今日头条2017秋招真题)Trie树

题目描述

给定整数m以及n个数字A1, A2, …, An,将数列A中所有元素两两异或,共能得到n(n-1)/2个结果。请求出这些结果中大于m的有多少个。

输入
第一行包含两个整数n, m。
第二行给出n个整数A1, A2, …, An。
样例输入
3 10
6 5 10
输出
输出仅包括一行,即所求的答案。
样例输出
2

一看到这道题开始想用暴力求解O(n^2),毫不意外超时了

后来看了大神的解答,利用Trie树,主要思想如下:
1,使用字典树(TrieTree)从高位到低位建立字典
2,再使用每个元素依次去字典中查对应高位异或结果。
3,若m对应位置为1, 则当前元素在该位的异或也必须为1
4,若m对应位置为0,则加上与当前元素异或结果为1的元素个数
5,将所有元素查找后的结果相加,然后再除以2,就是最终的结果。

#include
using namespace std;

const int N=100000;
int a[N];
struct Trie
{
    int count;
    Trie * next[2];//一个节点有两个分支0,1
    Trie()
    {
        count = 0;
        next[0] = NULL;
        next[1] = NULL;
    }
};

void insert(Trie * root, int num)
{
    Trie * p = root;
    for(int i = 31;i >=0;i--)
    {
        int temp = (num>>i)&1;
        if(p->next[temp] == NULL)
            p->next[temp] = new Trie();
        p = p->next[temp];
        p->count++;
    }
}

long long find(Trie * root, int m, int data)
{
    Trie * p = root;
    int x, y;
    long long result =0;
    for(int i = 31;i >=0;i--)
    {
        x = (data>>i)&1;
        y = (m>>i)&1;
        if(y == 1)//如果m的位为1,走向与x异或为1的分支
            p = p->next[x^1];
        else
        {
            if(p->next[x^1]!=NULL)//如果m的位为0,加上与x异或为1的count的数
                result += p->next[x^1]->count;
            p = p->next[x];//依然走向x分支
        }
        if(p == NULL)
            break;
    }
    return result;
}

int main()
{
    int n,m,i;
    cin>>n>>m;
    Trie * root = new Trie();
    for(i =0;i < n;i++)
    {
        cin>>a[i];
        insert(root, a[i]);
    }
    long long result=0;
    for(i =0;i < n;i++)
        result += find(root, m, a[i]);
    result = result/2;
    cout<return 0;
}

复习一下Trie树
Trie树,又称字典树,单词查找树或者前缀树,是一种用于快速检索的多叉树结构,如英文字母的字典树是一个26叉树,数字的字典树是一个10叉树。
Trie树可以利用字符串的公共前缀来节约存储空间。如下图所示,该trie树用10个节点保存了6个字符串tea,ten,to,in,inn,int:
异或(今日头条2017秋招真题)Trie树_第1张图片

在该trie树中,字符串in,inn和int的公共前缀是“in”,因此可以只存储一份“in”以节省空间。当然,如果系统中存在大量字符串且这些字符串基本没有公共前缀,则相应的trie树将非常消耗内存,这也是trie树的一个缺点。
Trie树的基本性质可以归纳为:
(1)根节点不包含字符,除根节点意外每个节点只包含一个字符。
(2)从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
(3)每个节点的所有子节点包含的字符串不相同。

字母树的插入(Insert)、删除( Delete)和查找(Find)都非常简单,用一个一重循环即可,即第i 次循环找到前i 个字母所对应的子树,然后进行相应的操作。实现这棵字母树,我们用最常见的数组保存(静态开辟内存)即可,当然也可以开动态的指针类型(动态开辟内存)。下面给出动态开辟内存的实现:

#define MAX_NUM 26
enum NODE_TYPE{ //"COMPLETED" means a string is generated so far.
  COMPLETED,
  UNCOMPLETED
};
struct Node {
  enum NODE_TYPE type;
  char ch;
  struct Node* child[MAX_NUM]; //26-tree->a, b ,c, .....z
};

struct Node* ROOT; //tree root

struct Node* createNewNode(char ch){
  // create a new node
  struct Node *new_node = (struct Node*)malloc(sizeof(struct Node));
  new_node->ch = ch;
  new_node->type == UNCOMPLETED;
  int i;
  for(i = 0; i < MAX_NUM; i++)
    new_node->child[i] = NULL;
  return new_node;
}

void initialization() {
//intiazation: creat an empty tree, with only a ROOT
ROOT = createNewNode(' ');
}

int charToindex(char ch) { //a "char" maps to an index
return ch - 'a'; } int find(const char chars[], int len) { struct Node* ptr = ROOT; int i = 0; while(i < len) { if(ptr->child[charToindex(chars[i])] == NULL) { break; } ptr = ptr->child[charToindex(chars[i])]; i++; } return (i == len) && (ptr->type == COMPLETED); } void insert(const char chars[], int len) { struct Node* ptr = ROOT; int i; for(i = 0; i < len; i++) { if(ptr->child[charToindex(chars[i])] == NULL) { ptr->child[charToindex(chars[i])] = createNewNode(chars[i]); } ptr = ptr->child[charToindex(chars[i])]; } ptr->type = COMPLETED; }

你可能感兴趣的:(笔试题)