Giải quyết bài toán Mảng con lớn nhất và Chuỗi con chung dài nhất bằng Quy hoạch động

Bài toán Mảng con có tổng lớn nhất

Phân tích bài toán

Đây là một bài toán kinh điển sử dụng Quy hoạch động (Dynamic Programming). Ý tưởng cốt lõi là tìm hiểu xem tại mỗi vị trí trong mảng, liệu ta nên tiếp tục mở rộng mảng con hiện tại hay bắt đầu một mảng con mới.

Xây dựng lời giải

Bước 1: Định nghĩa trạng thái

Gọi dp[i] là tổng lớn nhất của các mảng con kết thúc tại chỉ số i. Với mỗi phần tử, ta có hai lựa chọn:

  • Ghép nó vào mảng con trước đó
  • Bắt đầu một mảng con mới từ chính nó

Bước 2: Công thức chuyển trạng thái

Tại mỗi vị trí, ta chọn phương án tốt hơn giữa việc nối vào mảng trước đó hoặc bắt đầu mới:

dp[i] = max(nums[i], nums[i] + dp[i-1])

Bước 3: Điều kiện ban đầu

dp[0] = nums[0], vì mảng con duy nhất kết thúc tại vị trí 0 chính là phần tử đầu tiên.

Bước 4: Tối ưu không gian

Vì dp[i] chỉ phụ thuộc vào dp[i-1], ta không cần lưu toàn bộ mảng dp mà chỉ cần một biến lưu giá trị trước đó.

Triển khai mã nguồn

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        if not nums:
            return 0
        
        current_sum = nums[0]
        max_sum = nums[0]
        
        for i in range(1, len(nums)):
            # Chọn giữa việc mở rộng mảng hiện tại hoặc bắt đầu mới
            current_sum = max(nums[i], current_sum + nums[i])
            # Cập nhật tổng lớn nhất
            max_sum = max(max_sum, current_sum)
        
        return max_sum

Bài toán Chuỗi con chung dài nhất (Longest Common Subsequence - LCS)

Phân tích bài toán

Bài toán yêu cầu tìm độ dài của chuỗi con chung dài nhất giữa hai chuỗi. Đây cũng là ứng dụng điển hình của Quy hoạch động trên mảng hai chiều.

Xây dựng lời giải

Bước 1: Định nghĩa trạng thái

dp[i][j] biểu diễn độ dài chuỗi con chung dài nhất của tiền tố text1[0:i] và text2[0:j].

Bước 2: Công thức chuyển trạng thái

Xét hai ký tự tại vị trí i-1 và j-1:

  • Nếu text1[i-1] == text2[j-1]: Tìm thấy một ký tự chung, cộng thêm 1 vào kết quả của các tiền tố ngắn hơn:
    dp[i][j] = dp[i-1][j-1] + 1
  • Nếu text1[i-1] != text2[j-1]: Bỏ qua một ký tự từ một trong hai chuỗi:
    dp[i][j] = max(dp[i-1][j], dp[i][j-1])

Bước 3: Điều kiện ban đầu

Hàng 0 và cột 0 đại diện cho chuỗi rỗng, do đó dp[0][j] = 0 và dp[i][0] = 0 với mọi i, j.

Bước 4: Thứ tự tính toán

Duyệt từ trên xuống dưới, trái sang phải để đảm bảo các trạng thái phụ thuộc đã được tính toán.

Triển khai mã nguồn

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        len1, len2 = len(text1), len(text2)
        
        # Tạo bảng dp với kích thước (len1+1) x (len2+1)
        # Hàng và cột đầu tiên đại diện cho chuỗi rỗng
        lcs_table = [[0] * (len2 + 1) for _ in range(len1 + 1)]
        
        for row in range(1, len1 + 1):
            for col in range(1, len2 + 1):
                if text1[row - 1] == text2[col - 1]:
                    # Ký tự trùng khớp, mở rộng chuỗi con
                    lcs_table[row][col] = lcs_table[row - 1][col - 1] + 1
                else:
                    # Chọn kết quả tốt hơn từ việc bỏ qua một ký tự
                    lcs_table[row][col] = max(
                        lcs_table[row - 1][col], 
                        lcs_table[row][col - 1]
                    )
        
        return lcs_table[len1][len2]

Thẻ: Dynamic Programming LeetCode algorithm python array

Đăng vào ngày 9 tháng 10 lúc 05:09