0Pricing
Coding Interview Prep · บทเรียน

การเข้ารหัสสตริง การกลับลำดับ และพาลินโดรม

สร้างการกลับลำดับคำในที่เดิม การเข้ารหัสตามความยาวช่วง และการตรวจจับพาลินโดรม รวมถึงเทคนิคขยายรอบจุดกึ่งกลาง

การเข้ารหัสสตริง การกลับลำดับ และพาลินโดรม เป็นบทเรียน Coding Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Coding Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

การกลับลำดับสตริงในตำแหน่งเดิม

สตริงในไพทอนเปลี่ยนแปลงไม่ได้ ดังนั้นการกลับลำดับแบบทำในตำแหน่งเดิมจึงหมายถึงการแปลงเป็นรายการอักขระ สลับอักขระด้วยตัวชี้สองตัว แล้วใช้ join การสลับด้วยตัวชี้สองตัวแบบคลาสสิกคือ วาง left ไว้ที่ดัชนี 0 และ right ไว้ที่ดัชนีสุดท้าย จากนั้นสลับอักขระและเลื่อนตัวชี้เข้าหากันจนกว่าจะข้ามกัน วิธีนี้ใช้เวลา O(n) และใช้พื้นที่ O(n) สำหรับรายการอักขระ ซึ่งลดลงกว่านี้ไม่ได้เนื่องจากสตริงเปลี่ยนแปลงไม่ได้

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'

การกลับลำดับคำในประโยค

กลับลำดับของคำพร้อมตัดช่องว่างส่วนเกินออก วิธีแก้ปัญหาในไพทอนที่กระชับคือใช้ split (รองรับช่องว่างหลายตัว) กลับลำดับรายการ แล้วใช้ join สำหรับการกลับลำดับแบบทำในตำแหน่งเดิมบนอาร์เรย์อักขระ ให้กลับลำดับทั้งอาร์เรย์ก่อน แล้วจึงกลับลำดับคำแต่ละคำ วิธีสองรอบนี้ใช้เวลา O(n) และใช้พื้นที่ O(n) ซึ่งหลีกเลี่ยงไม่ได้เมื่อใช้สตริงในไพทอน เนื่องจากสตริงเปลี่ยนแปลงไม่ได้

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]))

การตรวจหาพาลินโดรม: แบบพื้นฐาน

สตริงเป็น พาลินโดรม หากมีค่าเท่ากับ reverse ของตัวมันเอง วิธีตรวจสอบในไพทอนที่เร็วที่สุดคือ s == s[::-1] สำหรับพาลินโดรมที่ไม่แยกตัวพิมพ์ใหญ่เล็กและประกอบด้วยอักขระตัวอักษรหรือตัวเลขเท่านั้น (รูปแบบที่พบบ่อยที่สุดในการสัมภาษณ์) ให้ปรับรูปแบบสตริงให้เป็นมาตรฐานก่อน โดยกรองอักขระที่ไม่ใช่ตัวอักษรหรือตัวเลขออก แล้วแปลงเป็นตัวพิมพ์เล็ก จากนั้นจึงเปรียบเทียบ ทั้งสองวิธีใช้เวลา 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

การตรวจหาพาลินโดรม: ตัวชี้สองตัว

หากต้องการใช้พื้นที่เพิ่มเติม O(1) ให้ตรวจสอบพาลินโดรมด้วยตัวชี้สองตัวแทนการตัดแบ่งสตริง วาง left ไว้ที่ 0 และ right ไว้ที่จุดสิ้นสุด ข้ามอักขระที่ไม่ใช่ตัวอักษรหรือตัวเลข เปรียบเทียบอักขระที่เหลือโดยไม่แยกตัวพิมพ์ใหญ่เล็ก และคืนค่าเป็นเท็จเมื่อพบความไม่ตรงกัน วิธีนี้เขียนยาวกว่า แต่ไม่ต้องสร้างสตริงที่ทำความสะอาดแล้วเลย จึงสำคัญเมื่อหน่วยความจำมีจำกัด

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

การขยายรอบจุดกึ่งกลางเพื่อหาพาลินโดรมที่ยาวที่สุด

