DSA Interview Prep · Oppitunti

Merkkijonojen koodaus, kääntäminen ja palindromit

Toteuttakaa sanojen kääntäminen paikallaan, run-length-koodaus ja palindromien tunnistus, mukaan lukien keskeltä laajentamisen tekniikka.

Oppitunti 4/413 vaihetta

Merkkijonojen koodaus, kääntäminen ja palindromit on ilmainen DSA Interview Prep-oppitunti CoddyKitissä. Tämä on oppitunti 4/4. Voit lukea tästä oppimispolusta kokonaan mitkä tahansa 3 oppituntia ilmaiseksi — sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä käytännön harjoittelun sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. Oppitunti kuuluu DSA Interview Prep-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. DSA Interview Prep-kurssilla on yhteensä 4 oppituntia.

Merkkijonon kääntäminen paikallaan

Pythonin merkkijonot ovat muuttumattomia, joten kääntäminen paikallaan tarkoittaa muuntamista merkkilistaksi, merkkien vaihtamista kahden osoittimen avulla ja listan yhdistämistä takaisin merkkijonoksi. Klassinen kahden osoittimen vaihto: asetetaan left indeksin 0 kohdalle ja right viimeiseen indeksiin; merkit vaihdetaan ja osoittimia siirretään kohti keskustaa, kunnes ne ohittavat toisensa. Aikavaativuus on O(n) ja merkkilistan tilavaativuus O(n) (tätä ei voi välttää, koska merkkijonot ovat muuttumattomia).

def reverse_string(s):
    chars = list(s)
    left, right = 0, len(chars) - 1
    while left < right:
        chars[left], chars[right] = chars[right], chars[left]
        left  += 1
        right -= 1
    return ''.join(chars)

print(reverse_string('hello'))   # 'olleh'
print(reverse_string('Hannah'))  # 'hannaH'

# Pythonic shortcut (creates new string):
print('hello'[::-1])  # 'olleh'

Lauseen sanojen kääntäminen

Kääntäkää sanojen järjestys ja poistakaa ylimääräiset välilyönnit. Selkeä Python-ratkaisu: split (käsittelee useita välilyöntejä), kääntäkää lista ja join. Paikallaan tehtävässä merkkitaulukon käännössä käännetään ensin koko taulukko ja sitten jokainen yksittäinen sana. Tämä kahden läpikäynnin menetelmä toimii O(n)-ajassa ja käyttää O(n) tilaa (Pythonin merkkijonojen muuttumattomuuden vuoksi tätä ei voi välttää).

def reverse_words(s):
    words = s.split()       # split and strip whitespace
    words.reverse()         # in-place reverse
    return ' '.join(words)  # single space between words

print(reverse_words('  hello   world  '))  # 'world hello'
print(reverse_words('a good example'))     # 'example good a'

# One-liner:
print(' '.join('  hello   world  '.split()[::-1]))

Palindromin tunnistus: yksinkertainen

Merkkijono on palindromi, jos se on sama kuin käänteensä. Nopein Python-tarkistus on: s == s[::-1]. Kirjainkoosta riippumattomissa, vain aakkosnumeerisista merkeistä koostuvissa palindromitarkistuksissa (yleisin työhaastattelumuunnelma) merkkijono normalisoidaan ensin: suodatetaan pois muut kuin aakkosnumeeriset merkit ja muunnetaan merkit pieniksi kirjaimiksi, minkä jälkeen merkkijonoja verrataan. Molempien aikavaativuus on O(n).

def is_palindrome(s):
    # Filter and normalise
    cleaned = ''.join(c.lower() for c in s if c.isalnum())
    return cleaned == cleaned[::-1]

print(is_palindrome('A man, a plan, a canal: Panama'))  # True
print(is_palindrome('race a car'))                       # False
print(is_palindrome('Was it a car or a cat I saw?'))     # True

Palindromin tunnistus kahdella osoittimella

