Skip to content

Commit

Permalink
Merge pull request #767 from iking07/Trie
Browse files Browse the repository at this point in the history
Add Important concepts In Trie section
  • Loading branch information
pankaj-bind authored Oct 24, 2024
2 parents b97162a + 2208a21 commit 9c2d2f6
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 2 deletions.
78 changes: 78 additions & 0 deletions Trie/K-th Lexicographical String/Program.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ALPHABET_SIZE 26

// Trie Node Structure
struct TrieNode {
struct TrieNode *children[ALPHABET_SIZE]; // Each node has 26 possible children (for each alphabet letter)
int isEndOfWord; // 1 if this node marks the end of a word
};

// Function to create a new Trie node
struct TrieNode *createNode() {
struct TrieNode *node = (struct TrieNode *)malloc(sizeof(struct TrieNode));
node->isEndOfWord = 0;
for (int i = 0; i < ALPHABET_SIZE; i++)
node->children[i] = NULL; // Initialize all children to NULL
return node;
}

// Insert a word into the Trie
void insert(struct TrieNode *root, const char *word) {
struct TrieNode *current = root;
while (*word) {
int index = *word - 'a'; // Calculate index for each character (0 for 'a', 1 for 'b', etc.)
if (!current->children[index])
current->children[index] = createNode(); // Create new node if it doesn't exist
current = current->children[index];
word++;
}
current->isEndOfWord = 1; // Mark the end of a valid word
}

// Depth-First Search (DFS) to collect words in lexicographical order
void dfs(struct TrieNode *root, char *currentWord, int level, int *k, char *result) {
if (*k <= 0) return; // If we have found the k-th word, stop searching

// If this node marks the end of a word, check if it's the k-th word
if (root->isEndOfWord) {
(*k)--; // Decrease k when a valid word is found
if (*k == 0) {
currentWord[level] = '\0'; // Null-terminate the word
strcpy(result, currentWord); // Copy the word to result
return;
}
}

// Recursively search for children in alphabetical order
for (int i = 0; i < ALPHABET_SIZE; i++) {
if (root->children[i]) {
currentWord[level] = 'a' + i; // Add the current letter to the word
dfs(root->children[i], currentWord, level + 1, k, result); // Go deeper in the Trie
}
}
}
// Function to find the k-th lexicographical string
void findKthWord(struct TrieNode *root, int k, char *result) {
char currentWord[100]; // To store the current word during DFS
dfs(root, currentWord, 0, &k, result); // Start DFS with level 0
}

int main() {
struct TrieNode *root = createNode(); // Create the root of the Trie
char *words[] = {"banana", "apple", "grape", "mango", "orange"};
int n = 5, k = 3; // Example: 5 words and find the 3rd lexicographical word

// Insert all words into the Trie
for (int i = 0; i < n; i++)
insert(root, words[i]);

char result[100]; // To store the k-th lexicographical word
findKthWord(root, k, result); // Find the k-th word

printf("The %d-th lexicographical word is: %s\n", k, result);
return 0;
}
// The 3-th lexicographical word is: grape
47 changes: 47 additions & 0 deletions Trie/K-th Lexicographical String/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
## Problem: K-th Lexicographical String in Trie

### Problem Statement:
Given a set of strings, your task is to find the **k-th lexicographical string** from this set. The lexicographical order is the same as dictionary order, where words are sorted alphabetically. To solve this problem efficiently, we can use a **Trie (Prefix Tree)**, which naturally maintains the order of characters and helps in finding lexicographical order.

### Approach Overview:
A **Trie (Prefix Tree)** allows us to store words in a hierarchical structure, with each node representing a character. By traversing this Trie in alphabetical order, we can collect the strings in lexicographical order and directly retrieve the k-th word.

### Steps:

1. **TrieNode Structure**:
- Each node in the Trie has an array `children[26]` for the lowercase English alphabet (`'a'` to `'z'`).
- `isEndOfWord` marks if the node is the end of a valid word.
- Each node can have 26 possible children, corresponding to each letter.

2. **Insertion**:
- Each word from the input list is inserted into the Trie, character by character.
- If a node for a character does not exist, a new node is created.
- At the end of each word, mark the final node as an `end of word`.

3. **Depth-First Search (DFS)** for K-th Lexicographical Word:
- After inserting the words into the Trie, perform a DFS traversal.
- Start at the root node and visit each child in lexicographical order (starting from `'a'` to `'z'`).
- For each word ending node, decrement the counter `k`.
- Once `k` reaches zero, the current string is the desired k-th lexicographical string.

### Example:
```c
char *words[] = {"banana", "apple", "grape", "mango", "orange"};
int k = 3;
```
1. Insert all the words into the Trie.
2. Traverse the Trie using DFS and find the 3rd lexicographical word.
**Output**:
```
The 3rd lexicographical word is: grape
```
### Key Points:
- The Trie efficiently organizes words such that DFS traversal yields lexicographical order.
- The traversal is designed to visit child nodes from `'a'` to `'z'`, ensuring that words are checked in alphabetical order.
- The algorithm stops when the k-th lexicographical word is found.
### Time Complexity:
- **Insertion**: Inserting all words into the Trie takes O(n * m) time, where `n` is the number of words, and `m` is the average length of the words.
- **Finding the k-th word**: Traversing the Trie in lexicographical order takes O(26 * m), where `m` is the length of the longest word.
2 changes: 1 addition & 1 deletion Trie/LongestCommonPrefix/LongestCommonPrefix.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,4 @@ int main()
printf("Longest Common Prefix: %s\n", prefix);

