I would like to start by thanking the poster for his question, and presenting a practical example of how time complexity and space complexity oppose one another. Given a large amount of space, your algorithm can be extremely fast (because you can create 'state'). Conversely, if you are very restricted in space, you will find it necessary to perform extra iterations to compensate for lack of state.
It turns out that this problem is easy to solve in space complexity O(n). If you are permitted to create space for the result (O(n)), there are several algorithms that can create a solution in time complexity O(n). One solution is posted (which I like), and I also provide a different, and perhaps elegant approach to also solve in O(n) and in time complexity O(n); refer to method solveOrderN().
The challenge is how to find a solution in space complexity less than O(n). To do this, you must not be permitted to create a result space, and you are forced as a result to operate within the tree space given in the question. You are forced to swap elements around.
The solution I have provided – solveSubOrderN() - does not create any result space; the answer is returned in the same memory space as the question.
I was very optimistic I could solve this problem in O(log base 2(n)) and even in time complexity close to O(n). But after much analysis, I can not get this aggressive.
When you begin to swap elements, you will eventually hit a 'halting condition', when you circle back to a non-processable element. If this halt did not exist, you could achieve O(log base 2 n). But you can't avoid it. So to compensate for this, I was forced to create some space (a boolean array) which represents the state of an element being processed.
I would like to discuss how this boolean state is much different than creating space for a result/solution. When you create for a solution (of n elements, of size s), you are creating space=n*s. In this question, s is an Integer. In general s can be very large, which makes it an 'expensive'' process (and the reason the poster created this problem). The space for an array of boolean is considerably less, and even arguably negligible as s grows in size (i.e. n/s approaches 0 as s grows).
It also turns out that you can not achieve time complexity O(n) when space complexity is less than O(n). You are forced to iterate again when you hit a halting condition. But it turns out that for a binary tree, the amount of extra iterations are small (and each iteration is to simply find the next start point).
So in summary, please find below two solution s; One has space complexity O(n) and time complexity O(n), but more importantly one which has a space complexity less than O(n), with a time complexity very close to O(n).
public class BinaryTree {
public static void main(String[] args) {
int treeN2[] = {1, 2, 3};
int treeN3[] = {1, 2, 3, 4, 5, 6, 7};
int treeN4[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
int treeN5[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
int answer[] = solveOrderN(treeN5);
System.out.println(Arrays.toString(answer));
solveSubOrderN(treeN5);
System.out.println(Arrays.toString(treeN5));
}
/**
* Given a binary tree, Perform inward spiral tree traversal.<br>
* With this approach, there is no space created for the result.
* All manipulation is done within the original tree<br>
* Some space is created (boolean[n]) to add necessary state of processing.
* Space Complexity: Less than O(n), greater than log(base2)(n)
* Time Complexity: Slightly greater than O(n)
* @param tree Input tree
*/
public static void solveSubOrderN(int tree[]) {
boolean complete[] = new boolean[tree.length];
Arrays.fill(complete, false);
System.out.println("Solving Sub O(n); tree size="+tree.length);
int n = log2Round(tree.length+1);
if (n == 1)
return;
int o[] = getLevelOffsets(n);
System.out.println("Number of levels="+n);
int pos = 0;
complete[0] = true;
int currentValue = 0;
int moves=1;
while (moves < tree.length) {
pos = getStartingPos(complete);
currentValue = tree[pos];
tree[pos] = 0;
while (true) {
int nextPos = getTargetPosition(pos, o, n);
int nextValue = tree[nextPos];
tree[nextPos] = currentValue;
complete[nextPos] = true;
currentValue = nextValue;
pos = nextPos;
moves++;
if (currentValue == 0)
break;
}
}
}
/**
* Given a binary tree, Perform inward spiral tree traversal.
* Space Complexity: O(n)
* Time Complexity: O(n)
* @param tree Input tree
* @return The solution
*/
public static int[] solveOrderN(int tree[]) {
int answer[] = new int[tree.length];
int n = log2Round(tree.length+1);
int o[] = getLevelOffsets(n);
System.out.println("Solving O(n); tree size="+tree.length);
System.out.println("Number of levels="+n);
for (int i = 0; i < tree.length; i++) {
answer[getTargetPosition(i, o, n)] = tree[i];
}
return answer;
}
/**
* Search for the first unprocessed element
* @param complete An array of boolean (true = processed)
* @return
*/
public static int getStartingPos(boolean[] complete) {
for (int i=0; i<complete.length; i++) {
if (!complete[i])
return i;
}
return 1;
}
public static int getTargetPosition(int pos, int o[], int n) {
int row = getRow(pos);
int rowOrder = getRowOrder(row, n);
boolean isReversed = isBottom(row, n);
int posInRow = getPosInRow(pos, n);
int rowOffset = getRowOffset(rowSize(row), posInRow, isReversed);
return o[rowOrder]+rowOffset;
}
public static int getRowOffset(int rowSize, int posInRow, boolean isReversed) {
if (!isReversed)
return posInRow;
else
return rowSize-posInRow-1;
}
public static int rowSize(int row) {
return exp(row, 2);
}
public static int getPosInRow(int pos, int n) {
int row = getRow(pos);
return pos-(exp(row,2)-1);
}
/**
* The top n/2 rows print forward, the bottom n/2 rows print reversed
* @param row Zero based row [0 to n-1]
* @param n Number of levels to the tree
* @return true if line should be printed forward, false if reversed
*/
public static boolean isBottom(int row, int n) {
int halfRounded = n/2;
return (row <= n-halfRounded-1) ? false : true;
}
public static int exp(int n, int pow) {
return (int)Math.pow(pow, n);
}
public static double log2(int n) {
return (Math.log(n) / Math.log(2));
}
public static int log2Round(int n) {
return (int)log2(n);
}
/**
* For a given position [0 to N-1], find the level on the binary tree [0 to n-1]
* @param pos Zero based position in the tree (0 to N-1)
* @return Zero based level (0 to n-1)
*/
public static int getRow(int pos) {
return log2Round(pos+1);
}
/**
* For a given row [0 to n-1], find the order in which that line would be processed [1 to log base 2 n]
* @param row The row in the tree [0 to n-1]
* @return The order that row would be processed [0 to n-1]
*/
public static int getRowOrder(int row, int n) {
return isBottom(row, n) ? (n-row-1)*2+1 : row*2;
}
public static int getRowForOffset(int row, int n) {
boolean isOdd = row%2 == 1;
return isOdd ? n-(row-1)/2 - 1 : row/2;
}
/**
* Compute the offset for a given ordered row
* @param n The number of levels in the tree
* @return Generated offsets for each row (to keep time complexity at O(n))
*/
public static int[] getLevelOffsets(int n) {
int o[] = new int[n];
Arrays.fill(o, 0);
o[0] = 0;
int offset = 0;
for (int i=0; i<n; i++) {
int nextRow = getRowForOffset(i, n);
o[i] = offset;
offset += exp(nextRow, 2);
}
return o;
}
}