Challenge: Palindromes
Subdomeniu: Probability (probability)
Scor cont: 40.0 / 40
Submission status: Accepted
Submission score: 1.0
Submission ID: 464751765
Limbaj: python3
Link challenge: https://www.hackerrank.com/challenges/palindromes/problem
Cerinta
Given a string, you keep swapping any two characters in the string randomly till the string becomes a palindrome. What is the [expected number](http://en.wikipedia.org/wiki/Expected_value) of swaps you will make? There will always be at least one palindrome which can be formed with the letters of the given string.
**Input:**
The first line contains the number of test cases T. Each of the next T lines contains a string each.
**Output:**
Output T lines containing the answer for the corresponding test case. Print the answer correct to 4 decimal places.
**Constraints:**
T <= 10000
The length of the string will be at most 8 characters.
The string will consist of only lower-case letters 'a'-'z'.
**Sample Input:**
4
b
bb
abb
cbaabbb
**Sample Output:**
0.0000
0.0000
3.0000
59.3380
**Explanation:**
For the first two cases, the string is already a palindrome so no swaps are needed.
For the third case, there are 3 possible swaps. The string will become "bab","bba" or remain "abb" with 1/3rd probability each. It's easy to see that the expected number of swaps needed is 3.0000
For the last case, the answer is 59.337962..., which should be printed as 59.3380
Cod sursa
#!/bin/python3
import os
import sys
from collections import Counter, defaultdict
def canonicalize(s: str) -> str:
mp = {}
nxt = 0
out = []
for ch in s:
if ch not in mp:
mp[ch] = chr(ord('0') + nxt)
nxt += 1
out.append(mp[ch])
return ''.join(out)
def is_palindrome(s: str) -> bool:
return s == s[::-1]
def is_non_absorbing_valid_state(s: str) -> bool:
# Non-absorbing: not already palindrome.
if is_palindrome(s):
return False
# Reachable palindrome condition for this multiset.
cnt = Counter(s)
odd = sum(v & 1 for v in cnt.values())
return odd == (len(s) & 1)
def generate_all_canonical_states(max_len: int = 8):
seen = set()
all_states = []
def dfs(cur: str):
c = canonicalize(cur)
if c in seen:
return
seen.add(c)
all_states.append(c)
if len(c) < max_len:
for ch in '0123':
dfs(c + ch)
dfs('')
return all_states
def solve_group(states):
m = len(states)
idx = {s: i for i, s in enumerate(states)}
n = len(states[0])
pairs = n * (n - 1) // 2
p = 1.0 / pairs
# Augmented matrix [A | b], where A * E = b.
mat = [[0.0] * (m + 1) for _ in range(m)]
for s, i in idx.items():
mat[i][i] = 1.0
mat[i][m] = 1.0
arr = list(s)
for a in range(n):
for b in range(a + 1, n):
arr[a], arr[b] = arr[b], arr[a]
t = ''.join(arr)
arr[a], arr[b] = arr[b], arr[a]
if is_palindrome(t):
continue
tc = canonicalize(t)
j = idx[tc]
mat[i][j] -= p
# Gaussian elimination with partial pivoting.
for col in range(m):
piv = col
for r in range(col + 1, m):
if abs(mat[r][col]) > abs(mat[piv][col]):
piv = r
mat[col], mat[piv] = mat[piv], mat[col]
pv = mat[col][col]
# Normalize pivot row.
for j in range(col, m + 1):
mat[col][j] /= pv
# Eliminate in all other rows.
for r in range(m):
if r == col:
continue
fac = mat[r][col]
if abs(fac) < 1e-15:
continue
for j in range(col, m + 1):
mat[r][j] -= fac * mat[col][j]
res = {}
for s, i in idx.items():
res[s] = mat[i][m]
return res
def precompute_expectations():
all_states = generate_all_canonical_states(8)
valid_states = [s for s in all_states if is_non_absorbing_valid_state(s)]
groups = defaultdict(list)
for s in valid_states:
sig = (len(s), tuple(sorted(Counter(s).values(), reverse=True)))
groups[sig].append(s)
ans = {}
for states in groups.values():
ans.update(solve_group(states))
return ans
def main():
expect = precompute_expectations()
data = sys.stdin.read().strip().split()
if not data:
return
t = int(data[0])
out = []
ptr = 1
for _ in range(t):
s = data[ptr]
ptr += 1
if len(s) <= 1 or is_palindrome(s):
out.append('0.0000')
else:
c = canonicalize(s)
out.append(f"{expect[c]:.4f}")
sys.stdout.write('\n'.join(out))
if __name__ == '__main__':
main()
HackerRank Probability – Palindromes
