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

ถอดรหัสวิธีและการนับเส้นทาง

แก้โจทย์วิธีถอดรหัส (การจับคู่ตัวเลขกับตัวอักษร) ด้วย DP ลักษณะคล้ายฟีโบนัชชี แล้วนับเส้นทางบนบันไดที่มีจำนวนก้าวแปรผัน

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

ปัญหาวิธีการถอดรหัส

วิธีการถอดรหัส (LeetCode 91) จะแปลงสตริงตัวเลขเป็นตัวอักษร: 'A'=1, 'B'=2, ..., 'Z'=26 เมื่อกำหนดสตริงตัวเลขที่เข้ารหัสแล้ว ให้หาจำนวนวิธีที่แตกต่างกันในการถอดรหัส ตัวอย่างเช่น '12' สามารถถอดเป็น 'AB' (1+2) หรือ 'L' (12) จึงมี 2 วิธี '226' สามารถเป็น 'BZ' (2+26), 'VF' (22+6) หรือ 'BBF' (2+2+6) จึงมี 3 วิธี ศูนย์นำหน้าทำให้การถอดรหัสบางแบบไม่ถูกต้อง

# Encoding: A=1, B=2, ..., Z=26
# '12' → 'AB' or 'L' → 2 ways
# '226' → 'BZ' or 'VF' or 'BBF' → 3 ways
# '06' → invalid (no letter for '0')
# '10' → 'J' only → 1 way (only valid as 10, not 1+0)

s = '226'
print('Decodings for', s, ':', 3)  # Expected: 3

การกำหนดสูตร DP สำหรับจำนวนวิธีถอดรหัส

ให้ dp[i] = จำนวนวิธีถอดรหัส s[:i] กรณีฐานคือ dp[0] = 1 (สตริงว่างมีหนึ่งวิธี) และ dp[1] = 1 หาก s[0] != '0' มิฉะนั้นเป็น 0 การเปลี่ยนสถานะคือ หาก s[i-1] != '0' ให้บวก dp[i-1] (การถอดรหัสเลขหลักเดียว) หาก 10 ≤ int(s[i-2:i]) ≤ 26 ให้บวก dp[i-2] (การถอดรหัสเลขสองหลัก) โดยพื้นฐานแล้วนี่คือ รูปแบบลำดับฟีโบนัชชีที่มีการตรวจสอบความถูกต้อง

def num_decodings(s):
    n = len(s)
    dp = [0] * (n + 1)
    dp[0] = 1  # empty prefix
    dp[1] = 0 if s[0] == '0' else 1
    
    for i in range(2, n + 1):
        # Single digit decode
        if s[i-1] != '0':
            dp[i] += dp[i-1]
        # Two digit decode
        two_digit = int(s[i-2:i])
        if 10 <= two_digit <= 26:
            dp[i] += dp[i-2]
    return dp[n]

print(num_decodings('12'))   # 2
print(num_decodings('226'))  # 3
print(num_decodings('06'))   # 0

กับดักเลขศูนย์นำหน้า

ส่วนที่ยากที่สุดของการถอดรหัสวิธีต่าง ๆ คือการจัดการเลขศูนย์ เลข 0 ที่อยู่เดี่ยว ๆ ไม่สามารถถอดรหัสได้ (ไม่มีตัวอักษรใดแทนค่าเป็น 0) ดังนั้นหาก s[i-1] == '0' อย่าบวก dp[i-1] เลข 0 ที่เป็นหลักที่สอง จะใช้ได้ก็ต่อเมื่อเลขสองหลักนั้นเป็น 10 หรือ 20 เท่านั้น ส่วน 30 หรือ 40 (รวมถึงค่าที่สูงกว่านั้น) ใช้ไม่ได้เนื่องจากเกิน 26 ตรวจสอบ 10 ≤ two_digit ≤ 26 เสมอ อย่าตรวจสอบเพียง two_digit ≤ 26 เท่านั้น

def num_decodings(s):
    if not s or s[0] == '0': return 0
    n = len(s)
    dp = [0] * (n + 1)
    dp[0] = 1
    dp[1] = 1  # s[0] != '0' guaranteed by guard above
    for i in range(2, n + 1):
        one = int(s[i-1])
        two = int(s[i-2:i])
        if one != 0: dp[i] += dp[i-1]  # valid single digit
        if 10 <= two <= 26: dp[i] += dp[i-2]  # valid two digits
    return dp[n]

print(num_decodings('10'))   # 1 (only 'J')
print(num_decodings('30'))   # 0 (30 > 26, '0' alone invalid)
print(num_decodings('100'))  # 0 (dp[2]=1 then '00' invalid, single '0' invalid)

