|
1 | 1 | class Solution: |
2 | 2 | def largestPalindrome(self, n: int, k: int) -> str: |
3 | | - # Build palindrome from outside to inside |
4 | | - # Use DP: track remainder as we build |
| 3 | + # For very large n, use optimized construction |
| 4 | + # Build palindrome digit by digit, only keeping best per remainder |
5 | 5 |
|
6 | | - def build(pos, rem, pal_list): |
7 | | - if pos >= (n + 1) // 2: |
8 | | - if rem == 0: |
9 | | - return ''.join(pal_list) |
10 | | - return None |
11 | | - |
12 | | - mirror_pos = n - 1 - pos |
13 | | - best = None |
| 6 | + # dp[rem] = tuple of digits (first half only) |
| 7 | + dp = {0: ()} |
| 8 | + |
| 9 | + half = (n + 1) // 2 |
| 10 | + |
| 11 | + for pos in range(half): |
| 12 | + new_dp = {} |
14 | 13 |
|
15 | | - for digit in range(9, -1, -1): |
16 | | - if pos == 0 and digit == 0: |
17 | | - continue |
| 14 | + for rem, digits in dp.items(): |
| 15 | + # Try digits 9 down to 0, but stop early if we find a solution |
| 16 | + found_solution = False |
18 | 17 |
|
19 | | - # Calculate remainder contribution |
20 | | - if pos == mirror_pos: |
21 | | - # Center position (odd n) |
22 | | - power = pow(10, pos, k) |
23 | | - new_rem = (rem + digit * power) % k |
24 | | - new_pal = pal_list[:] |
25 | | - new_pal[pos] = str(digit) |
26 | | - else: |
27 | | - # Symmetric positions |
28 | | - left_power = pow(10, pos, k) |
29 | | - right_power = pow(10, mirror_pos, k) |
30 | | - new_rem = (rem + digit * (left_power + right_power)) % k |
31 | | - new_pal = pal_list[:] |
32 | | - new_pal[pos] = str(digit) |
33 | | - new_pal[mirror_pos] = str(digit) |
| 18 | + for digit in range(9, -1, -1): |
| 19 | + if pos == 0 and digit == 0: |
| 20 | + continue |
| 21 | + |
| 22 | + mirror_pos = n - 1 - pos |
| 23 | + |
| 24 | + # Calculate new remainder |
| 25 | + if pos == mirror_pos: |
| 26 | + power = pow(10, pos, k) |
| 27 | + new_rem = (rem + digit * power) % k |
| 28 | + else: |
| 29 | + left_power = pow(10, pos, k) |
| 30 | + right_power = pow(10, mirror_pos, k) |
| 31 | + new_rem = (rem + digit * (left_power + right_power)) % k |
| 32 | + |
| 33 | + new_digits = digits + (digit,) |
| 34 | + |
| 35 | + # Keep best for this remainder |
| 36 | + if new_rem not in new_dp or new_digits > new_dp[new_rem]: |
| 37 | + new_dp[new_rem] = new_digits |
| 38 | + |
| 39 | + # Early check: if this is the last position and remainder is 0, we're done |
| 40 | + if pos == half - 1 and new_rem == 0: |
| 41 | + found_solution = True |
| 42 | + # Reconstruct and return immediately |
| 43 | + pal_list = [''] * n |
| 44 | + for i, d in enumerate(new_digits): |
| 45 | + pal_list[i] = str(d) |
| 46 | + if i != n - 1 - i: |
| 47 | + pal_list[n - 1 - i] = str(d) |
| 48 | + res = ''.join(pal_list) |
| 49 | + if len(res) == n and res[0] != '0': |
| 50 | + return res |
34 | 51 |
|
35 | | - result = build(pos + 1, new_rem, new_pal) |
36 | | - if result and (not best or result > best): |
37 | | - best = result |
38 | | - if best: |
39 | | - break # Found largest, no need to try smaller digits |
| 52 | + # If we found a solution, we can break early |
| 53 | + if found_solution: |
| 54 | + break |
| 55 | + |
| 56 | + dp = new_dp |
| 57 | + # Limit states: only keep best candidate per remainder |
| 58 | + # This prevents explosion of states |
40 | 59 |
|
41 | | - return best |
| 60 | + # Final reconstruction |
| 61 | + if 0 in dp: |
| 62 | + digits = dp[0] |
| 63 | + pal_list = [''] * n |
| 64 | + for i, d in enumerate(digits): |
| 65 | + pal_list[i] = str(d) |
| 66 | + if i != n - 1 - i: |
| 67 | + pal_list[n - 1 - i] = str(d) |
| 68 | + res = ''.join(pal_list) |
| 69 | + if len(res) == n and res[0] != '0': |
| 70 | + return res |
42 | 71 |
|
43 | | - pal_list = [''] * n |
44 | | - res = build(0, 0, pal_list) |
45 | | - return res if res else str(k) * n |
| 72 | + return str(k) * n |
0 commit comments