เทคนิค การขยายรอบจุดกึ่งกลาง ใช้ค้นหาสตริงย่อยแบบพาลินโดรมที่ยาวที่สุดในเวลา O(n²) โดยใช้พื้นที่เพิ่มเติม O(1) สำหรับอักขระแต่ละตัว (พาลินโดรมความยาวคี่) และช่องว่างระหว่างอักขระแต่ละคู่ (พาลินโดรมความยาวคู่) ให้ขยายออกด้านนอกตราบใดที่อักขระยังตรงกัน เก็บคู่ (start, end) ที่ดีที่สุดที่พบ มีจุดกึ่งกลางทั้งหมด 2n-1 จุด และการขยายแต่ละครั้งใช้เวลา O(n) ในกรณีเลวร้ายที่สุด

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'

เกริ่นนำอัลกอริทึมมานาเชอร์

อัลกอริทึมมานาเชอร์ค้นหาสตริงย่อยแบบพาลินโดรมที่ยาวที่สุดในเวลา O(n) โดยอาศัยข้อสังเกตว่า พาลินโดรมที่อยู่ภายในพาลินโดรมขนาดใหญ่กว่าสามารถกำหนดค่าเริ่มต้นจากตำแหน่งสะท้อนได้ โดยทั่วไปไม่ค่อยมีการขอให้เขียนอัลกอริทึมนี้ในการสัมภาษณ์ แต่ก็ควรรู้ว่ามีอัลกอริทึมนี้อยู่ ผู้สัมภาษณ์ส่วนใหญ่ยอมรับวิธีการขยายรอบจุดกึ่งกลางที่ใช้เวลา O(n²) ว่า “มีประสิทธิภาพเพียงพอ” หากถูกถามต่อ ให้กล่าวถึงมานาเชอร์ในฐานะวิธีแก้ปัญหาเชิงทฤษฎีที่ใช้เวลา O(n)

# 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'

การเข้ารหัสความยาวช่วง

การเข้ารหัสความยาวช่วง (RLE) ใช้บีบอัดอักขระที่ซ้ำกันต่อเนื่องกัน โดย 'aaabbc' จะกลายเป็น 'a3b2c1' วิธีเขียนคือสแกนด้วยตัวชี้ที่เลื่อนไปข้างหน้าอย่างรวดเร็วเพื่อหาจุดสิ้นสุดของแต่ละช่วง เขียนอักขระและจำนวนลงในรายการผลลัพธ์ แล้วใช้ join ข้อมูลเข้าอาจสั้นกว่าผลลัพธ์ที่เข้ารหัสแล้วสำหรับช่วงสั้น ๆ ดังนั้นให้ตรวจสอบเสมอว่าเวอร์ชันที่เข้ารหัสสั้นกว่าหรือไม่ก่อนส่งคืน

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)

การถอดรหัสสตริงที่เข้ารหัสแบบความยาวช่วง

การถอดรหัส RLE จะอ่านอักขระและลำดับตัวเลขที่ตามมา แล้วขยายแต่ละช่วง ผู้สัมภาษณ์บางครั้งนำเสนอรูปแบบของ LeetCode ซึ่งใช้การเข้ารหัสรูปแบบ k[encoded_string] สำหรับสตริงย่อยที่ซ้ำกัน เช่น 3[ab] → ababab รูปแบบที่มีการซ้อนกันนี้ต้องใช้สแตกเพื่อจัดการการซ้อนหลายระดับ

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'

พาลินโดรมที่ถูกต้อง II: อนุญาตให้ลบได้หนึ่งอักขระ

เมื่อกำหนดสตริง ให้คืนค่าเป็นจริงหากสามารถทำให้เป็นพาลินโดรมได้ด้วยการลบ อักขระไม่เกินหนึ่งตัว ใช้ตัวชี้สองตัว เมื่อพบความไม่ตรงกันครั้งแรก ให้ตรวจสอบว่า s[left+1:right+1] หรือ s[left:right] เป็นพาลินโดรมหรือไม่ (กล่าวคือ ลองข้ามอักขระที่ไม่ตรงกันแต่ละตัว) หากด้านใดด้านหนึ่งเป็นพาลินโดรม ให้คืนค่าเป็นจริง วิธีแบบละโมบนี้ใช้ได้เพราะการข้ามอักขระที่ไม่ตรงกันคือการดำเนินการเดียวที่มีประโยชน์

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

การแบ่งพาลินโดรม I