การถอดรหัสแบบลดหน่วยความจำ

เช่นเดียวกับลำดับฟีโบนัชชี ความสัมพันธ์เวียนเกิดของจำนวนวิธีถอดรหัสจะย้อนดูเพียงสองตำแหน่งก่อนหน้า ดังนั้นจึงลดหน่วยความจำจาก O(n) เป็น O(1) ได้ด้วยตัวแปรสองตัว ใช้ prev2 (ย้อนกลับไปสองขั้น) และ prev1 (ย้อนกลับไปหนึ่งขั้น) ในแต่ละขั้น ให้คำนวณ curr จากค่าทั้งสอง แล้วเลื่อนค่าไป นี่เป็นการปรับให้เหมาะสมแบบใช้ตัวแปรสองตัวเช่นเดียวกับลำดับฟีโบนัชชี → การปรับให้เหมาะสมแบบใช้ตัวแปรสองตัว

def num_decodings_o1(s):
    if not s or s[0] == '0': return 0
    prev2 = 1  # dp[0]
    prev1 = 1  # dp[1]
    for i in range(2, len(s) + 1):
        curr = 0
        if s[i-1] != '0':
            curr += prev1
        two = int(s[i-2:i])
        if 10 <= two <= 26:
            curr += prev2
        prev2, prev1 = prev1, curr
    return prev1

print(num_decodings_o1('226'))   # 3
print(num_decodings_o1('12'))    # 2
print(num_decodings_o1('0'))     # 0

การนับเส้นทางบนบันได

การปีนบันได (LeetCode 70) ถามว่า หากสามารถก้าวครั้งละ 1 หรือ 2 ขั้น จะมีวิธีปีนบันได n ขั้นได้กี่วิธี ปัญหานี้ตรงกับลำดับฟีโบนัชชีพอดี: ways(n) = ways(n-1) + ways(n-2) โดย ways(1)=1, ways(2)=2, ways(3)=3, ways(4)=5 และสามารถสรุปทั่วไปได้เมื่อก้าวได้สูงสุด k ขั้น: ways(n) = sum(ways(n-1), ..., ways(n-k))

def climb_stairs(n):
    if n <= 2: return n
    prev2, prev1 = 1, 2
    for _ in range(3, n + 1):
        prev2, prev1 = prev1, prev1 + prev2
    return prev1

for i in range(1, 8):
    print(f'climb_stairs({i}) = {climb_stairs(i)}')
# 1, 2, 3, 5, 8, 13, 21 — Fibonacci!

การปีนบันไดด้วยจำนวนขั้นที่เปลี่ยนแปลงได้

เมื่อสามารถก้าวได้หลายจำนวนจากเซตที่กำหนด (เช่น {1, 3, 5}) ความสัมพันธ์เวียนเกิดจะเป็น dp[i] = sum(dp[i-k] for k in steps if i-k >= 0) ใช้หน้าต่างเลื่อนขนาด max(steps) เพื่อประหยัดหน่วยความจำ นี่คือรูปแบบการนับของปัญหาเป้สะพายหลังแบบไม่จำกัดจำนวน โดยสามารถใช้ขนาดการก้าวแต่ละค่าได้กี่ครั้งก็ได้

def count_ways(n, steps):
    dp = [0] * (n + 1)
    dp[0] = 1  # one way to stay at ground
    for i in range(1, n + 1):
        for step in steps:
            if i >= step:
                dp[i] += dp[i - step]
    return dp[n]

# Steps of 1 or 2 (classic climbing stairs)
print(count_ways(5, [1, 2]))    # 8
# Steps of 1, 3, or 5
print(count_ways(5, [1, 3, 5])) # 5
# Steps of 2 or 3
print(count_ways(6, [2, 3]))    # 3 (2+2+2, 3+3, 2+4-invalid, 2+2+2, 3+3, 3+2+1-no...)

การปีนบันไดด้วยต้นทุนต่ำสุด

การปีนบันไดด้วยต้นทุนต่ำสุด (LeetCode 746) กำหนดต้นทุนให้แต่ละขั้น และถามหาต้นทุนต่ำสุดในการไปถึงยอดบันได จากขั้น i สามารถกระโดดไปยัง i+1 หรือ i+2 ได้ ความสัมพันธ์เวียนเกิดคือ dp[i] = cost[i] + min(dp[i-1], dp[i-2]) คุณสามารถเริ่มจากขั้น 0 หรือขั้น 1 ก็ได้ คำตอบคือ min(dp[n-1], dp[n-2])

