Skip to content
Snippets Groups Projects
Verified Commit b318683f authored by Dmytro Bogatov's avatar Dmytro Bogatov :two_hearts:
Browse files

Solve Find Strings.

parent fff66861
Branches
No related tags found
No related merge requests found
using System.Collections.Generic;
using System.Linq;
namespace CodingInterview.HackerRank
{
/// <summary>
/// A substring is defined as a contiguous sequence of one or more characters in the string.
/// More information on substrings can be found here (https://en.wikipedia.org/wiki/Substring).
///
/// You are given n strings w[1], w[2], ... , w[n].
/// Let S[i] denote the set of all unique substrings of the string w[i].
/// Let S = { S[1] U S[2] ... U S[n] }, that is, S is a set of strings that is the union of all substrings in all sets S[1], S[2], ... , S[n].
/// There will be many queries.
/// For each query you will be given an integer k.
/// Your task is to find the k_th element of the 1-indexed lexicographically ordered set of substrings in the set S.
/// If there is no element k, return INVALID.
///
/// For example, your strings are w = [abc, cde].
/// All of the substrings are S[1] = { a, b, c, ab, bc, abc } and S[2] = { c, d, e, cd, de, cde }.
/// Combine the two sets and sort them to get S = [ a, ab, abc, b, bc, c, cd, cde, d, de, e ].
/// So, for instance if k = 1, we return 'a'.
/// If k = 5, we return 'bc'.
/// If k = 20 though, there is not an S[20] so we return INVALID.
///
/// https://www.hackerrank.com/challenges/find-strings/problem
/// </summary>
/// <remark>
/// This solution somehow does not pass all HackerRank tests.
/// It shows "runtime error", but without tests I was not able to reproduce.
/// </remark>
public class FindStrings
{
class PrefixTree
{
class Node
{
public string Value { get; set; }
// the number of elements in the the subtree defined by this node;
// this includes this node's value
public int SubtreeSize { get; set; }
// parent link for upward traversal
public Node Parent { get; set; }
// lexicographically ordered list of children
public List<Node> Children { get; set; } = new List<Node>();
}
// the empty root
private Node _root;
public PrefixTree()
{
_root = new Node { Value = "" };
}
public string Query(int query)
{
query++; // we count empty root as 1
// we know a priori if the query results in INVALID
if (_root.SubtreeSize < query || query < 2)
{
return "INVALID";
}
// iterative traversal (somewhat easier than the recursive)
var thisNode = _root;
while (true)
{
// return the value if query asks for it
if (query == 1)
{
return thisNode.Value;
}
// traverse children
foreach (var child in thisNode.Children)
{
// decide if to skip the child go into it
if (child.SubtreeSize < query - 1)
{
query -= child.SubtreeSize;
}
else
{
thisNode = child;
query -= 1; // remember to count this value
break;
}
}
}
}
public void Add(string @new)
{
var thisNode = _root;
while (true)
{
// the value already exists (just created or not), so exit
if (thisNode.Value == @new)
{
break;
}
var i = 0; // index of the child of interest
var inserted = false; // if we decide to go inside child or not
// traverse children in order
while (i < thisNode.Children.Count)
{
// traverse while the last character is lexicographically no larger than the inserted's last character
if (thisNode.Children[i].Value.Last() <= @new[thisNode.Value.Length])
{
// if last characters are equal
if (thisNode.Children[i].Value.Last() == @new[thisNode.Value.Length])
{
// dive into child
thisNode = thisNode.Children[i];
inserted = true;
break;
}
i++;
}
else
{
break;
}
}
// this happens if we have not moved into any child
if (!inserted)
{
// i remembers where we want to insert the element
thisNode.Children.Insert(i, new Node
{
// we insert the prefix only
Value = @new.Substring(0, thisNode.Value.Length + 1),
Parent = thisNode
});
thisNode = thisNode.Children[i];
}
}
// update counts
while (thisNode != null)
{
thisNode.SubtreeSize = thisNode.Children.Sum(ch => ch.SubtreeSize) + 1;
thisNode = thisNode.Parent;
}
}
}
/// <summary>
/// It should return an array of strings - answers to queries
/// </summary>
/// <param name="w">an array of strings - inputs</param>
/// <param name="queries">an array of integers - queries</param>
/// <returns>an array of strings - answers to queries</returns>
public string[] Solve(string[] w, int[] queries)
{
// construct the trie
PrefixTree trie = new PrefixTree();
foreach (var @string in w)
{
// add the input strings
for (int i = 0; i < @string.Length; i++)
{
// for each string, add itself and all its suffixes (trie will naturally deal with prefixes)
trie.Add(@string.Substring(i, @string.Length - i));
}
}
// run the queries
var answer = new List<string>();
foreach (var query in queries)
{
answer.Add(trie.Query(query));
}
return answer.ToArray();
}
}
}
using Xunit;
namespace CodingInterview.Tests.HackerRank
{
public class FindStrings
{
[Fact]
public void TestCases()
{
// From Hacker Rank
Assert.Equal(
new string[] { "a", "bc", "INVALID" },
new CodingInterview.HackerRank.FindStrings().Solve(new string[] { "abc", "cde" }, new int[] { 1, 5, 20 }));
Assert.Equal(
new string[] { "aab", "c", "INVALID" },
new CodingInterview.HackerRank.FindStrings().Solve(new string[] { "aab", "aac" }, new int[] { 3, 8, 23 }));
// This one is my own
Assert.Equal(
new string[] { "a", "abc", "cde", "dez", "fh", "z", "INVALID" },
new CodingInterview.HackerRank.FindStrings().Solve(new string[] { "abc", "cdez", "cfe", "cfh" }, new int[] { 1, 3, 8, 15, 20, 22, 25 }));
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment