เส้นทางไม่ซ้ำและผลรวมเส้นทางต่ำสุดบนกริด
เติมตาราง DP สองมิติสำหรับเส้นทางไม่ซ้ำทั้งแบบมีและไม่มีสิ่งกีดขวาง แล้วปรับใช้เพื่อทำให้ผลรวมค่าตลอดเส้นทางต่ำที่สุด
เส้นทางไม่ซ้ำและผลรวมเส้นทางต่ำสุดบนกริด เป็นบทเรียน DSA Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน DSA Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
เส้นทางที่ไม่ซ้ำกันบนตาราง
เส้นทางที่ไม่ซ้ำกัน (LeetCode 62) ถามว่า ในตารางขนาด m×n จะมีเส้นทางที่แตกต่างกันจากมุมซ้ายบนไปยังมุมขวาล่างกี่เส้นทาง หากเคลื่อนที่ได้เฉพาะ ไปทางขวา หรือ ลงด้านล่าง สำหรับตารางขนาด 3×7 คำตอบคือ 28 แนวคิดสำคัญคือ เส้นทางทุกเส้นทางที่ไปยังช่อง (i,j) ต้องมาจาก (i-1,j) (ด้านบน) หรือ (i,j-1) (ด้านซ้าย) จึงกำหนดสูตร DP สองมิติได้อย่างเป็นธรรมชาติ
# 3x7 grid: robot starts at (0,0), goes to (2,6)
# Must make exactly 2 down-moves and 6 right-moves
# Total moves = 8, choose 2 for down = C(8,2) = 28
import math
print('Unique paths 3x7:', math.comb(3+7-2, 3-1)) # 28
print('Unique paths 3x3:', math.comb(3+3-2, 3-1)) # 6
print('Unique paths 2x2:', math.comb(2+2-2, 2-1)) # 2ตาราง DP สองมิติสำหรับเส้นทางที่ไม่ซ้ำกัน
กำหนดให้ dp[i][j] = จำนวนเส้นทางไปยังช่อง (i,j) แถวแรกและคอลัมน์แรกมีค่าเป็น 1 ทั้งหมด (มีเพียงหนึ่งวิธีที่จะไปยังช่องใด ๆ ในแถวบนสุดหรือคอลัมน์ซ้ายสุด) สำหรับช่องอื่น ๆ: dp[i][j] = dp[i-1][j] + dp[i][j-1] เติมตารางทีละแถว และคำตอบคือ dp[m-1][n-1] ความซับซ้อนด้านเวลาคือ O(m×n) และหน่วยความจำคือ O(m×n) ซึ่งลดลงเป็น O(n) ได้
def unique_paths(m, n):
dp = [[1] * n for _ in range(m)]
# First row and column stay as 1s (base cases)
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]
print(unique_paths(3, 7)) # 28
print(unique_paths(3, 3)) # 6
print(unique_paths(1, 1)) # 1 (already at destination)การลดหน่วยความจำเป็น O(n)
เนื่องจาก dp[i][j] ขึ้นอยู่กับเพียงแถวปัจจุบันและแถวก่อนหน้า คุณจึงแทนที่ตารางสองมิติเต็มรูปแบบด้วยอาร์เรย์หนึ่งมิติได้ กำหนดค่าเริ่มต้นทั้งหมดเป็น 1 จากนั้นสำหรับแต่ละแถว ให้อัปเดตค่าภายในอาร์เรย์เดิม: dp[j] += dp[j-1] หลังประมวลผลแถว i แล้ว dp[j] จะเก็บค่าที่เดิมเป็น dp[i][j] ในตารางสองมิติ นี่เป็นรูปแบบการปรับให้เหมาะสมที่ใช้บ่อยสำหรับปัญหา DP สองมิติ
def unique_paths_1d(m, n):
dp = [1] * n # initial row: all 1s
for i in range(1, m):
for j in range(1, n):
dp[j] += dp[j-1] # dp[j] was dp[i-1][j], dp[j-1] is dp[i][j-1]
return dp[n-1]
print(unique_paths_1d(3, 7)) # 28
print(unique_paths_1d(3, 3)) # 6
# Or use math for O(1)
import math
print(math.comb(3+7-2, 3-1)) # 28เส้นทางที่ไม่ซ้ำกัน II: สิ่งกีดขวาง
เส้นทางที่ไม่ซ้ำกัน II (LeetCode 63) เพิ่มสิ่งกีดขวาง (ช่องที่มีค่า 1) ลงในตาราง เส้นทางใดก็ตามที่ผ่านสิ่งกีดขวางถือว่าใช้ไม่ได้ ดังนั้น dp[i][j] = 0 หาก obstacle[i][j] == 1 มิฉะนั้นความสัมพันธ์เวียนเกิดยังคงเหมือนเดิม: dp[i][j] = dp[i-1][j] + dp[i][j-1] หากจุดเริ่มต้นหรือจุดสิ้นสุดถูกกีดขวาง คำตอบจะเป็น 0 ทันที กำหนดกรณีฐานอย่างระมัดระวัง — เมื่อพบ 1 ในแถวแรกหรือคอลัมน์แรก ช่องถัดไปทั้งหมดในแถวหรือคอลัมน์นั้นจะมีค่าเป็น 0
def unique_paths_with_obstacles(obstacle_grid):
m, n = len(obstacle_grid), len(obstacle_grid[0])
dp = [[0] * n for _ in range(m)]
# First row
for j in range(n):
if obstacle_grid[0][j] == 1: break
dp[0][j] = 1
# First column
for i in range(m):
if obstacle_grid[i][0] == 1: break
dp[i][0] = 1
for i in range(1, m):
for j in range(1, n):
if obstacle_grid[i][j] == 0:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[m-1][n-1]
grid = [[0,0,0],[0,1,0],[0,0,0]]
print(unique_paths_with_obstacles(grid)) # 2ปัญหาผลรวมเส้นทางต่ำสุด
ผลรวมเส้นทางต่ำสุด (LeetCode 64) ถามว่า เมื่อกำหนดตารางขนาด m×n ที่เต็มไปด้วยจำนวนเต็มไม่ติดลบ ให้หาเส้นทางจากมุมซ้ายบนไปยังมุมขวาล่างที่ทำให้ผลรวมของตัวเลขทั้งหมดตามเส้นทางมีค่าต่ำที่สุด (เคลื่อนที่ได้เฉพาะไปทางขวาหรือด้านล่าง) ตัวอย่างเช่น ใน [[1,3,1],[1,5,1],[4,2,1]] เส้นทาง 1→3→1→1→1 ให้ผลรวมเป็น 7 สถานะ DP เหมือนกับปัญหาเส้นทางที่ไม่ซ้ำกัน แต่ความสัมพันธ์เวียนเกิดเปลี่ยนจากการบวกเป็นการหาค่าต่ำสุด
grid = [[1, 3, 1],
[1, 5, 1],
[4, 2, 1]]
# Optimal path: (0,0)→(0,1)→(0,2)→(1,2)→(2,2)
# Values: 1 + 3 + 1 + 1 + 1 = 7
print('Expected minimum path sum:', 7)การใช้งาน DP สำหรับผลรวมเส้นทางต่ำสุด
กำหนดให้ dp[i][j] = ต้นทุนต่ำสุดในการไปถึงช่อง (i,j) กรณีฐานคือ dp[0][0] = grid[0][0] แถวแรก: dp[0][j] = dp[0][j-1] + grid[0][j] (มีทางเดียวคือมาจากด้านซ้าย) คอลัมน์แรก: dp[i][0] = dp[i-1][0] + grid[i][0] (มีทางเดียวคือมาจากด้านบน) กรณีทั่วไป: dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1]) นี่คือการนำหลักการความเหมาะสมที่สุดมาใช้โดยตรง
def min_path_sum(grid):
m, n = len(grid), len(grid[0])
dp = [[0]*n for _ in range(m)]
dp[0][0] = grid[0][0]
for j in range(1, n): # first row
dp[0][j] = dp[0][j-1] + grid[0][j]
for i in range(1, m): # first column
dp[i][0] = dp[i-1][0] + grid[i][0]
for i in range(1, m):
for j in range(1, n):
dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
return dp[m-1][n-1]
grid = [[1,3,1],[1,5,1],[4,2,1]]
print(min_path_sum(grid)) # 7ผลรวมเส้นทางต่ำสุดในข้อมูลเดิม
หากอนุญาตให้แก้ไขตารางข้อมูลนำเข้า คุณสามารถอัปเดตตารางนั้น โดยตรงในข้อมูลเดิม เพื่อไม่ต้องจัดสรรตาราง DP แยกต่างหาก วิธีนี้ลดหน่วยความจำเสริมเหลือ O(1) (นอกเหนือจากข้อมูลนำเข้า) ผู้สัมภาษณ์มักถามถึงการปรับให้เหมาะสมนี้ — ตรวจสอบให้ชัดเจนก่อนว่าการแก้ไขข้อมูลนำเข้าได้รับอนุญาตหรือไม่ หากไม่ได้รับอนุญาต เทคนิคอาร์เรย์เลื่อนหนึ่งมิติจะให้หน่วยความจำ O(n) โดยไม่แก้ไขข้อมูลนำเข้า
def min_path_sum_inplace(grid):
m, n = len(grid), len(grid[0])
# Mutate in place
for i in range(m):
for j in range(n):
if i == 0 and j == 0: continue
if i == 0:
grid[i][j] += grid[i][j-1]
elif j == 0:
grid[i][j] += grid[i-1][j]
else:
grid[i][j] += min(grid[i-1][j], grid[i][j-1])
return grid[m-1][n-1]
import copy
grid = [[1,3,1],[1,5,1],[4,2,1]]
print(min_path_sum_inplace(copy.deepcopy(grid))) # 7ผลรวมเส้นทางต่ำสุดในสามเหลี่ยม
สามเหลี่ยม (LeetCode 120) ขอให้หาผลรวมเส้นทางต่ำสุดจากด้านบนลงด้านล่างของอาร์เรย์รูปสามเหลี่ยม โดยแต่ละขั้นจะไปยังตัวเลขที่อยู่ติดกันในแถวด้านล่าง การใช้ DP จากล่างขึ้นบนเป็นวิธีที่สะอาดที่สุด โดยเริ่มจากแถวรองสุดท้าย แล้วสำหรับแต่ละช่องให้บวกค่าต่ำสุดของสองช่องที่อยู่ด้านล่างโดยตรง วิธีนี้ไม่ต้องติดตามดัชนีเริ่มต้น และทำให้คำตอบเลื่อนขึ้นไปยังยอดสามเหลี่ยมได้อย่างเป็นธรรมชาติ
def minimum_total(triangle):
# Bottom-up: start from second-to-last row
dp = triangle[-1][:] # copy of bottom row
for row in range(len(triangle) - 2, -1, -1):
for col in range(len(triangle[row])):
dp[col] = triangle[row][col] + min(dp[col], dp[col+1])
return dp[0]
triangle = [
[2],
[3, 4],
[6, 5, 7],
[4, 1, 8, 3]
]
print(minimum_total(triangle)) # 11 (2+3+5+1)DP บนตารางของดันเจียน
เกมดันเจียน (LeetCode 174) ถามหาพลังชีวิตเริ่มต้นต่ำสุดที่จำเป็นต่อการช่วยเจ้าหญิงซึ่งอยู่ที่มุมขวาล่างของตารางที่มีช่องสร้างความเสียหาย (ค่าลบ) และช่องฟื้นพลัง (ค่าบวก) คุณต้องเคลื่อนที่ไปทางขวาหรือด้านล่าง เคล็ดลับคือเติมตาราง DP ย้อนกลับ (จากมุมขวาล่างไปยังมุมซ้ายบน) โดยคำนวณพลังชีวิตต่ำสุดที่จำเป็นในแต่ละช่อง สำหรับแต่ละช่อง: dp[i][j] = max(1, min(dp[i+1][j], dp[i][j+1]) - dungeon[i][j]) พลังชีวิตต้องมีค่าอย่างน้อย 1 อยู่เสมอ
def calculate_minimum_hp(dungeon):
m, n = len(dungeon), len(dungeon[0])
dp = [[0]*n for _ in range(m)]
# Fill from bottom-right
dp[m-1][n-1] = max(1, 1 - dungeon[m-1][n-1])
for i in range(m-2, -1, -1): # last column
dp[i][n-1] = max(1, dp[i+1][n-1] - dungeon[i][n-1])
for j in range(n-2, -1, -1): # last row
dp[m-1][j] = max(1, dp[m-1][j+1] - dungeon[m-1][j])
for i in range(m-2, -1, -1):
for j in range(n-2, -1, -1):
need = min(dp[i+1][j], dp[i][j+1])
dp[i][j] = max(1, need - dungeon[i][j])
return dp[0][0]
dungeon = [[-2,-3,3],[-5,-10,1],[10,30,-5]]
print(calculate_minimum_hp(dungeon)) # 7การเปรียบเทียบปัญหา DP บนตาราง
ปัญหา DP บนตารางมีโครงสร้างเหมือนกัน แต่แตกต่างกันที่ ทิศทางการเติมตาราง และ การดำเนินการเปลี่ยนสถานะ: เส้นทางที่ไม่ซ้ำกันใช้การบวก (นับทุกวิธี) ผลรวมเส้นทางต่ำสุดใช้การหาค่าต่ำสุด (หาต้นทุนเส้นทางที่เหมาะสมที่สุด) เกมดันเจียนเติมตารางย้อนกลับ (คำนวณพลังชีวิตที่จำเป็นจากอนาคต) เมื่อพบปัญหา DP บนตารางใหม่ ให้ถามตนเองว่า (1) แต่ละช่องแทนอะไร (2) ควรเติมตารางไปในทิศทางใด (3) ใช้การดำเนินการใดรวมค่าจากช่องข้างเคียง คำตอบของคำถามทั้งสามข้อนี้จะเผยให้เห็นวิธีแก้ทั้งหมด
# Summary: Grid DP Patterns
#
# Problem Fill Dir Transition
# Unique Paths top-left dp[i][j] = dp[i-1][j] + dp[i][j-1]
# Unique Paths II top-left same but 0 if obstacle
# Min Path Sum top-left dp[i][j] = grid[i][j] + min(above, left)
# Triangle bottom-up dp[col] = row[col] + min(dp[col], dp[col+1])
# Dungeon bottom-right max(1, min(right, down) - cell)
# Recognise the pattern, write the transition, verify with examples
print('Grid DP summary complete')สรุปความซับซ้อนสำหรับ DP บนตาราง
ปัญหา DP บนตารางทั้งหมดในที่นี้ใช้เวลา O(m×n) หน่วยความจำมีตั้งแต่ O(m×n) สำหรับตารางเต็มรูปแบบ ลดลงเป็น O(n) ด้วยอาร์เรย์เลื่อนหนึ่งมิติ และเป็น O(1) สำหรับหน่วยความจำเสริมเมื่อสามารถแก้ไขตารางโดยตรงในข้อมูลเดิมได้ ในการสัมภาษณ์งาน ให้กล่าวถึงการลดหน่วยความจำเป็น O(n) หลังนำเสนอวิธีแก้ O(m×n) — สิ่งนี้แสดงให้เห็นว่าคุณตระหนักถึงข้อแลกเปลี่ยนต่าง ๆ สำหรับทุกปัญหา ควรพิจารณาด้วยว่ามีทางลัดแบบละโมบหรือไม่ (เช่น สูตรทางคณิตศาสตร์สำหรับเส้นทางที่ไม่ซ้ำกัน)
# O(n) space version of Min Path Sum
def min_path_sum_1d(grid):
m, n = len(grid), len(grid[0])
dp = [float('inf')] * n
dp[0] = 0
for i in range(m):
dp[0] += grid[i][0] # first column: only from above
for j in range(1, n):
dp[j] = grid[i][j] + min(dp[j], dp[j-1])
return dp[n-1]
grid = [[1,3,1],[1,5,1],[4,2,1]]
print(min_path_sum_1d(grid)) # 7ตรวจสอบความเข้าใจ
ทดสอบความเข้าใจแนวคิดโครงสร้างข้อมูลและอัลกอริทึม — การเตรียมตัวสัมภาษณ์งานเขียนโปรแกรมจากบทเรียนนี้
สรุปบทเรียน
ในบทเรียนนี้ คุณได้เรียนรู้ว่า เส้นทางที่ไม่ซ้ำกันเติมตารางสองมิติด้วย dp[i][j] = dp[i-1][j] + dp[i][j-1] และสามารถคำนวณด้วย O(1) โดยใช้การจัดเชิงการนับ ผลรวมเส้นทางต่ำสุดใช้โครงสร้างเดียวกัน แต่แทนที่การบวกด้วย min เพื่อหาต้นทุนเส้นทางที่เหมาะสมที่สุด และ ปัญหา DP บนตารางทั้งหมดมีรูปแบบร่วมกันคือการกำหนดสถานะให้แต่ละช่องและเลือกตัวดำเนินการเปลี่ยนสถานะ (ผลรวม ค่าต่ำสุด ค่าสูงสุด) บทถัดไปเราจะศึกษา LCS โดยใช้ DP สองมิติกับลำดับสองชุด
คำถามที่พบบ่อย
บทเรียน “เส้นทางไม่ซ้ำและผลรวมเส้นทางต่ำสุดบนกริด” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “เส้นทางไม่ซ้ำและผลรวมเส้นทางต่ำสุดบนกริด” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส DSA Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “เส้นทางไม่ซ้ำและผลรวมเส้นทางต่ำสุดบนกริด”
เติมตาราง DP สองมิติสำหรับเส้นทางไม่ซ้ำทั้งแบบมีและไม่มีสิ่งกีดขวาง แล้วปรับใช้เพื่อทำให้ผลรวมค่าตลอดเส้นทางต่ำที่สุด คุณปฏิบัติ DSA Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน DSA Interview Prep หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน DSA Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน
บทเรียน “เส้นทางไม่ซ้ำและผลรวมเส้นทางต่ำสุดบนกริด” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน DSA Interview Prep นี้ได้ไหม
ได้ บทเรียน DSA Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- เส้นทางไม่ซ้ำและผลรวมเส้นทางต่ำสุดบนกริด
- ลำดับร่วมที่ยาวที่สุด
- ระยะห่างการแก้ไข (Levenshtein)
- การเพิ่มประสิทธิภาพพื้นที่สำหรับ DP สองมิติ