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