Kun lisätilan tarve halutaan pitää O(1):ssä, palindromi tarkistetaan kahdella osoittimella viipaloinnin sijaan. Asetetaan left arvoon 0 ja right loppuun. Ohitetaan muut kuin aakkosnumeeriset merkit, verrataan jäljelle jääviä merkkejä kirjainkoosta riippumatta ja palautetaan False, jos merkit eivät täsmää. Tämä on sanallisempi tapa, mutta siinä ei luoda puhdistettua merkkijonoa lainkaan — tämä on tärkeää, kun muistia on niukasti.

def is_palindrome_twoptr(s):
    left, right = 0, len(s) - 1
    while left < right:
        while left < right and not s[left].isalnum():
            left += 1
        while left < right and not s[right].isalnum():
            right -= 1
        if s[left].lower() != s[right].lower():
            return False
        left += 1; right -= 1
    return True

print(is_palindrome_twoptr('A man, a plan, a canal: Panama'))  # True

Pisin palindromi keskeltä laajentamalla

Keskipisteen ympärille laajentamisen tekniikalla löydetään pisin palindrominen osamerkkijono O(n²)-ajassa ja O(1):n lisätilalla. Jokaisen merkin (parittoman pituuden palindromit) ja jokaisen merkkien välisen aukon (parillisen pituuden palindromit) kohdalla laajennetaan ulospäin niin kauan kuin merkit täsmäävät. Seurataan parasta tähän mennessä löydettyä (alku, loppu) -paria. Keskipisteitä on 2n-1, ja jokainen laajennus vaatii pahimmillaan O(n) aikaa.

def longest_palindrome(s):
    best_start = best_end = 0

    def expand(left, right):
        while left >= 0 and right < len(s) and s[left] == s[right]:
            left -= 1; right += 1
        return left + 1, right - 1  # last valid bounds

    for i in range(len(s)):
        l, r = expand(i, i)      # odd-length
        if r - l > best_end - best_start:
            best_start, best_end = l, r
        l, r = expand(i, i + 1)  # even-length
        if r - l > best_end - best_start:
            best_start, best_end = l, r

    return s[best_start:best_end+1]

print(longest_palindrome('babad'))    # 'bab' or 'aba'
print(longest_palindrome('cbbd'))     # 'bb'

Manacherin algoritmin esikatselu

Manacherin algoritmi löytää pisimmän palindromisen osamerkkijonon O(n)-ajassa hyödyntämällä oivallusta, jonka mukaan suuremman palindromin sisällä oleva palindromi voidaan alustaa peilauskohdan perusteella. Algoritmia pyydetään harvoin toteuttamaan työhaastatteluissa, mutta on hyvä tietää sen olemassaolosta. Useimmat haastattelijat hyväksyvät O(n²)-aikaisen keskipisteen ympärille laajentamisen lähestymistavan riittävän hyvänä — Manacherin algoritmi kannattaa mainita teoreettisena O(n)-ratkaisuna, jos esitetään jatkokysymys.

# Manacher's: O(n) longest palindromic substring
def manacher(s):
    # Transform s into '#a#b#a#' to handle even/odd uniformly
    t = '#' + '#'.join(s) + '#'
    n = len(t)
    P = [0] * n  # P[i] = palindrome radius at i
    center = right = 0
    for i in range(n):
        mirror = 2 * center - i
        if i < right:
            P[i] = min(right - i, P[mirror])
        while (i + P[i] + 1 < n and i - P[i] - 1 >= 0
               and t[i+P[i]+1] == t[i-P[i]-1]):
            P[i] += 1
        if i + P[i] > right:
            center, right = i, i + P[i]
    max_len = max(P)
    center_idx = P.index(max_len)
    start = (center_idx - max_len) // 2
    return s[start:start+max_len]

print(manacher('babad'))   # 'bab'

Ajonpituuskoodaus

Ajonpituuskoodaus (RLE) pakkaa peräkkäiset toistuvat merkit: 'aaabbc' muuttuu muotoon 'a3b2c1'. Toteutus: käydään syöte läpi nopealla osoittimella, jolla etsitään kunkin toistojakson loppu, kirjoitetaan merkki ja lukumäärä tulostelistaan ja yhdistetään lista lopuksi. Syöte voi olla lyhyillä toistoilla lyhyempi kuin koodattu tuloste — tarkistakaa aina, onko koodattu versio alkuperäistä lyhyempi, ennen kuin palautatte sen.

