The difference is exactly as the code shows
int* p = new int[n];
allocates int, but
int** p = new int*[n];
allocates int*.
You can allocate anything (more or less). If you allocate T then what you get back is a pointer to T, i.e. T*.
But there's nothing special about pointers, they can be allocated too. So if you allocate T* (a pointer to T) then what you get back is a pointer to a pointer to T (i.e. T**).
Allocating pointers is the first step to allocating a 2D array. A 2D array is just an array of 1D arrays. The pointers are the rows in the 2D array (if you like) but the columns still need to be allocated.
int** array2d = new int*[num_rows]; // allocate the rows
for (int i = 0; i < num_rows; ++i)
     array2d[i] = new int[num_cols]; // allocate the columns for row i
After executing this code array2d is a dynamically allocated 2D array with num_rows rows and num_cols columns.