Sunday, September 19, 2010

Traverse binary tree in level order by spiral

Another puzzle is at stake, folks. This time it is binary tree related (not necessarily binary search tree as we are not interested in data relations but rather in binary tree structure). We need to traverse binary tree level by level (level is defined as set of all nodes at the same distance from root) in such a way that traversal direction within level changes from level to level thus forming a spiral. For example, consider binary tree below (it is rotated 90 degrees counter clockwise). Asterisk means NIL node.

                *
            20
                *
         19
                   *
               18
                   *
            17
                   *
               13
                   *
      12
          *
   11
       *
10
             *
          7
             *
       6
             *
          5
             *
    4
          *
       2
          *

Desired traversal for this tree will be: 10, 4, 11, 12, 6, 2, 5, 7, 19, 20, 17, 13, 18. Dissected into levels it looks like:

  1. 10
  2. 4, 11
  3. 12, 6, 2
  4. 5, 7, 19
  5. 20, 17
  6. 13, 18

It may not be obvious how to approach the problem. We’ll start with a well know problem of traversing binary tree level by level (also known as breadth first traversal). It can be done using queue.

static IEnumerable<T> LevelOrderLeftToRight<T>(TreeNode<T> root)
{
	if (root == null)
		yield break;

	var next = new Queue<TreeNode<T>>();
	next.Enqueue(root);

	while(next.Count > 0)
	{
		var node = next.Dequeue();
		yield return node.Data;

		EnqueueIfNotNull(next, node.Left);
		EnqueueIfNotNull(next, node.Right);
	}
}

static void EnqueueIfNotNull<T>(Queue<T> queue, T value)
	where T:class
{
	if (value != null)
		queue.Enqueue(value);
}

The queue contains yet to be examined nodes in level by level left to right (as we first enqueue left child and then right) order. At each step node at the front is dequeued, examined and its children are enqueued for further examination thus preserving level by level left to right order. It yields the following traversal: 10, 4, 11, 2, 6, 12, 5, 7, 19, 17, 20, 13, 18. Let’s split node sequence into levels:

  1. 10
  2. 4, 11
  3. 2, 6, 12
  4. 5, 7, 19
  5. 17, 20
  6. 13, 18

That looks close to sought-for order except every odd numbered level has must be reversed. With all nodes in the same container (queue) it is hard to do this. Let’s change code a little bit to make every two adjacent layers are separated into different containers.

static IEnumerable<T> LevelOrderLeftToRight<T>(TreeNode<T> root)
{
	if (root == null)
		yield break;

	var curr = new Queue<TreeNode<T>>();
	var next = new Queue<TreeNode<T>>();
	next.Enqueue(root);

	do
	{
		// Swap level containers
		Swap(ref curr, ref next);
		// Examine all nodes at current level
		while (curr.Count > 0)
		{
			var node = curr.Dequeue();
			yield return node.Data;
			// Fill next level preserving order
			EnqueueIfNotNull(next, node.Left);
			EnqueueIfNotNull(next, node.Right);
		}
		// Continue until next level has nodes
	} while (next.Count > 0);
}

static void Swap<T>(ref T a, ref T b)
{
	var tmp = a;
	a = b;
	b = tmp;
}

With adjacent levels separated we can change order within levels. Next level is a set of child nodes from current level. FIFO container (queue) makes sure that child nodes of earlier examined node will also be examined earlier. But this is opposite to what we are looking for. Change it to LIFO container (stack)! And that’s it. Child nodes of earlier examined node will be examined later.

static IEnumerable<T> LevelOrderBySpiral<T>(TreeNode<T> root)
	where T : IComparable<T>
{
	if (root == null)
		yield break;

	var curr = new Stack<TreeNode<T>>();
	var next = new Stack<TreeNode<T>>();
	// Specifies direction for the next level
	var leftToRight = true;
	next.Push(root);

	do
	{
		Swap(ref curr, ref next);
		while (curr.Count > 0)
		{
			var node = curr.Pop();
			yield return node.Data;
			// If next level must be traversed from left to right
			// we must first push right child node and then left
			// and in opposite order if next level will be 
			// traversed from right to left
			PushIfNotNull(next, leftToRight ? node.Right : node.Left);
			PushIfNotNull(next, leftToRight ? node.Left : node.Right);
		}
		// Change direction within level
		leftToRight = !leftToRight;
	} while (next.Count > 0);
}