return 0;
}
}
1 change: 0 additions & 1 deletion Trie/LongestCommonPrefix/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ The longest common prefix we found is `"fl"`.
```
Longest Common Prefix: fl
```
### Key Points:
- The Trie helps build a common prefix by inserting each word one character at a time and following the path that is shared by all words.
Expand Down
72 changes: 72 additions & 0 deletions Trie/Maximum XOR/Program.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#include <stdio.h>
#include <stdlib.h>

#define BITS 31 // We consider 31 bits for non-negative integers

// Trie Node Structure for Binary Trie
struct TrieNode {
struct TrieNode *children[2]; // Binary Trie has 2 children (for bit 0 and bit 1)
};

// Function to create a new Trie node
struct TrieNode *createBinaryNode() {
struct TrieNode *node = (struct TrieNode *)malloc(sizeof(struct TrieNode));
node->children[0] = node->children[1] = NULL; // Initialize both children to NULL
return node;
}

// Insert a number into the binary Trie
void insertBinaryTrie(struct TrieNode *root, int num) {
struct TrieNode *current = root;
for (int i = BITS; i >= 0; i--) {
int bit = (num >> i) & 1; // Get the i-th bit of the number
if (!current->children[bit])
current->children[bit] = createBinaryNode(); // Create a new node for this bit
current = current->children[bit]; // Move to the child corresponding to this bit
}
}

// Find the maximum XOR with a given number in the Trie
int findMaxXOR(struct TrieNode *root, int num) {
struct TrieNode *current = root;
int maxXOR = 0;
for (int i = BITS; i >= 0; i--) {
int bit = (num >> i) & 1; // Get the i-th bit of the number
int oppositeBit = 1 - bit; // We want to find the opposite bit to maximize XOR
if (current->children[oppositeBit]) {
maxXOR = (maxXOR << 1) | 1; // If opposite bit exists, add 1 to maxXOR
current = current->children[oppositeBit]; // Move to the opposite bit node
} else {
maxXOR = (maxXOR << 1); // If opposite bit doesn't exist, add 0 to maxXOR
current = current->children[bit]; // Move to the same bit node
}
}
return maxXOR;
}

// Function to find the maximum XOR of two numbers in an array
int findMaximumXOR(int *nums, int n) {
struct TrieNode *root = createBinaryNode(); // Create the root of the Trie
insertBinaryTrie(root, nums[0]); // Insert the first number into the Trie

int maxResult = 0; // Variable to store the maximum XOR result

// Process the rest of the numbers
for (int i = 1; i < n; i++) {
// Find the maximum XOR for the current number and update maxResult
maxResult = (maxResult > findMaxXOR(root, nums[i])) ? maxResult : findMaxXOR(root, nums[i]);
insertBinaryTrie(root, nums[i]); // Insert the current number into the Trie
}

return maxResult; // Return the maximum XOR found
}
int main() {
int nums[] = {3, 10, 5, 25, 2, 8};
int n = sizeof(nums) / sizeof(nums[0]);

int maxXOR = findMaximumXOR(nums, n); // Find the maximum XOR
printf("Maximum XOR is: %d\n", maxXOR);

return 0;
}
// Maximum XOR is: 28
36 changes: 36 additions & 0 deletions Trie/Maximum XOR/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## Problem: Maximum XOR of Two Numbers

### Problem Statement:
Given an array of non-negative integers, find the maximum XOR of two numbers in the array. The XOR operation between two integers results in a number where each bit is `1` if the corresponding bits of the two numbers differ, and `0` if they are the same.

### Approach Overview:
To find the maximum XOR of two numbers in an array, we use a **Binary Trie** data structure. This allows us to efficiently compare bits of numbers and attempt to maximize the XOR value.

### Steps:
1. **Binary TrieNode Structure**:
- Each node has two children: `children[0]` for the bit `0` and `children[1]` for the bit `1`.
- We represent numbers in their binary form (using up to 31 bits).

2. **Insertion**:
- Each number in the array is inserted into the Trie bit by bit, starting from the most significant bit (MSB).
- New nodes are created as necessary for each bit (`0` or `1`).

3. **Finding Maximum XOR**:
- For each number, we traverse the Trie and try to maximize the XOR by choosing the opposite bit at each step (e.g., if the current bit is `0`, try to go to `1`, and vice versa).
- The XOR is maximized when we take the opposite bit at each level of the Trie.

### Example:
```c
int nums[] = {3, 10, 5, 25, 2, 8};
```
1. Insert the numbers into the Trie.
2. For each number, calculate the maximum XOR by traversing the Trie and choosing the path that gives the highest XOR.
**Output**:
```
Maximum XOR: 28
```
### Key Points:
- The Binary Trie allows efficient lookup and insertion of binary representations of numbers.
- The XOR is maximized by selecting the opposite bit at each level, ensuring we get the maximum possible value.

0 comments on commit 9c2d2f6

Please sign in to comment.