Search code examples
javalistcollectionspascals-triangle

List may not be initialized for Pascal's triangle, but works when order is changed. Why?


While looking up solutions for the Pascal's triangle problem which takes in the number of rows as input (int numRows), and outputs a List containing all rows of the triangle, I tried this code:-

class Solution {
    public List<List<Integer>> generate(int numRows) {
        List<List<Integer>> rows = new ArrayList<List<Integer>>();
        List<Integer> pre, row = null;
        
        for(int i=0; i<numRows; ++i){
            row = new ArrayList<Integer>();
            for(int j=0; j<=i; ++j){
                if(j==0 || j==i)
                    row.add(1);
                else
                    row.add(pre.get(j-1)+pre.get(j));
            }
            pre=row;
            rows.add(row);
        }
        
        return rows;
    }
}

This gave me the error:-

Line 12: error: variable pre might not have been initialized
                    row.add(pre.get(j-1)+pre.get(j));
                            ^

However, by changing the fourth line to:- List<Integer> row, pre = null;

The code worked just fine. What's the reason behind this?


Solution

  • A variable initializer only initializes the variable before it, regardless of how many variables the declaration statement creates. So

    List<Integer> pre, row = null;
    

    only initializes row with null whereas

    List<Integer> row, pre = null;
    

    only initializes pre with null which would be the same as

    List<Integer> pre = null, row;
    

    which demonstrates that is not an issue of the variable order.


    The first statement of your loop body is row = new ArrayList<Integer>();

    Therefore, row does not need to be initialized before the loop. The loop body will not be executed if numRows is zero or less, but this is not a problem, because row is not accessed after the loop. All accesses to row occur after the variable has been initialized.

    In fact, you could move the declaration of row into the loop at the place where you initialize it.

    In contrast, the pre.get(j-1) occurs before the first assignment, i.e. pre=row;, therefore, the compiler considers this an access to a potentially uninitialized variable for the first loop iteration. An experienced reader may recognize that this line will never be executed in the first loop iteration and the pre variable is initialized in subsequent iterations. But the compiler does not analyze the conditions to that depth.

    So you need to initialize pre as with List<Integer> row, pre = null; even if you know that this initial value will never be used.

    An alternative is to move the logic of the first iteration before the loop, e.g.

    public List<List<Integer>> generate(int numRows) {
        if(numRows <= 0) return Collections.emptyList();
    
        List<List<Integer>> rows = new ArrayList<>();
        List<Integer> pre = Collections.singletonList(1);
        rows.add(pre);
    
        for(int i = 1; i < numRows; ++i) {
            List<Integer> row = new ArrayList<>();
            for(int j = 0; j <= i; ++j) {
                if(j == 0 || j == i)
                    row.add(1);
                else
                    row.add(pre.get(j - 1) + pre.get(j));
            }
            pre = row;
            rows.add(row);
        }
    
        return rows;
    }
    

    The adds the invariant first row to the rows before the loop and starts the loop with the second row (index one). Note that this requires handling the empty case at the beginning. But the advantage is that this code doesn’t need to initialize a variable with an unused value.