pow() isn't just slow and bulky. In this application, it'll give you unexpected results. pow() returns a double result - identical to float in the Arduino. float values are always almost exact, but they often differ from the expected value by a very small amount. In this program, the results of pow() are sometimes just a little bit short of the theoretical values. When the results are stored in the int variable lights, they're truncated rather than rounded.
If you want to see this in action, just Serial.print(lights). To see it in action even more clearly, declare a float variable, set it to pow(2,v), and print it to too many decimal places, like 6. You'll see that lights comes up short for v = 2, 3, 4, 5, 6, and 7.
float is great for performing scientific calculations, where the inputs themselves usually aren't known with absolute accuracy. But that's not what this program does - it generates a bit pattern using a numerical calculation as a handy way of generating that pattern. You don't need the extended range or the deep precision of float. You need the accuracy of integer arithmetic.
You have options. You could write a function to multiply 1 by 2 an arbitrary number of times, using integer arithmetic, and it would work, certainly for arguments less than 8. But, the straightforward way is to use the shift operator. Compared to pow(), it generates a whole lot less code, runs very fast, and never misses.