I would first like to thank the poster for its question and provide a practical example of how the complexity of time and spatial complexity contrast each other. Given the large amount of space, your algorithm can be extremely fast (because you can create a “state”). Conversely, if you are very limited in space, you will need to perform additional iterations to compensate for the lack of state.
It turns out that this problem is easily solved in the spatial complexity of O (n). If you are allowed to create space for the result (O (n)), there are several algorithms that can create a solution in the time complexity of O (n). One of them is published (which I like), and I also propose another, and possibly elegant, approach to solving in O (n) and in the time complexity of O (n); refer to solveOrderN () method.
The problem is how to find a solution in space complexity less than O (n). To do this, you should not be allowed to create a result space, and you are forced to work as a result within the tree space asked in the question. You are forced to change elements around.
The solution I 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 a lot of analysis, I can't get it aggressive.
When you start replacing elements, you will eventually click the “stop condition” when you return to the non-recyclable element. If this stop does not exist, you can reach O (base base 2 n). But you cannot avoid it. Therefore, in order to compensate for this, I was forced to create some space (a Boolean array) that represents the state of the element being processed.
I would like to discuss how this logical state is very different from creating space for the result / solution. When you create a solution (of n elements, size s), you create space = n * s. In this question, s is an integer. In the general case, s can be very large, which makes it an “expensive” process (and the reason that the poster created this problem). The space for the Boolean array is much smaller and even negligible as s grows (i.e., N / s approaches 0 as s grows).
It also turns out that you cannot achieve the time complexity of O (n) when the complexity of the space is less than O (n). You are forced to repeat the iteration when you fall into a stop state. But it turns out that for a binary tree the number of additional iterations is small (and each iteration should just find the next starting point).
So, let us summarize two solutions s; One has a spatial complexity of O (n) and a time complexity of O (n), but, more importantly, one that has a spatial complexity of 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)); } 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; } } } 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; } 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); } 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); } public static int getRow(int pos) { return log2Round(pos+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; } 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; } }