def min_cost_climbing(cost):
    n = len(cost)
    if n == 1: return cost[0]
    dp = [0] * n
    dp[0] = cost[0]
    dp[1] = cost[1]
    for i in range(2, n):
        dp[i] = cost[i] + min(dp[i-1], dp[i-2])
    return min(dp[-1], dp[-2])  # can start from step 0 or 1

print(min_cost_climbing([10, 15, 20]))      # 15
print(min_cost_climbing([1, 100, 1, 1, 1, 100, 1, 1, 100, 1]))  # 6

การถอดรหัสวิธีต่าง ๆ II: ตัวเลขแทนค่าได้

การถอดรหัสวิธีต่าง ๆ II (LeetCode 639) เพิ่มอักขระแทนค่าได้ '*' ซึ่งสามารถแทนตัวเลขใดก็ได้ตั้งแต่ 1 ถึง 9 ทำให้จำนวนวิธีถอดรหัสที่ถูกต้องเพิ่มขึ้นอย่างมาก '*' หนึ่งตัวมีส่วนช่วย 9 วิธี (แทนตัวเลข 1-9 ได้) '*' สองตัวรวมกันสร้างชุดค่าผสมเลขสองหลักได้ 9×9 แบบ แต่ใช้ได้เฉพาะค่าที่ไม่เกิน 26 เท่านั้น (11-19 = 9 วิธี, 21-26 = 6 วิธี ดังนั้น '**' มี 15 วิธี) จึงต้องวิเคราะห์แต่ละกรณีอย่างรอบคอบ

def num_decodings_ii(s):
    MOD = 10**9 + 7
    prev2, prev1 = 1, 9 if s[0] == '*' else (0 if s[0] == '0' else 1)
    for i in range(1, len(s)):
        curr = 0
        c, p = s[i], s[i-1]
        # Single digit
        if c == '*': curr += 9 * prev1
        elif c != '0': curr += prev1
        # Two digits
        if p == '*' and c == '*': curr += 15 * prev2  # 11-19(9) + 21-26(6)
        elif p == '*': curr += (2 if c <= '6' else 1) * prev2
        elif c == '*': curr += (9 if p == '1' else (6 if p == '2' else 0)) * prev2
        else:
            two = int(p + c)
            if 10 <= two <= 26: curr += prev2
        prev2, prev1 = prev1, curr % MOD
    return prev1 % MOD

print(num_decodings_ii('*'))   # 9
print(num_decodings_ii('1*'))  # 18

ความเชื่อมโยงกับลำดับฟีโบนัชชี

ทั้งการถอดรหัสวิธีต่าง ๆ และการปีนบันไดล้วนเป็นปัญหาใน ตระกูลฟีโบนัชชีที่แฝงอยู่ หาก DP ใดมี dp[i] ขึ้นอยู่กับเพียง dp[i-1] และ dp[i-2] ปัญหานั้นจะมีรูปแบบฟีโบนัชชีและแก้ได้ด้วยหน่วยความจำ O(1) การตรวจสอบความถูกต้อง (เช่น ตัวเลขศูนย์และขนาดการก้าว) จะเปลี่ยนว่าการเปลี่ยนสถานะใดใช้งานได้ แต่ไม่เปลี่ยนโครงสร้างพื้นฐานที่ย้อนดูสองตำแหน่งก่อนหน้า การจดจำตระกูลนี้ได้ทันทีเป็นรูปแบบที่มีประโยชน์อย่างมากในการเพิ่มความเร็วระหว่างการสัมภาษณ์งาน

# Fibonacci family: dp[i] = f(dp[i-1], dp[i-2])
# Fibonacci itself:        dp[i] = dp[i-1] + dp[i-2]
# Climbing stairs:         dp[i] = dp[i-1] + dp[i-2]
# Decode ways:             dp[i] = (dp[i-1] if one_valid) + (dp[i-2] if two_valid)
# Min cost stairs:         dp[i] = cost[i] + min(dp[i-1], dp[i-2])
# House robber:            dp[i] = max(dp[i-1], nums[i] + dp[i-2])

# All solved with 2 rolling variables:
prev2, prev1 = 0, 1
for _ in range(10):
    prev2, prev1 = prev1, prev1 + prev2
print('Fibonacci F(10):', prev1)  # 89

การนับเส้นทางบนตาราง

ปัญหาการนับที่เกี่ยวข้องกันคือ เมื่อกำหนดตารางขนาด m×n จะมีเส้นทางที่ไม่ซ้ำกันจากมุมซ้ายบนไปยังมุมขวาล่างกี่เส้นทาง หากเคลื่อนที่ได้เฉพาะไปทางขวาหรือด้านล่าง คำตอบคือสัมประสิทธิ์ทวินาม C(m+n-2, m-1) วิธีแก้ด้วย DP จะเติมตารางสองมิติ โดยมี dp[i][j] = dp[i-1][j] + dp[i][j-1] นี่คือบันไดฟีโบนัชชีในรูปแบบสองมิติ แต่ละช่องมีค่าเป็นผลรวมของช่องด้านบนและช่องด้านซ้าย