static void PushIfNotNull<T>(Stack<T> stack, T value)
	where T : class
{
	if (value != null)
		stack.Push(value);
}

The code yields sought-for order.

Minimum window that contains all characters

Like most of life's problems, this one can be solved with bending

- Bender B.Rodrigues

Let’s bend another problem. Given set of characters P and string T find minimum window in T that contains all characters in P. Applicable solution is restricted to O(length(T)) time complexity. For example, given a string T “of characters and as” and set of characters T in a form of a string “aa s” the minimum window will be “and as”.

The problem can be broken into two parts:

  • How to select window?
  • How to check that selected window contains all characters from P?

Selecting every possible window (all unique pairs (i, j) where 0 <= i <= j < length(T)) will lead to solution worse than O(length(T)^2) because you still need to check if all characters from P are within selected window. Instead we will check every possible window ending position. Thus there are at least length(T) windows to consider.

Any feasible window has length equal to or greater than length(P). Performing recheck for any considered window will result in a solution no better than O(length(T)*length(P)). Instead we need to use check results from previous iteration.

Now we need to make sure that checking if a particular character is in P is done in an optimal way. Taking into account that a particular character may appear more than once and window thus must contain appropriate number of characters. We will use hash table to map unique characters from P to their count for fast lookup.

And now let’s tie all things together.

  • Until reached the end of T move by one current window ending position.
  • Append next character to the end of previous window which to this moment doesn’t contain all necessary characters. Char to count map is used to track the number of characters left to find. Basically if character is in P count is decremented. The number may become negative meaning that there are more than required characters.
  • If unmatched character count goes to zero the window contains all required characters. However there may be redundant characters. Thus we try to compact current window. It is ok to do this as we are looking for minimum window and any window that is extended from this one won’t be better.
  • Once window is compacted compare it with the minimum one and updated it if needed.
  • If current window contains all the characters remove from it the first one simply by moving by one starting position to make sure that at each iteration previous window doesn’t contain all the characters (there is no point in appending new characters to a window that already contains all of the required ones).

Code the thing! =)

static string FindMinWindow(string t, string p)
{
 // Create char to count mapping for fast lookup
 // as some characters may appear more than once
 var charToCount = new Dictionary<char, int>();
 foreach (var c in p)
 {
  if (!charToCount.ContainsKey(c))
   charToCount.Add(c, 0);
  charToCount[c]++;
 }

 var unmatchesCount = p.Length;
 int minWindowLength = t.Length + 1, minWindowStart = -1;
 int currWindowStart = 0, currWindowEnd = 0;
 for (; currWindowEnd < t.Length; currWindowEnd++)
 {
  var c = t[currWindowEnd];
  // Skip chars that are not in P
  if (!charToCount.ContainsKey(c))
   continue;
  // Reduce unmatched characters count
  charToCount[c]--;
  if (charToCount[c] >= 0)
   // But do this only while count is positive
   // as count may go negative which means 
   // that there are more than required characters
   unmatchesCount--;

  // No complete match, so continue searching
  if (unmatchesCount > 0)
   continue;

  // Decrease window as much as possible by removing 
  // either chars that are not in T or those that 
  // are in T but there are too many of them
  c = t[currWindowStart];
  var contains = charToCount.ContainsKey(c);
  while (!contains || charToCount[c] < 0)
  {
   if (contains)
    // Return character to P
    charToCount[c]++;

   c = t[++currWindowStart];
   contains = charToCount.ContainsKey(c);
  }

  if (minWindowLength > currWindowEnd - currWindowStart + 1)
  {
   minWindowLength = currWindowEnd - currWindowStart + 1;
   minWindowStart = currWindowStart;
  }

  // Remove last char from window - it is definitely in a 
  // window because we stopped at it during decrease phase
  charToCount[c]++;
  unmatchesCount++;
  currWindowStart++;
 }

 return minWindowStart > -1 ?
        t.Substring(minWindowStart, minWindowLength) :
        String.Empty;
}

Every character is examined at most twice (during appending to the end and during compaction) so the whole solution has O(length(T)) time complexity assuming hash table lookup is O(1) operation.