Unit Iii
Unit Iii
SYLLABUS
Tree Algorithms: Fenwick Tree, Segment Tree –
Applications-Range Sum Queries. Treap- Applications- Kth
Largest Element in an Array.
Trie: Introduction, Suffix Tree, Applications-Index Pairs of a
String, Longest word with all prefixes, top K frequent words.
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
Tree Algorithms
Applications
1. Fenwick Tree.
Fenwick Tree
Fenwick Tree- also known as Binary Indexed Tree (BIT)—were invented by Peter
M.Fenwick in 1994.
Fenwick Tree provides a way to represent an array of numbers in an array, allowing prefix
sums to be calculated efficiently.
For example, an array is [2, 3, -1, 0, 6] the length 3 prefix [2, 3, -1] with sum 2 + 3 + -1 = 4.
Calculating prefix sums efficiently is useful in various scenarios.
Let's start with a simple problem.
We are given an array a[], and we want to be able to perform two types of operations on it.
1. Change the value stored at an index i. (This is called a point update operation)
2. Find the sum of a prefix of length k. (This is called a range sum query)
This is a perfect solution, but unfortunately the time required to calculate a prefix sum is
proportional to the length of the array, so this will usually time out when large number of
such intermingled operations are performed.
One efficient solution is to use segment tree that can perform both operation in O(logN) time.
Using binary Indexed tree also, we can perform both the tasks in O(logN) time.
But then why learn another data structure when segment tree can do the work for us. It’s
because binary indexed trees require less space and are very easy to implement during
programming contests.
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
Before starting with binary indexed tree, we need to understand a particular bit manipulation
trick. Here it goes.
How to isolate?
Example:-
The last set bit is given by x & (-x) = (10)1(0) & (01)1(0) = 0010 = 2(in decimal)
We know the fact that each integer can be represented as sum of powers of two.
Similarly, for a given array of size N, we can maintain an array BIT[] such that, at any index
we can store sum of some numbers of the given array. This can also be called a partial sum
tree.
int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
The above picture shows the binary indexed tree, each enclosed box of which denotes the
value BIT[index] and each BIT[index] stores partial sum of some numbers.
Notice
BIT[X]={ a[x] -----if x is odd
a[1]+……….+a[x] ------- if x is power of 2 }
To generalize this, every index i in the BIT[] array stores the cumulative sum from the index i
to i - (1 << r) + 1 (both inclusive), where r represents the last set bit in the index i
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
Applications
Fenwick tree can be used to calculate Range Sum, i.e., finding the sum within range.
Sum(1,7) in the below example array is
Value 5 2 9 -3 5 20 10 -7 2 3 -4 0 -2 15 5
Index 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Updation
When we Update the array recomputing the Fenwick tree will not be too costly, see the below
code for updation.
void add(int i, int k)
{
while (i < T.length)
{
T[i] += k;
i += i & -i; // add last set bit
}
}
Example :- add(4,10) i.e., making index 4 value to be 7
BIT[00100] = 13 + 10 =23
BIT[01000] = 41 + 10 = 51
BIT[10000] is out of array index, function holds.
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
}
return sum;
}
Sample input:
85
1 2 13 4 25 16 17 8
126
107
2 2 18
2 4 17
127
Output:
75
86
80
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
Segment Tree
➢ Segment tree or Segtree is basically a binary tree used for storing the intervals or
segments.
➢ Each node in the segment tree represents an interval.
Consider an array A of size N and a corresponding Segtree T:
➢ The root of T will represent the whole array A[0:N-1].
➢ Each leaf in the Segtree T will represent a single element A[i] such that 0 <= i < N.
➢ The internal nodes in the Segtree T represent union of elementary intervals A[i:j] where 0
<= i < j < N.
➢ The root of the Segtree will represent the whole array A[0:N-1]. Then we will break the
interval or segment into half and the two children of the root will represent the A[0:(N-1) /
2] and A[(N-1) / 2 + 1:(N-1)].
➢ So, in each step we will divide the interval into half and the two children will represent the
two halves, so the height of the segment tree will be log2N. There are N leaves
representing the N elements of the array. The number of internal nodes is N-1. So total
number of nodes are 2*N - 1.
➢ Once we have built a Segtree we cannot change its structure i.e., its structure is static. We
can update the values of nodes but we cannot change its structure. Segment tree is
recursive in nature. Because of its recursive nature, Segment tree is very easy to
implement. Segment tree provides two operations:
➢ Update: In this operation we can update an element in the Array and reflect the
corresponding change in the Segment tree.
➢ Query: In this operation we can query on an interval or segment and return the answer to
the problem on that particular interval.
Implementation:
Since a segment tree is a binary tree, we can use a simple linear array to represent the
segment tree. In almost any segment tree problem we need to think about what we need to
store in the segment tree?
For example, if we want to find the sum of all the elements in an array from index left to
right, then at each node (except leaf nodes) we will store the sum of its child nodes. If we
want to find the minimum of all the elements in an array from index left to right, then at each
node (except leaf nodes) we will store the minimum of its child nodes.
Once we know what we need to store in the segment tree we can build the tree
using recursion (bottom-up approach). We will start with the leaves and go up to the root
and update the corresponding changes in the nodes that are in the path from leaves to root.
Leaves represent a single element. In each step we will merge two children to form an
internal node. Each internal node will represent a union of its children’s intervals. Merging
may be different for different problems. So, recursion will end up at root which will represent
the whole array.
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
For update, we simply have to search the leaf that contains the element to update. This
can be done by going to either on the left child or the right child depending on the interval
which contains the element. Once we found the leaf, we will update it and again use the
bottom-up approach to update the corresponding change in the path from leaf to root.
To make a query on the segment tree we will be given a range from l to r. We will
recurse on the tree starting from the root and check if the interval represented by the node is
completely in the range from l to r. If the interval represented by a node is completely in the
range from l to r, we will return that node’s value.
Let us see how to use segment tree and what we will store in the segment tree in this
problem. As we know that each node of the segtree will represent an interval or segment. In
this problem we need to find the sum of all the elements in the given range. So, in each node
we will store the sum of all the elements of the interval represented by the node. How do we
do that? We will build a segment tree using recursion (bottom-up approach) as explained
above. Each leaf will have a single element. All the internal nodes will have the sum of both
of its children.
To update an element, we need to look at the interval in which the element is and recurse
accordingly on the left or the right child.
void update(int node, int start, int end, int idx, int val)
{
if(start == end)
{
// Leaf node
A[idx] += val;
tree[node] += val;
}
else
{
int mid = (start + end) / 2;
if(start <= idx and idx <= mid)
{
// If idx is in the left child, recurse on the left child
update(2*node, start, mid, idx, val);
}
else
{
// if idx is in the right child, recurse on the right child
update(2*node+1, mid+1, end, idx, val);
}
// Internal node will have the sum of both of its children
tree[node] = tree[2*node] + tree[2*node+1];
}
}
Complexity of update will be O(logN).
If the range represented by a node is completely outside the given range, we will
simply return 0. If the range represented by a node is completely inside the given range, we
will return the value of the node which is the sum of all the elements in the range represented
by the node. And if the range represented by a node is partially inside and partially outside
the given range, we will return sum of the left child and the right child. Complexity of query
will be O(logN).
Sometimes problems will ask you to update an interval from l to r, instead of a single
element. One solution is to update all the elements one by one. Complexity of this approach
will be O(N) per operation since where are N elements in the array and updating a single
element will take O(logN) time.
To avoid multiple call to update function, we can modify the update function to work
on an interval.
void updateRange(int node, int start, int end, int l, int r, int val)
{
// out of range
if (start > end or start > r or end < l)
return;
// Current node is a leaf node
if (start == end)
{
// Add the difference to current node
tree[node] += val;
return;
}
// If not a leaf node, recur for children.
int mid = (start + end) / 2;
updateRange(node*2, start, mid, l, r, val);
updateRange(node*2 + 1, mid + 1, end, l, r, val);
// Use the result of children calls to update this node
tree[node] = tree[node*2] + tree[node*2+1];
}
Let's be Lazy i.e., do work only when needed. How? When we need to update an
interval, we will update a node and mark its child that it needs to be updated and update it
when needed. For this we need an array lazy[] of the same size as that of segment tree.
Initially all the elements of the lazy[] array will be 0 representing that there is no pending
update. If there is non-zero element lazy[k] then this element needs to update node k in the
segment tree before making any query operation.
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
1. If current segment tree node has any pending update, then first add that pending update to
current node.
2. If the interval represented by current node lies completely in the interval to update, then
update the current node and update the lazy[] array for children nodes.
3. If the interval represented by current node overlaps with the interval to update, then
update the nodes as the earlier update function
Since we have changed the update function to postpone the update operation, we will
have to change the query function also. The only change we need to make is to check if there
is any pending update operation on that node. If there is a pending update operation, first
update the node and then work same as the earlier query function.
void updateRange (int node, int start, int end, int l, int r, int val)
{
if(lazy[node] != 0)
{
// This node needs to be updated
tree[node] += (end - start + 1) * lazy[node]; // Update it
if(start != end)
{
lazy[node*2] += lazy[node]; // Mark child as lazy
lazy[node*2+1] += lazy[node]; // Mark child as lazy
}
lazy[node] = 0; // Reset it
}
if(start > end or start > r or end < l) // Current segment is not within range [l, r]
return;
if(start >= l and end <= r)
{
// Segment is fully within range
tree[node] += (end - start + 1) * val;
if(start != end)
{
// Not leaf node
lazy[node*2] += val;
lazy[node*2+1] += val;
}
return;
}
int mid = (start + end) / 2;
updateRange(node*2, start, mid, l, r, val); // Updating left child
updateRange(node*2 + 1, mid + 1, end, l, r, val); // Updating right child
tree[node] = tree[node*2] + tree[node*2+1]; // Updating root with max value
}
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
import java.util.*;
public class SegmentTree
{
class SegmentTreeNode
{
int start, end;
SegmentTreeNode left, right;
int sum;
printTree(root.left);
printTree(root.right);
}
else
{
int mid = (start + end) / 2;
ret.left = buildTree(nums, start, mid);
ret.right = buildTree(nums, mid + 1, end);
ret.sum = ret.left.sum + ret.right.sum;
}
return ret;
}
}
{
if (end <= mid)
{
return sumRange(root.left, start, end);
}
else if (start >= mid+1)
{
return sumRange(root.right, start, end);
}
else
{
return sumRange(root.left, start, mid) + sumRange(root.right,
mid+1, end);
}
}
}
}
class SegmentTreeTest
{
public static void main(String args[] )
{
Scanner scan = new Scanner(System.in);
int n=scan.nextInt();
int q=scan.nextInt();
int[] nums=new int[n];
for(int i=0; i<n; i++)
{
nums[i] = scan.nextInt();
}
SegmentTree st = new SegmentTree(nums);
while(q != 0)
{
int opt=scan.nextInt();
if(opt==1)
{
int s1 = scan.nextInt();
int s2 = scan.nextInt();
System.out.println(st.sumRange(s1,s2));
}
else
{
int ind = scan.nextInt();
int val= scan.nextInt();
st.update(ind, val);
}
q--;
}
}
}
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
Sample input :-
85
4 2 13 4 25 16 17 8
126
107
2 2 18
2 4 17
127
Output :-
75
89
80
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
Treap
➢ A treap is a data structure which combines binary tree and binary heap (hence the name:
tree + heap ⇒ Treap).
➢ Treap is a Balanced Binary Search Tree, but not guaranteed to have height as O(log n).
➢ The idea is to use Randomization and Binary Heap property to maintain balance with high
probability.
➢ The expected time complexity of search, insert and delete is O(log n).
➢ More specifically, treap is a data structure that stores pairs(X,Y) in a binary tree in such a
way that it is a binary search tree by X and a binary heap by Y.
➢ If some node of the tree contains values (X0,Y0), all nodes in the left subtree have X<=X0
all nodes in the right subtree have X0<= X, and all nodes in both left and right subtrees
have Y<=Y0. Here X denotes the key value and Y denotes the priority or weights.
1) Key Follows standard BST ordering (left is smaller and right is greater)
2) Priority(or weights) Randomly assigned value that follows Max-Heap property.
1. Insertion in Treap
To insert a new key x into the treap, generate a random priority y for x. Binary search
for x in the tree, and create a new node at the leaf position where the binary search determines
a node for x should exist. Then as long as x is not the root of the tree and has a larger priority
number than its parent z, perform a tree rotation that reverses the parent-child relation
between x and z.
1. Create new node with key equals to x and value equals to a random value.
2. Perform standard BST insert.
3. Use rotations to make sure that inserted node's priority follows max heap property.
2. Deletion in Treap
To delete a node x from the treap, remove it if it is a leaf of the tree. If x has a single
child, z, remove x from the tree and make z be the child of the parent of x (or make z the root
of the tree if x had no parent). Finally, if x has two children, swap its position in the tree with
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
its immediate successor z in the sorted order, resulting in one of the previous cases. In this
last case, the swap may violate the heap-ordering property for z, so additional rotations may
need to be performed to restore this property.
1. If node to be deleted is a leaf, delete it.
2. Else replace node's priority with minus infinite ( -INF ), and do appropriate rotations to
bring the node down to a leaf.
3. Searching in Treap
To search for a given key value, apply a standard search algorithm in a binary search
tree, ignoring the priorities.
// A Treap Node
class TreapNode
{
int key, priority;
TreapNode left, right;
}
class treap
{
public static TreapNode rightRotate(TreapNode y)
{
System.out.println("rightRotate " + y.key);
TreapNode x = y.left;
TreapNode T2 = x.right;
// Perform rotation
x.right = y;
y.left = T2;
TreapNode y = x.right;
TreapNode T2 = y.left;
// Perform rotation
y.left = x;
x.right = T2;
return temp;
}
if (root == null)
return root;
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
// IF KEY IS AT ROOT
// If left is NULL
else if (root.left == null)
{
TreapNode temp = root.right;
root = temp; // Make right child as root
}
// If Right is NULL
else if (root.right == null)
{
TreapNode temp = root.left;
root = temp; // Make left child as root
}
// If key is at root and both left and right are not NULL
// If priority of right child is greater, perform left rotation at node
else if (root.left.priority < root.right.priority)
{
root = leftRotate(root);
root.left = deleteNode(root.left, key);
}
else // If priority of left child is greater, perform right rotation at node
{
root = rightRotate(root);
root.right = deleteNode(root.right, key);
}
return root;
}
if (root != null)
{
System.out.println("key: " + root.key + " | priority: " + root.priority);
preorder(root.left);
preorder(root.right);
}
}
// 6
// 2 4 3 1 7 5
do
{
System.out.println("Enter item to delete ");
key = sc.nextInt();
root = deleteNode(root, key);
System.out.println("After delete");
preorder(root);
} while(key != -1 && root != null);
}
}
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
Input:-
6
234517
Output:-
key: 2 | priority: 94
key: 1 | priority: 47
key: 7 | priority: 85
key: 5 | priority: 23
key: 4 | priority: 14
key: 3 | priority: 6
(note : here only partial output is shown this is for your understanding purpose)
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
Given an integer array nums and an integer k, return the kth largest element in the
array.
Note that it is the kth largest element in the sorted order, not the kth distinct element.
Example 1:
Output: 5
Example 2:
Output: 4
Note: You are supposed to print the K'th largest height in the sorted order of heights[].
Explanation
➢ In the first example, we can see that k=2, so we have to find the second largest
element in this array. If we rearrange the array in descending
order [9,7,6,4,3,2,1][9,7,6,4,3,2,1], we can see that the element 7 is in the second
position, so it is considered the second largest element.
➢ In the second example, we can see that k=4, so we have to find the fourth largest
element in this array. If we rearrange the array in descending
order [90,65,34,12][90,65,34,12], we can see that the element 12 is in the fourth
position, so it is considered the fourth largest element.
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
KthLargest.java
import java.util.*;
class TreapNode
{
int data;
int priority;
TreapNode left;
TreapNode right;
TreapNode(int data)
{
this.data = data;
this.priority = new Random().nextInt(1000);
this.left = this.right = null;
}
}
class KthLargest
{
static int k;
public static TreapNode rotateLeft(TreapNode root)
{
TreapNode R = root.right;
TreapNode X = root.right.left;
R.left = root;
root.right = X;
return R;
}
root = rotateRight(root);
}
}
else
{
root.right = insertNode(root.right, data);
if (root.right != null && root.right.priority > root.priority)
{
root = rotateLeft(root);
}
}
return root;
}
{
root = insertNode(root,a);
}
printTreap(root);
inorder(root);
}
}
Test case=1
Input =6 3
243165
Output =4
Test case=2
Input =6 2
321564
Output =5
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
We already know that the Trie data structure can contain any number of characters
including alphabets, numbers, and special characters. But for this example, we will discuss
strings with characters a-z. Therefore, only 26 pointers need for every node, where the 0th
index represents ‘a’ and the 25th index represents ‘z’ characters.
Any lowercase English word can start with a-z, then the next letter of the word could
be a-z, the third letter of the word again could be a-z, and so on. So for storing a word, we
need to take an array (container) of size 26 and initially, all the characters are empty as there
are no words and it will look as shown below.
Let’s see how a word “and” and “ant” is stored in the Trie data structure:
➢ The word “and” starts with “a“, So we will mark the position “a” as filled in the
Trie node, which represents the use of “a”.
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
➢ After placing the first character, for the second character again there are 26
possibilities, So from “a“, again there is an array of size 26, for storing the 2nd
character.
➢ The second character is “n“, So from “a“, we will move to “n” and mark “n” in the
2nd array as used.
➢ After “n“, the 3rd character is “d“, So mark the position “d” as used in the
respective array.
2. Store “ant” in the Trie data structure:
➢ The word “ant” starts with “a” and the position of “a” in the root node has already
been filled. So, no need to fill it again, just move to the node ‘a‘ in Trie
➢ For the second character ‘n‘ we can observe that the position of ‘n’ in the ‘a’ node
has already been filled. So, no need to fill it again, just move to node ‘n’ in Trie.
➢ For the last character ‘t‘ of the word, The position for ‘t‘ in the ‘n‘ node is not
filled. So, filled the position of ‘t‘ in ‘n‘ node and move to ‘t‘ node.
After storing the word “and” and “ant” the Trie will look like this:
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
Every Trie node consists of a character pointer array or hash map and a flag to
represent if the word is ending at that node or not. But if the words contain only lower-case
letters (i.e. a-z), then we can define Trie Node with an array instead of a hash map.
This operation is used to insert new strings into the Trie data structure. Let us see how this
works: Let us try to Insert “and” & “ant” in this Trie:
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
From the above representation of insertion, we can see that the word “and” & “ant” have
shared some common node (i.e “an”) this is because of the property of the Trie data structure
that If two strings have a common prefix then they will have the same ancestor in the trie.
Now let us try to Insert “dad” & “do”:
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
➢ Define a function insert(TrieNode *root, string &word) which will take two parameters
one for the root and the other for the string that we want to insert in the Trie data
structure.
➢ Now take another pointer currentNode and initialize it with the root node.
➢ Iterate over the length of the given string and check if the value is NULL or not in the
array of pointers at the current character of the string.
✓ If It’s NULL then, make a new node and point the current character to
➢ Finally, increment the wordCount of the last currentNode, this implies that there is a
string ending currentNode.
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
Search operation in Trie is performed in a similar way as the insertion operation but
the only difference is that whenever we find that the array of pointers in curr node does not
point to the current character of the word then return false instead of creating a new node
for that current character of the word.
This operation is used to search whether a string is present in the Trie data structure or not.
There are two search approaches in the Trie data structure.
1. Find whether the given word exists in Trie.
2. Find whether any word that starts with the given prefix exists in Trie.
There is a similar search pattern in both approaches. The first step in searching a given
word in Trie is to convert the word to characters and then compare every character with the
trie node from the root node. If the current character is present in the node, move forward to
its children. Repeat this process until all characters are found.
It is similar to prefix search but additionally, we have to check if the word is ending at the last
character of the word or not.
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
This operation is used to delete strings from the Trie data structure. There are three cases
when deleting a word from Trie.
1. The deleted word is a prefix of other words in Trie.
2. The deleted word shares a common prefix with other words in Trie.
3. The deleted word does not share any common prefix with other words in Trie.
1. The deleted word is a prefix of other words in Trie.
As shown in the following figure, the deleted word “an” share a complete prefix with another
word “and” and “ant“.
An easy solution to perform a delete operation for this case is to just decrement the
wordCount by 1 at the ending node of the word.
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
2. The deleted word shares a common prefix with other words in Trie.
➢ As shown in the following figure, the deleted word “and” has some common prefixes
with other words ‘ant’. They share the prefix ‘an’.
➢ The solution for this case is to delete all the nodes starting from the end of the prefix
to the last character of the given word.
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
3. The deleted word does not share any common prefix with other words in Trie
As shown in the following figure, the word “geek” does not share any common prefix with
any other words.
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
import java.util.*;
class Trie
{
static final int NUM_CHARS = 26;
// To handle prefix deletion
static boolean isDeleted = false;
// trie node
static class TrieNode
{
TrieNode[] children = new TrieNode[NUM_CHARS];
// isEndOfWord is true if the node represents end of a word
boolean isEndOfWord;
TrieNode()
{
isEndOfWord = false;
for (int i = 0; i < NUM_CHARS; i++)
children[i] = null;
}
};
static TrieNode root;
// If not present, inserts key into trie
// If the key is prefix of trie node, just marks leaf node
static void insert(String key)
{
int level;
int length = key.length();
int index;
TrieNode currentNode = root;
for (level = 0; level < length; level++)
{
index = key.charAt(level) - 'a';
if (currentNode.children[index] == null)
currentNode.children[index] = new TrieNode();
currentNode = currentNode.children[index];
}
// mark last node as leaf
currentNode.isEndOfWord = true;
}
// Returns true if key (prefix or complete word) is present in trie, else false
static boolean search(String key)
{
int level;
int length = key.length();
int index;
TrieNode currentNode = root;
for (level = 0; level < length; level++)
{
index = key.charAt(level) - 'a';
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
if (currentNode.children[index] == null)
return false;
currentNode = currentNode.children[index];
}
// To check if prefix exists in the Trie
// return true;
// To check for complete word
return (currentNode.isEndOfWord);
}
// Returns true if root has no children, else false
static boolean isEmpty(TrieNode root)
{
for (int i = 0; i < NUM_CHARS; i++)
if (root.children[i] != null)
return false;
return true;
}
// Recursive function to delete a key from given Trie
static TrieNode delete(TrieNode root, String key, int depth)
{
// If tree is empty
if (root == null)
return null;
// If last character of key is being processed
if (depth == key.length())
{
// isDeleted is true if it is end of word and false otherwise
isDeleted = root.isEndOfWord;
// This node is no more end of word after removal of given key
if (root.isEndOfWord)
root.isEndOfWord = false;
// If given is not prefix of any other word
if (isEmpty(root))
{
return null;
}
return root;
}
// If not last character, recur for the child obtained using ASCII value
int index = key.charAt(depth) - 'a';
if (root.children[index] == null)
return null;
root.children[index] = delete(root.children[index], key, depth + 1);
/* If root does not have any child (its only child got deleted), and it is not end
of another word.*/
if (isEmpty(root) && root.isEndOfWord == false)
{
return null;
}
return root;
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
}
// To check if current node is leaf node or not
static boolean isLeafNode(TrieNode root)
{
return root.isEndOfWord == true;
}
// print Trie
static void print(TrieNode root, char[] str, int level)
{
// If node is leaf node, it indicates end of string,
// so, a null character is added and string is printed
if (isLeafNode(root))
{
for (int k = level; k < str.length; k++)
str[k] = 0;
System.out.println(str);
}
int i;
for (i = 0; i < NUM_CHARS; i++)
{
// if NON-NULL child is found add parent key to str and
// call the print function recursively for child node
if (root.children[i] != null)
{
str[level] = (char) (i + 'a');
print(root.children[i], str, level + 1);
}
}
}
public static void main(String args[])
{
Scanner sc=new Scanner(System.in);
String keys[]=sc.nextLine().split(" ");
root = new TrieNode();
// Construct trie
int i;
for (i = 0; i < keys.length ; i++)
insert(keys[i]);
char[] str = new char[50];
String word;
label1: while(true)
{
int opt = sc.nextInt();
sc.nextLine();
switch(opt){
case 4:
System.out.println("Content of Trie: ");
print(root, str, 0);
break;
case 1:
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
String s = sc.nextLine();
insert(s);
System.out.println("Content of Trie: ");
print(root, str, 0);
break;
case 2:
word = sc.next();
if(search(word) == true)
System.out.println(word + " is present ");
else System.out.println(word + " is not present");
break;
case 3:
word = sc.next();
if(delete(root, word, 0) != null & isDeleted == true)
System.out.println(word + " is deleted ");
else
System.out.println(word + " is not present in Trie to be deleted");
System.out.println("Content of Trie after deletion: ");
print(root, str, 0);
break;
case 5:
break label1;
}
}
}
}
ghi
mno
pqr
3 //input
abc //input
abc is deleted
Content of Trie after deletion:
def
ghi
mno
pqr
5
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
Applications
1. Index Pairs of a String
2. Longest word with all prefixes
3. Top K frequent words.
Java Program for Index Pairs of a String using Trie DS: IndexPairs.java
import java.util.*;
class Solution
{
public int[][] indexPairs(String text, String[] words)
{
/*initializing tire and put all word from words into Trie.*/
Trie trie=new Trie();
for(String s:words)
{
Trie cur=trie;
for(char c:s.toCharArray())
{
if(cur.children[c-'a']==null)
{
cur.children[c-'a']=new Trie();
}
cur=cur.children[c-'a'];
}
cur.end=true; /*mark there is a word*/
}
while(cur.children[cc-'a']!=null)
{
cur=cur.children[cc-'a'];
if(cur.end)
{ /*there is a word ending here, put into our list*/
list.add(new int[]{i,j});
}
j++;
if(j==len)
{ /*reach the end of the text, we stop*/
break;
}
else
{
cc=text.charAt(j);
}
}
}
/*put all the pairs from list into array*/
int size=list.size();
int[][] res=new int[size][2];
int i=0;
for(int[] r:list)
{
res[i]=r;
i++;
}
return res;
}
}
class Trie
{
Trie[] children;
boolean end; /*indicate whether there is a word*/
public Trie()
{
end=false;
children=new Trie[26];
}
}
class IndexPairs
{
public static void main(String args[])
{
Scanner sc=new Scanner(System.in);
String org=sc.nextLine();
String arr[]=sc.nextLine().split(" ");
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
Sample Input-1:
---------------
thekmecandngitcolleges
kmec ngit colleges
Sample Output-1:
----------------
36
10 13
14 21
Sample Input-2:
---------------
xyxyx
xyx xy
Sample Output-2:
----------------
01
02
23
24
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
Example 1:
Input: words = [“k”,”ki”,”kir”,”kira”, “kiran”]
Output: “kiran”
Explanation: “kiran” has prefixes “kira”, “kir”, “ki”, and “k”, and all of them appear in
words.
Example 2:
Input: words = [“a”, “banana”, “app”, “appl”, “ap”, “apply”, “apple”]
Output: “apple”
Explanation: Both “apple” and “apply” have all their prefixes in words. However, “apple” is
lexicographically smaller, so we return that.
Example 3:
Input: words = [“abc”, “bc”, “ab”, “qwe”]
Output: “ ”
Solution:
Use a trie to store all the words. Then do depth first search on the trie. Search the nodes from
left to right to make sure that the leftmost nodes (with lexicographically smallest letters) are
visited first. For the words that all nodes are ends of words, update the longest word. Finally,
return the longest word.
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
Java Program for Longest word with all prefixes using Trie DS: LongestWord.java
import java.util.*;
class trieHelper
{
Trie root = new Trie();
String res = "";
public String longestWord(String[] words)
{
for (String word : words)
addWord(word);
for (String word : words)
searchPrefix(word);
return res;
}
private void searchPrefix(String word)
{
System.out.println("start res " + res + " word " + word);
Trie cur = root;
for (char c : word.toCharArray())
{
cur = cur.children[c-'a'];
if (!cur.isWord) return;
}
if(res.equals(""))
{
res = word;
return;
}
System.out.println("res " + res + " word " + word);
if (res.length() < word.length() || res.length() == word.length() &&
res.compareTo(word) > 0)
res = word;
}
private void addWord(String word)
{
Trie cur = root;
for(char c : word.toCharArray())
{
if (cur.children[c-'a'] == null)
cur.children[c-'a'] = new Trie();
cur = cur.children[c-'a'];
}
cur.isWord = true;
}
}
class Trie
{
Trie[] children = new Trie[26];
boolean isWord;
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
class LongestWord
{
public static void main(String args[])
{
Scanner sc=new Scanner(System.in);
String arr[]=sc.nextLine().split(" ");
System.out.println(new trieHelper().longestWord(arr));
}
}
Sample Testcase =3
input = abc bc ab abcd
output = ""
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
Example-1:
Input:
words = ["i","love","writing","i","love","coding"], k = 2
Output:
["i","love"]
Explanation: "i" and "love" are the two most frequent words.
Example-2:
Input:
words = ["it","is","raining","it","it","raining","it","is","is"], k = 3
Output:
["it","is","raining"]
Approach:
In this method, we will use Trie data structure and bucket sort
Trie data structure is like a tree which is used to store strings. It is also known as the digital
tree or prefix tree. There can be maximum of 26 children in each node of the trie and the root
node is always the null node.
Sorting is used for sorting elements by arranging them into multiple groups.
Algorithm:
➢ In this question the least frequency of any word can be greater than or equal to 1 and
the maximum frequency is less than or equal to the length of the given vector.
➢ Then for each group we will define a trie to store the words of the same frequency.
➢ We don't have to sort the words inside each group as the words will be arranged in the
alphabetical order inside trie.
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
class Trie
{
public Node root;
public Trie()
{
this.root = new Node('\0');
}
public void insert(String word)
{
Node curr = root;
for(int i = 0;i<word.length();i++)
{
char c = word.charAt(i);
if(curr.children[c-'a'] == null)
curr.children[c-'a'] = new Node(c);
curr = curr.children[c-'a'];
}
curr.isWord = true;
curr.count +=1;
curr.str = word;
}
public void traverse(Node root, PriorityQueue<Node> pq)
{
if(root.isWord == true)
pq.offer(root);
for(int i = 0;i<26;i++)
if(root.children[i]!=null)
traverse(root.children[i], pq);
}
}
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
for(int i = 0;i<words.length;i++)
{
t.insert(words[i]);
}
t.traverse(t.root, pq);
System.out.println(res);
}
}
case =2
input =ball,are,case,doll,egg,case,doll,egg,are,are,egg,case,are,egg,are
3
output =[are, egg, case]
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
Suffix tree
A suffix tree is a data structure that is used to store all the suffixes of a given string in
an efficient manner. It can be used to solve a wide range of string problems in linear time
complexity, making it a powerful tool in competitive programming.
A suffix tree represents all the suffixes of a string in a tree-like data structure. The root
of the tree represents the empty string, and each leaf of the tree represents a suffix of the
string. Each internal node of the tree represents a common substring of two or more suffixes.
To build a suffix tree, we can start by creating a root node and adding edges to
represent each character in the input string. Then, we add additional edges to represent all
possible suffixes of the string, splitting the edges wherever there is a branching in the
suffixes. This process continues until all suffixes have been added.
Once the suffix tree has been built, we can use it to search for substrings in the
original string. We can start at the root of the tree and traverse the edges based on the
characters in the substring we are searching for. If we can successfully traverse all the
characters in the substring, we have found a match.
Suffix trees are very useful for solving a variety of string-related problems, such as
finding the longest repeated substring in a string or finding all occurrences of a pattern in a
text. They are also used in bioinformatics to analyze DNA sequences.
Here are some important concepts and operations/applications related to suffix trees:
Construction: A suffix tree can be constructed from a given string using various algorithms
such as Ukkonen's algorithm. These algorithms use a concept called implicit suffix tree,
where the tree is constructed as suffixes are added one by one.
Searching: Once a suffix tree is constructed, it can be used to search for a given pattern in the
string. This is done by traversing the tree based on the characters in the pattern. If the pattern
is found, the tree can be used to find all the occurrences of the pattern in the string.
Longest common substring: A suffix tree can be used to find the longest common substring
between two strings. This is done by first constructing a suffix tree for both strings and then
finding the deepest node in the tree that has children from both strings.
Longest repeated substring: A suffix tree can also be used to find the longest repeated
substring in a given string. This is done by finding the deepest node in the tree that has more
than one leaf node.
Counting substrings: A suffix tree can be used to count the number of occurrences of all
substrings of a given string in linear time complexity. This is done by counting the number of
leaf nodes in the subtree rooted at each internal node.
These are some of the important concepts and operations related to suffix trees in competitive
programming.
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
import java.util.*;
class Suffix
{
static class SuffixTrieNode
{
static final int MAX_CHAR = 26;
SuffixTrieNode[] children = new SuffixTrieNode[MAX_CHAR];
SuffixTrieNode()
{
for (int i = 0; i < MAX_CHAR; i++)
children[i] = null;
}
void insertSuffix(String s)
{
if (s.length() > 0)
{
char cIndex = (char) (s.charAt(0) - 'a');
if (children[cIndex] == null)
{
System.out.println("children null");
children[cIndex] = new SuffixTrieNode();
}
children[cIndex].insertSuffix(s.substring(1));
}
}
}
static class Suffix_trie
{
static final int MAX_CHAR = 26;
SuffixTrieNode root;
Suffix_trie(String s)
{
root = new SuffixTrieNode();
for (int i = 0; i < s.length(); i++)
root.insertSuffix(s.substring(i));
}
int _countNodesInTrie(SuffixTrieNode node)
{
if (node == null)
return 0;
int count = 0;
for (int i = 0; i < MAX_CHAR; i++)
{
if (node.children[i] != null)
count += _countNodesInTrie(node.children[i]);
}
return (1 + count);
}
COMPETITIVE PROGRAMMING UNIT-III III –II SEM(KR20 Regulations)
int countNodesInTrie()
{
return _countNodesInTrie(root);
}