def unique_paths(m, n):
    dp = [[1] * n for _ in range(m)]
    for i in range(1, m):
        for j in range(1, n):
            dp[i][j] = dp[i-1][j] + dp[i][j-1]
    return dp[m-1][n-1]

# Or use math for O(1) solution
import math
def unique_paths_math(m, n):
    return math.comb(m + n - 2, m - 1)

print(unique_paths(3, 7))         # 28
print(unique_paths_math(3, 7))    # 28
print(unique_paths(3, 3))         # 6

สรุปจุดพลาดในการสัมภาษณ์งาน

จุดพลาดที่พบบ่อยในการถอดรหัสวิธีต่าง ๆ ได้แก่ (1) ลืมว่าเลข 0 เดี่ยว ๆ ใช้ไม่ได้ — ตรวจสอบ s[i-1] != '0' เสมอก่อนบวก dp[i-1] (2) ใช้ two_digit <= 26 โดยไม่ตรวจสอบ two_digit >= 10 — '07' ไม่ควรถอดรหัสเป็น 'G' (3) คืนค่า dp[n-1] แทนที่จะเป็น dp[n] — ตารางเริ่มนับดัชนีที่ 1 ดังนั้น dp[n] จึงสอดคล้องกับสตริงทั้งหมด ตรวจสอบดัชนีของอาร์เรย์ให้ถี่ถ้วนเสมอเมื่อ ตาราง DP ของคุณมีสมาชิกมากกว่าข้อมูลนำเข้าอยู่หนึ่งรายการ

# Common bug: checking two_digit <= 26 without >= 10
def buggy_decode(s):
    dp = [0] * (len(s) + 1)
    dp[0] = dp[1] = 1
    for i in range(2, len(s) + 1):
        if s[i-1] != '0': dp[i] += dp[i-1]
        two = int(s[i-2:i])
        # BUG: '07' gives two=7, and 7 <= 26 would add dp[i-2]
        # Fix: require two >= 10
        if 10 <= two <= 26: dp[i] += dp[i-2]  # CORRECT
    return dp[len(s)]

print(buggy_decode('06'))   # 0 (correct, '0' alone invalid)
print(buggy_decode('07'))   # 0 (correct, '07' not valid, '0' alone invalid)
print(buggy_decode('27'))   # 1 (only 'BG', 27>26 so no two-digit)

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

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

สรุปบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้ว่า การถอดรหัสวิธีต่าง ๆ ใช้ความสัมพันธ์เวียนเกิดคล้ายฟีโบนัชชี โดยมีเงื่อนไขตรวจสอบความถูกต้องสำหรับการถอดรหัสเลขหลักเดียว (ต้องไม่เป็นศูนย์) และเลขสองหลัก (10-26) การปีนบันไดและการปีนบันไดด้วยต้นทุนต่ำสุดเป็นรูปแบบเฉพาะของฟีโบนัชชีที่แก้ได้ด้วยหน่วยความจำ O(1) และ การจดจำตระกูลฟีโบนัชชีที่ย้อนดูสองตำแหน่งก่อนหน้าช่วยประหยัดเวลาได้มากระหว่างการสัมภาษณ์งาน บทถัดไปเราจะศึกษา DP สองมิติด้วยเส้นทางที่ไม่ซ้ำกันและผลรวมเส้นทางต่ำสุดบนตาราง

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

บทเรียน “ถอดรหัสวิธีและการนับเส้นทาง” ฟรีหรือไม่

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

คุณจะเรียนรู้อะไรในบทเรียน “ถอดรหัสวิธีและการนับเส้นทาง”

แก้โจทย์วิธีถอดรหัส (การจับคู่ตัวเลขกับตัวอักษร) ด้วย DP ลักษณะคล้ายฟีโบนัชชี แล้วนับเส้นทางบนบันไดที่มีจำนวนก้าวแปรผัน คุณปฏิบัติ 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. โจรปล้นบ้าน: ความสัมพันธ์เวียนเกิดแบบเลือกหรือข้าม
  2. ช่วงย่อยผลรวมสูงสุดและช่วงย่อยผลคูณสูงสุด
  3. การแบ่งคำและการแบ่งสตริงเป็นส่วน
  4. ถอดรหัสวิธีและการนับเส้นทาง
← กลับไปที่ Coding Interview Prep