def encode_rle(s):
    if not s: return ''
    parts = []
    i = 0
    while i < len(s):
        char = s[i]
        j = i
        while j < len(s) and s[j] == char:
            j += 1
        count = j - i
        parts.append(char + (str(count) if count > 1 else ''))
        i = j
    encoded = ''.join(parts)
    return encoded if len(encoded) < len(s) else s

print(encode_rle('aaabbc'))    # 'a3b2c'
print(encode_rle('abc'))       # 'abc'  (no compression gain)

Ajonpituuskoodattujen merkkijonojen purkaminen

Ajonpituuskoodauksen purkaminen lukee merkit ja niitä seuraavat numeromerkkijaksot ja laajentaa kunkin toiston. Haastatteluissa esitetään toisinaan LeetCode-muunnelma, jossa koodaus käyttää toistettaville osamerkkijonoille muotoa k[encoded_string]: esimerkiksi 3[ab] → ababab. Tämä sisäkkäinen muunnelma edellyttää pinoa useiden sisäkkäisyystasojen käsittelemiseen.

def decode_rle(s):
    result = []
    i = 0
    while i < len(s):
        char = s[i]; i += 1
        num_str = ''
        while i < len(s) and s[i].isdigit():
            num_str += s[i]; i += 1
        count = int(num_str) if num_str else 1
        result.append(char * count)
    return ''.join(result)

print(decode_rle('a3b2c'))    # 'aaabbc'
print(decode_rle('a2b3c1'))   # 'aabbbc'

# Nested bracket decode (LeetCode 394)
def decode_bracket(s):
    stack = []
    for c in s:
        if c != ']':
            stack.append(c)
        else:
            chars = []
            while stack[-1] != '[':
                chars.append(stack.pop())
            stack.pop()  # remove '['
            k = int(stack.pop())
            stack.append(''.join(reversed(chars)) * k)
    return ''.join(stack)
print(decode_bracket('3[ab]'))  # 'ababab'

Kelvollinen palindromi II: yksi poisto sallittu

Kun annettuna on merkkijono, palauttakaa True, jos siitä voidaan tehdä palindromi poistamalla enintään yksi merkki. Käyttäkää kahta osoitinta; ensimmäisen ristiriidan kohdalla tarkistakaa, onko joko s[left+1:right+1] tai s[left:right] palindromi (toisin sanoen kokeilkaa kummankin ristiriitaisen merkin ohittamista). Jos jompikumpi puoli on palindromi, palauttakaa True. Tämä ahne menetelmä toimii, koska ristiriitaisen merkin ohittaminen on ainoa hyödyllinen toimenpide.

def valid_palindrome(s):
    def is_pal(l, r):
        while l < r:
            if s[l] != s[r]: return False
            l += 1; r -= 1
        return True

    left, right = 0, len(s) - 1
    while left < right:
        if s[left] != s[right]:
            # Try skipping either character
            return is_pal(left+1, right) or is_pal(left, right-1)
        left += 1; right -= 1
    return True

print(valid_palindrome('aba'))    # True
print(valid_palindrome('abca'))   # True  (delete 'c')
print(valid_palindrome('abc'))    # False

Palindromiositus I

Jakakaa merkkijono kaikiksi palindromisiksi osamerkkijonoiksi. Käyttäkää peruutushakua: kokeilkaa jokaisessa vaiheessa kaikkia jäljellä olevan merkkijonon etuliitteitä; jos etuliite on palindromi, jatkakaa hakua rekursiivisesti merkkijonon loppuosasta. Esilaskekaa 2D-totuustaulukko is_pal[i][j] väli-DP:n avulla, jotta palindromin tarkistaminen vie O(1) aikaa. Näin koko peruutushaun aikavaativuus pienenee muodosta O(n² × 2^n) muotoon O(n × 2^n) — tämä on hyväksyttävää, koska kaikkien ositusten tuottaminen on luonnostaan eksponentiaalista.

