Problem statement:
Given a string as a "source" string, find the smallest substring of source such that it contains all characters in "search" string (which contains distinct characters).
For example, for search string ['a','b','c'], source string "aefbcgaxy", the shortest string is "bcga".
My algorithm
In this algorithm, I practiced two-pointer techniques and also used a sliding window to reduce the time complexity to almost linear complexity by overloading the count value of unique characters.
For example, search string ['a','b','c'] can map to a C# dictionary with 'a' as key with value 1, 'b' with 1, 'c' with 1. The value of key 'a' can also be used to track how many 'a' are consumed when iterating the search string from left to right. Key 'a' with value 0 means that no 'a' is needed in sliding window, -1 means that the substring in sliding window has extra one 'a'.
My mistakes
I have practiced string search algorithm since January 2015. But I am still not good at thinking about edge cases. I made a few mistakes in the first few practices, and finally I came out the above test case to cover all edge cases.
I still make those common mistakes. Visit the hashmap without checking the key is existing in the dictionary first. The second one is to consider two cases, either in the dictionary or not in the dictionary. The third one is to maintain the dictionary value by adding one or decreasing one.
TED principle
I am still learning how to write an efficient solution in terms of time complexity, and to apply the principle I learned a few months ago called TED Principle (Terse, Express the intent, Do one thing) through pluralsight.com course clean code: write code for humans. Please help me review my code and help me to write clean code.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace substring_practice
{
class Program
{
static void Main(string[] args)
{
var testResult = GetShortestUniqueSubstring(new char[] { 'a', 'b', 'c' }, "aefbcgaxy");
Debug.Assert(testResult.CompareTo("bcga") == 0);
}
public static string GetShortestUniqueSubstring(char[] search, string source)
{
if (search == null || search.Length == 0)
{
return "";
}
// assume that search string is not empty
if (source == null || source.Length == 0)
{
return "";
}
// put unique chars in search string to the dictionary
var map = new Dictionary<char, int>();
foreach (var item in search)
{
map.Add(item, 1);
}
var needChars = search.Length; // 'xyz' - 3, var match = needChars == 0
// iterate the string and find match, and also keep track of minimum
var left = 0;
var length = source.Length;
var smallestLength = length + 1;
var smallestSubstring = "";
for (int index = 0; index < length; index++)
{
var visit = source[index];
var inMap = map.ContainsKey(visit);
var needOne = inMap && map[visit] > 0;
if (inMap)
{
map[visit]--;
}
if (needOne)
{
needChars--;
}
var findMatch = needChars == 0;
if (!findMatch)
{
continue;
}
// move left point forward - while loop
while (left <= index && (!map.ContainsKey(source[left]) || (map.ContainsKey(source[left]) && map[source[left]] < 0)))
{
var removeChar = source[left];
// update the variable needChars
if (map.ContainsKey(source[left]))
{
map[removeChar]++;
}
left++;
}
var currentLength = index - left + 1;
var findSmallerOne = currentLength < smallestLength;
if (findSmallerOne)
{
smallestLength = currentLength;
smallestSubstring = source.Substring(left, currentLength);
needChars++;
map[source[left]]++;
left = left + 1;
}
}
// edge case
if (smallestLength == length + 1)
{
return "";
}
else
{
return smallestSubstring;
}
}
}
}