0

I'm practicing Java by working through algorithms on leetcode. I just solved the "Construct a binary tree from inorder and postorder traversal" problem and was playing with my code to try to get better performance (as measured by the leetcode compilation/testing). Here is the code I wrote:

class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        if(inorder.length == 1){
            TreeNode root = new TreeNode(inorder[0]);
            return root;
        }
        if(inorder.length == 0)
            return null;
        //int j = inorder.length; //Calculate this once, instead of each time the for loop executes
        return reBuild(inorder, postorder, 0, inorder.length - 1, 0, postorder.length - 1);
    }
    
    public TreeNode reBuild(int[] inorder, int[] postorder, int inStart, int inEnd, int postStart, int postEnd){ //j passed in as argument here
        if(inStart > inEnd)
            return null; //base case
        
        int rIndex = 0;
        int j = inorder.length;
        TreeNode root = new TreeNode(postorder[postEnd]); //Root is the last item in the postorder array
        if(inStart == inEnd)
            return root;  //This node has no children
        
        //for(int i = 0; i < inorder.length; ++i)
        
        for(int i = 0; i < j; ++i){ //Find the next root value in inorder and get index
            if(inorder[i] == root.val){
                rIndex = i;
                break;
            }
        }
        root.left = reBuild(inorder, postorder, inStart, rIndex - 1, postStart, postStart - inStart + rIndex - 1); //Build left subtree
        root.right = reBuild(inorder, postorder, rIndex + 1, inEnd, postEnd - inEnd + rIndex, postEnd - 1); //Build right subtree
        return root;
    }
}

My question concerns the for loop in the reBuild function. My first submission calculated the length of inorder each time the loop ran, which is obviously inefficient. I then took this out, and stored the length in a variable j, and used that in the for loop instead. This gave me a boost of ~1ms runtime. So far, so good. Then, I tried moving the calculation of j to the buildTree function, rationalizing that I don't need to calculate it in each recursive call since it doesn't change. When I moved it there and passed it in as a parameter, my runtime went back up 1ms, but my memory usage decreased significantly. Is this a quirk of how leetcode measures efficiency? If not, why would that move increase runtime?

jrbobdobbs83
  • 103
  • 1
  • 1
  • 9
  • 1ms is not a whole lot improvement imo. Run the code at least 3 times in both cases and compare the average. Also, it would make sense that your memory usage decreases if j is passed as parameter as it will be on variable less to hold in the stack. put j in the function but make it static. See what is the performance. – Yoshikage Kira May 26 '21 at 02:44
  • Took your advice and ran several times with both cases. With the int j declaration in the recursive function, runtime is consistently reported as 3ms by leetcode, and 4ms with the declaration in the buildTree function. – jrbobdobbs83 May 26 '21 at 04:03
  • Differences that small are simply not possible to reliably measure in the way Leetcode is doing it. – kaya3 May 26 '21 at 05:49
  • Does this answer your question? [How do I write a correct micro-benchmark in Java?](https://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java) – kaya3 May 26 '21 at 05:50

1 Answers1

0

If by calculating the length you mean accessing inorder.length then this is likely why you are losing performance.

When created, arrays hold onto a fixed value for their length called "length". this is a value not a method(thus no real performance used).

If j is never changed (ie j always equals inorder.length) The compiler likely ignores "j = inorder.length;" and simply accesses inorder.length when it sees j. you are then adding complexity to the function call by passing j where inorder (and thus inorder.length) is also present. Though this depends on the compiler implementation and may not actually happen.

In terms of access time, I think public object variables are slower than in-scope variables (think access inorder then access length).

warning hardware talk: Another thing to consider is registers. These are data storage locations on the CPU itself which the code is actually run from (think HDD/SSD>RAM>cache>registers) and generally cant hold much more than 100 values at a time. Thus depending on the size of the current method (number of variables in scope) the code can run much faster or slower. Java seems to add a lot of overhead to this so for small functions, 1 or 2 extra values in scope can drastically affect the speed (as the program has to access cache).

  • Ok, that makes some sense. But then shouldn't the performance have been unaffected by moving the access of inorder.length out of the for loop? – jrbobdobbs83 May 26 '21 at 04:05
  • @jrbobdobbs83 to be honest I'm not sure about the compiler part as its implementation specific. I updated the answer to add something you might find interesting. – Patrick Anderson May 30 '21 at 09:07