การเข้ารหัสสตริง การกลับลำดับ และพาลินโดรม
สร้างการกลับลำดับคำในที่เดิม การเข้ารหัสตามความยาวช่วง และการตรวจจับพาลินโดรม รวมถึงเทคนิคขยายรอบจุดกึ่งกลาง
การเข้ารหัสสตริง การกลับลำดับ และพาลินโดรม เป็นบทเรียน 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- ส่วนติดต่อสตริงของ Python สำหรับการสัมภาษณ์
- หน้าต่างเลื่อนสำหรับสตริงย่อย
- แอนนาแกรมและแผนผังความถี่อักขระ
- การเข้ารหัสสตริง การกลับลำดับ และพาลินโดรม