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?
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.