The (.*)(?:table)?
fails with table
(matches it) as the first group (.*)
is a greedy dot matching pattern that grabs the whole string into Group 1. The regex engine backtracks and looks for table
in the optional non-capturing group, and matches an empty string at the end of the string.

The regex trick is to match any text that does not start with table
before the optional group:
^((?:(?!table).)+)(?:table)?$
See the regex demo
Now, Group 1 - ((?:(?!table).)+)
- contains a tempered greedy token (?:(?!table).)+
that matches 1 or more chars other than a newline that do not start a table
sequence. Thus, the first group will never match table
.
The anchors make the regex match the whole line.
NOTE: Non-regex solutions might turn out more efficient though, as a tempered greedy token is rather resource consuming.
NOTE2: Unrolling the tempered greedy token usually enhances performance n times:
^([^t]*(?:t(?!able)[^t]*)*)(?:table)?$
See another demo
But usually it looks "cryptic", "unreadable", and "unmaintainable".