Use three sets to track constraints:
1. rows[i]: digits used in row i.
2. cols[j]: digits used in column j.
3. boxes[i/3][j/3]: digits used in that 3×3 box.
For each empty cell, try digits 1-9. If a digit satisfies all constraints, place it and recurse. Backtrack if no valid digit exists.