def partition(s):
    n = len(s)
    dp = [[False]*n for _ in range(n)]
    for i in range(n):
        dp[i][i] = True
    for length in range(2, n+1):
        for i in range(n-length+1):
            j = i + length - 1
            if s[i] == s[j]:
                dp[i][j] = length == 2 or dp[i+1][j-1]

    result = []
    def backtrack(start, path):
        if start == n: result.append(path[:]); return
        for end in range(start, n):
            if dp[start][end]:
                path.append(s[start:end+1])
                backtrack(end+1, path)
                path.pop()
    backtrack(0, [])
    return result

print(partition('aab'))  # [['a','a','b'],['aa','b']]

Lyhin palindromi: merkkijonojen hajautus

Etsikää lyhin palindromi, joka voidaan muodostaa lisäämällä merkkejä merkkijonon alkuun. Keskeinen oivallus on löytää merkkijonon s pisin palindrominen etuliite ja lisätä sen jäljelle jäävän loppuosan käänne alkuun. Pisim­män palindromisen etuliitteen löytämiseksi tehokkaasti käytetään KMP:n epäonnistumisfunktiota merkkijonolle s + '#' + reverse(s). Epäonnistumisfunktion viimeinen arvo kertoo pisimmän palindromisen etuliitteen pituuden.

def shortest_palindrome(s):
    rev = s[::-1]
    combined = s + '#' + rev  # '#' prevents overlap
    n = len(combined)
    kmp = [0] * n
    j = 0
    for i in range(1, n):
        while j > 0 and combined[i] != combined[j]:
            j = kmp[j-1]
        if combined[i] == combined[j]:
            j += 1
        kmp[i] = j
    # kmp[-1] = length of longest palindromic prefix
    to_add = rev[:len(s) - kmp[-1]]
    return to_add + s

print(shortest_palindrome('aacecaaa'))  # 'aaacecaaa'
print(shortest_palindrome('abcd'))      # 'dcbabcd'

Pikatarkistus

Testatkaa ymmärrystänne tämän oppitunnin Data Structures & Algorithms — Coding Interview Prep -käsitteistä.

Oppitunnin kertaus

Tässä oppitunnissa opitte: kahdella osoittimella tehtävä palindromin tunnistus toimii O(n)-ajassa ja käyttää O(1) tilaa — suosikaa aina indeksipohjaisia tarkistuksia käännetyn kopion varaamisen sijaan, kun tilalla on merkitystä, keskipisteen ympärille laajentamalla löydetään pisin palindrominen osamerkkijono O(n²)-ajassa käsittelemällä kutakin 2n-1 sijainnista mahdollisena palindromin keskipisteenä ja ajonpituuskoodaus pakkaa peräkkäiset toistojaksot O(n)-ajassa, kun taas sisäkkäisen hakasuljemuunnelman purkaminen edellyttää pinoa. Seuraavaksi tutustumme kuplalajitteluun ja lisäyslajitteluun.

Aloita maksutta

Opi Python tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
30
Oppitunnit
120

Usein kysytyt kysymykset

Onko oppitunti ”Merkkijonojen koodaus, kääntäminen ja palindromit” ilmainen?

Kyllä — voit lukea täällä verkossa kokonaan ilmaiseksi mitkä tahansa DSA Interview Prep-oppimispolun 3 oppituntia, myös oppitunnin “Merkkijonojen koodaus, kääntäminen ja palindromit”. Sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä interaktiiviset harjoitukset sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. DSA Interview Prep-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”Merkkijonojen koodaus, kääntäminen ja palindromit”?

Toteuttakaa sanojen kääntäminen paikallaan, run-length-koodaus ja palindromien tunnistus, mukaan lukien keskeltä laajentamisen tekniikka. Harjoittelet DSA Interview Prep-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni DSA Interview Prep-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin DSA Interview Prep-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 4/4.

Kuinka kauan ”Merkkijonojen koodaus, kääntäminen ja palindromit”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä DSA Interview Prep-oppitunnilla?

Kyllä. Jokainen DSA Interview Prep-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. Pythonin merkkijono-API haastattelutehtäviin
  2. Liukuva ikkuna alimerkkijonoille
  3. Anagrammit ja merkkifrekvenssikartat
  4. Merkkijonojen koodaus, kääntäminen ja palindromit
← Takaisin: DSA Interview Prep