partition สตริงออกเป็นสตริงย่อยทั้งหมดที่เป็นพาลินโดรม ใช้ backtracking โดยในแต่ละขั้นให้ลองคำนำหน้าทั้งหมดของสตริงส่วนที่เหลือ หากคำนำหน้าเป็นพาลินโดรม ให้เรียกซ้ำกับส่วนที่เหลือ คำนวณตารางบูลีนสองมิติ is_pal[i][j] ล่วงหน้าโดยใช้ DP ตามช่วง เพื่อให้การตรวจสอบพาลินโดรมใช้เวลา O(1) ส่งผลให้เวลา backtracking โดยรวมลดจาก O(n² × 2^n) เหลือ O(n × 2^n) ซึ่งยอมรับได้ เนื่องจากการสร้างการแบ่งทั้งหมดมีลักษณะเป็นเลขชี้กำลังโดยธรรมชาติ

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']]

พาลินโดรมที่สั้นที่สุด: การแฮชสตริง

ค้นหาพาลินโดรมที่สั้นที่สุดซึ่งได้จากการเพิ่มอักขระไว้ด้านหน้าสตริง ข้อสังเกตสำคัญคือ ให้หาคำนำหน้าแบบพาลินโดรมที่ยาวที่สุดของ s แล้วเติม reverse ของส่วนต่อท้ายที่เหลือไว้ด้านหน้า หากต้องการหาคำนำหน้าแบบพาลินโดรมที่ยาวที่สุดอย่างมีประสิทธิภาพ ให้ใช้ฟังก์ชันความล้มเหลวของ KMP กับสตริง s + '#' + reverse(s) ค่าสุดท้ายของฟังก์ชันความล้มเหลวจะให้ความยาวของคำนำหน้าแบบพาลินโดรมที่ยาวที่สุด

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'

ตรวจสอบความเข้าใจ

ทดสอบความเข้าใจแนวคิดโครงสร้างข้อมูลและอัลกอริทึม — การเตรียมตัวสัมภาษณ์การเขียนโปรแกรมจากบทเรียนนี้

ทบทวนบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้ว่า การตรวจหาพาลินโดรมด้วยตัวชี้สองตัวใช้เวลา O(n) และใช้พื้นที่ O(1) — เมื่อพื้นที่มีความสำคัญ ให้เลือกการตรวจสอบตามดัชนีแทนการจัดสรรสำเนาที่กลับลำดับเสมอ การขยายรอบจุดกึ่งกลางค้นหาสตริงย่อยแบบพาลินโดรมที่ยาวที่สุดในเวลา O(n²) โดยถือว่าตำแหน่งทั้ง 2n-1 ตำแหน่งเป็นจุดกึ่งกลางที่อาจเป็นพาลินโดรม และ การเข้ารหัสความยาวช่วงบีบอัดช่วงที่ต่อเนื่องกันในเวลา O(n) ขณะที่การถอดรหัสรูปแบบวงเล็บที่มีการซ้อนกันต้องใช้สแตก บทถัดไปเราจะสำรวจการเรียงลำดับแบบฟองและการเรียงลำดับแบบแทรก

คำถามที่พบบ่อย

บทเรียน “การเข้ารหัสสตริง การกลับลำดับ และพาลินโดรม” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การเข้ารหัสสตริง การกลับลำดับ และพาลินโดรม” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Coding Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การเข้ารหัสสตริง การกลับลำดับ และพาลินโดรม”

สร้างการกลับลำดับคำในที่เดิม การเข้ารหัสตามความยาวช่วง และการตรวจจับพาลินโดรม รวมถึงเทคนิคขยายรอบจุดกึ่งกลาง คุณปฏิบัติ Coding Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Coding Interview Prep หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Coding Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “การเข้ารหัสสตริง การกลับลำดับ และพาลินโดรม” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Coding Interview Prep นี้ได้ไหม

ได้ บทเรียน Coding Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. ส่วนติดต่อสตริงของ Python สำหรับการสัมภาษณ์
  2. หน้าต่างเลื่อนสำหรับสตริงย่อย
  3. แอนนาแกรมและแผนผังความถี่อักขระ
  4. การเข้ารหัสสตริง การกลับลำดับ และพาลินโดรม
← กลับไปที่ Coding Interview Prep