1. Giới thiệu về Quy hoạch động tuyến tính

Quy hoạch động tuyến tính: Phương pháp quy hoạch động tuyến tính là phương pháp quy hoạch động mà các giai đoạn được chia thành “tuyến tính” (được gọi là “DP tuyến tính”).

Nếu trạng thái có nhiều chiều, nhưng mỗi chiều đều được chia thành các giai đoạn tuyến tính, cũng được coi là DP tuyến tính. Ví dụ như bài toán cái túi, DP khoảng cách, DP số chữ số, v.v. đều thuộc loại DP tuyến tính.

Có nhiều cách để chia các bài toán DP tuyến tính.

  • Nếu phân loại theo “số chiều của trạng thái”, chúng ta có thể chia các bài toán DP tuyến tính thành: bài toán DP tuyến tính một chiều, bài toán DP tuyến tính hai chiều và bài toán DP tuyến tính nhiều chiều.
  • Nếu phân loại theo “định dạng đầu vào của bài toán”, chúng ta có thể chia các bài toán DP tuyến tính thành: bài toán DP tuyến tính đơn chuỗi, bài toán DP tuyến tính hai chuỗi, bài toán DP tuyến tính ma trận và bài toán DP tuyến tính không có chuỗi.

Trong bài viết này, chúng ta sẽ phân loại các bài toán DP tuyến tính theo định dạng đầu vào của chúng và giải thích từng loại bài toán.

2. Vấn đề DP tuyến tính đơn chuỗi

Vấn đề DP tuyến tính đơn chuỗi: Vấn đề có đầu vào là một mảng hoặc một chuỗi duy nhất trong DP tuyến tính. Trạng thái thường được định nghĩa là , biểu thị:

  1. Các giải pháp liên quan đến “mảng con kết thúc tại vị trí thứ trong mảng ()“.
  2. Các giải pháp liên quan đến “mảng con kết thúc tại vị trí thứ trong mảng ()“.
  3. Các giải pháp liên quan đến “mảng con bao gồm phần tử đầu tiên trong mảng ()“.

Sự khác biệt giữa 3 cách định nghĩa trạng thái này là một phần tử .

  1. Trạng thái thứ nhất: Độ dài của mảng con là , mảng con không thể rỗng.
  2. Trạng thái thứ hai và trạng thái thứ ba: Hai trạng thái này có cùng mô tả. Độ dài của mảng con là , mảng con có thể rỗng. Khi , trạng thái này được sử dụng để biểu thị mảng rỗng (bao gồm phần tử đầu tiên trong mảng).

2.1 Dãy con tăng dài nhất

Vấn đề DP tuyến tính đơn chuỗi phổ biến nhất trong DP tuyến tính là “Dãy con tăng dài nhất” (Longest Increasing Subsequence - LIS).

2.1.1 Liên kết vấn đề

2.1.2 Tóm tắt vấn đề

Mô tả: Cho một mảng số nguyên .

Yêu cầu: Tìm độ dài của dãy con tăng dài nhất trong mảng.

Giải thích:

  • Dãy con: Một dãy con được tạo ra bằng cách xóa (hoặc không xóa) các phần tử trong mảng mà không làm thay đổi thứ tự các phần tử còn lại. Ví dụ, là một dãy con của mảng .
  • .
  • .

Ví dụ:

  • Ví dụ 1:
Input: nums = [10,9,2,5,3,7,101,18]
Output: 4
Explanation: Dãy con tăng dài nhất là [2,3,7,101], nên độ dài là 4.
  • Ví dụ 2:
Input: nums = [0,1,0,3,2,3]
Output: 4

2.1.3 Ý tưởng giải quyết

Ý tưởng 1: Quy hoạch động
1. Phân chia giai đoạn

Phân chia giai đoạn dựa trên vị trí kết thúc của dãy con.

2. Định nghĩa trạng thái

Định nghĩa trạng thái biểu thị: độ dài của dãy con tăng dài nhất kết thúc tại vị trí thứ trong mảng.

3. Công thức chuyển tiếp trạng thái

Nếu một số nhỏ hơn xuất hiện sau một số lớn hơn, nó sẽ tạo thành một dãy con tăng dài hơn.

Với các phần tử thỏa mãn :

  • Nếu , thì có thể được thêm vào sau , trong trường hợp này, độ dài của dãy con tăng dài nhất kết thúc tại vị trí thứ sẽ là độ dài của dãy con tăng dài nhất kết thúc tại vị trí thứ cộng thêm , tức là .
  • Nếu , thì không thể được thêm vào sau , nên ta có thể bỏ qua.

Tổng kết lại, công thức chuyển tiếp trạng thái là: .

4. Điều kiện ban đầu

Mặc định, mỗi phần tử trong mảng được coi là một dãy con tăng dài nhất có độ dài là , tức là .

5. Kết quả cuối cùng

Dựa trên định nghĩa trạng thái, biểu thị: độ dài của dãy con tăng dài nhất kết thúc tại vị trí thứ trong mảng. Để tính toán độ dài dãy con tăng dài nhất lớn nhất, ta cần duyệt qua mảng và tìm giá trị lớn nhất.

Ý tưởng 1: Code quy hoạch động
class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        size = len(nums)
        dp = [1 for _ in range(size)]
 
        for i in range(size):
            for j in range(i):
                if nums[i] > nums[j]:
                    dp[i] = max(dp[i], dp[j] + 1)
        
        return max(dp)
Ý tưởng 1: Phân tích độ phức tạp
  • Độ phức tạp thời gian: . Vòng lặp lồng nhau có độ phức tạp , và việc tìm giá trị lớn nhất có độ phức tạp , vì vậy tổng thời gian phức tạp là .
  • Độ phức tạp không gian: . Sử dụng mảng một chiều để lưu trạng thái, vì vậy không gian phức tạp là .

2.2 Tổng con tăng lớn nhất

Trong các vấn đề DP tuyến tính một chuỗi, ngoài các vấn đề liên quan đến dãy con, còn có các vấn đề liên quan đến mảng con.

Lưu ý:

  • Dãy con: Là một dãy được tạo ra bằng cách xóa (hoặc không xóa) các phần tử trong mảng mà không làm thay đổi thứ tự các phần tử còn lại.
  • Mảng con: Là một dãy con liên tiếp trong mảng.

2.2.1 Liên kết vấn đề

2.2.2 Tóm tắt vấn đề

Mô tả: Cho một mảng số nguyên .

Yêu cầu: Tìm tổng lớn nhất của một mảng con liên tiếp trong mảng.

Giải thích:

  • Mảng con: Là một dãy con liên tiếp trong mảng.
  • .
  • .

Ví dụ:

  • Ví dụ 1:
Input: nums = [-2,1,-3,4,-1,2,1,-5,4]
Output: 6
Explanation: Mảng con liên tiếp [4,-1,2,1] có tổng lớn nhất là 6.
  • Ví dụ 2:
Input: nums = [1]
Output: 1

2.2.3 Ý tưởng giải quyết

Ý tưởng 1: Quy hoạch động
1. Phân chia giai đoạn

Phân chia giai đoạn dựa trên vị trí kết thúc của mảng con liên tiếp.

2. Định nghĩa trạng thái

Định nghĩa trạng thái là tổng lớn nhất của một mảng con liên tiếp kết thúc tại vị trí thứ trong mảng.

3. Công thức chuyển tiếp trạng thái

Trạng thái là tổng lớn nhất của một mảng con liên tiếp kết thúc tại vị trí thứ trong mảng. Vì vậy, chúng ta có thể thảo luận về dựa trên “tổng lớn nhất của mảng con liên tiếp kết thúc tại vị trí thứ ” và “giá trị hiện tại của phần tử “.

  • Nếu , tức là “tổng lớn nhất của mảng con liên tiếp kết thúc tại vị trí thứ ” + “giá trị hiện tại của phần tử ” < “giá trị hiện tại của phần tử ”, tức là . Vì vậy, lúc này sẽ bằng “giá trị hiện tại của phần tử ”, tức là .
  • Nếu , tức là “tổng lớn nhất của mảng con liên tiếp kết thúc tại vị trí thứ ” + “giá trị hiện tại của phần tử ” >= “giá trị hiện tại của phần tử ”, tức là . Vì vậy, lúc này sẽ bằng “tổng lớn nhất của mảng con liên tiếp kết thúc tại vị trí thứ ” + “giá trị hiện tại của phần tử ”, tức là .

Tổng kết lại, công thức chuyển tiếp trạng thái là:

4. Điều kiện ban đầu
  • Tổng lớn nhất của mảng con kết thúc tại vị trí thứ , tức là .
5. Kết quả cuối cùng

Dựa trên định nghĩa trạng thái, là tổng lớn nhất của một mảng con liên tiếp kết thúc tại vị trí thứ trong mảng. Vì vậy, kết quả cuối cùng sẽ là giá trị lớn nhất trong tất cả các , tức là .

Ý tưởng 1: Code quy hoạch động
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        size = len(nums)
        dp = [0 for _ in range(size)]
 
        dp[0] = nums[0]
        for i in range(1, size):
            if dp[i - 1] < 0:
                dp[i] = nums[i]
            else:
                dp[i] = dp[i - 1] + nums[i]
        return max(dp)
Ý tưởng 1: Phân tích độ phức tạp
  • Độ phức tạp thời gian: , trong đó là số lượng phần tử trong mảng .
  • Độ phức tạp không gian: .
Ý tưởng 2: Quy hoạch động + Tối ưu cuộn

chỉ phụ thuộc vào và phần tử hiện tại , chúng ta cũng có thể sử dụng một biến để biểu diễn tổng lớn nhất của một mảng con liên tiếp kết thúc tại vị trí thứ . Sau đó, chúng ta sử dụng biến để lưu giữ giá trị lớn nhất toàn cục.

Ý tưởng 2: Code quy hoạch động
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        size = len(nums)
        subMax = nums[0]
        ansMax = nums[0]
 
        for i in range(1, size):
            if subMax < 0:
                subMax = nums[i]
            else:
                subMax += nums[i]
            ansMax = max(ansMax, subMax)
        return ansMax
Ý tưởng 2: Phân tích độ phức tạp
  • Độ phức tạp thời gian: , trong đó là số lượng phần tử trong mảng .
  • Độ phức tạp không gian: .

2.3 Độ dài dãy con Fibonacci dài nhất

Một số vấn đề DP tuyến tính một chuỗi đòi hỏi xem xét hai vị trí kết thúc, và không thể mô tả rõ ràng vấn đề chỉ với một vị trí kết thúc. Trong trường hợp này, chúng ta cần thêm một chiều kết thúc để định nghĩa trạng thái.

2.3.1 Liên kết vấn đề

2.3.2 Tóm tắt vấn đề

Mô tả: Cho một mảng số nguyên được sắp xếp tăng dần.

Yêu cầu: Tìm độ dài dãy con Fibonacci dài nhất trong mảng. Nếu không có dãy con Fibonacci, trả về 0.

Giải thích:

  • Dãy con Fibonacci: Nếu dãy thỏa mãn:

    • ;
    • Với mọi , ta có .

    thì gọi dãy đó là dãy con Fibonacci.

  • Dãy con Fibonacci: Là một dãy con liên tiếp trong mảng thỏa mãn dãy con Fibonacci. Ví dụ: . Dãy con Fibonacci của .

  • .

  • .

Ví dụ:

  • Ví dụ 1:
Input: arr = [1,2,3,4,5,6,7,8]
Output: 5
Explanation: Dãy con Fibonacci dài nhất là [1,2,3,5,8].
  • Ví dụ 2:
Input: arr = [1,3,7,11,12,14,18]
Output: 3
Explanation: Có 3 dãy con Fibonacci dài nhất là [1,11,12], [3,11,14] và [7,11,18].

2.3.3 Ý tưởng giải quyết

Ý tưởng 1: Duyệt tất cả các cặp (vượt quá giới hạn thời gian)

Giả sử là hai phần tử trong mảng và thỏa mãn với .

Dựa trên , chúng ta có thể xác định giá trị tiếp theo trong dãy Fibonacci là .

Vì mảng đã được sắp xếp tăng dần, nếu chúng ta tìm thấy trong mảng với thì chúng ta có thể tạo ra một dãy Fibonacci dài hơn.

Để tìm dãy Fibonacci dài nhất, chúng ta sẽ duyệt qua tất cả các cặp và kiểm tra xem có thể tạo ra dãy Fibonacci dài hơn không.

Ý tưởng 1: Code
class Solution:
    def lenLongestFibSubseq(self, arr: List[int]) -> int:
        size = len(arr)
        ans = 0
        for i in range(size):
            for j in range(i + 1, size):
                temp_ans = 0
                temp_i = i
                temp_j = j
                k = j + 1
                while k < size:
                    if arr[temp_i] + arr[temp_j] == arr[k]:
                        temp_ans += 1
                        temp_i = temp_j
                        temp_j = k
                    k += 1
                if temp_ans > ans:
                    ans = temp_ans
 
        if ans > 0:
            return ans + 2
        else:
            return ans
Ý tưởng 1: Phân tích độ phức tạp
  • Độ phức tạp thời gian: , trong đó là số lượng phần tử trong mảng .
  • Độ phức tạp không gian: .
Ý tưởng 2: Sử dụng bảng băm

Với , chúng ta có thể kiểm tra xem có tồn tại trong mảng hay không bằng cách sử dụng một bảng băm. Bảng băm này sẽ lưu trữ các giá trị trong mảng và chỉ mất thời gian để kiểm tra một giá trị có tồn tại trong bảng băm hay không.

Ý tưởng 2: Code
class Solution:
    def lenLongestFibSubseq(self, arr: List[int]) -> int:
        size = len(arr)
        ans = 0
        idx_map = dict()
        for idx, value in enumerate(arr):
            idx_map[value] = idx
        
        for i in range(size):
            for j in range(i + 1, size):
                temp_ans = 0
                temp_i = i
                temp_j = j
                while arr[temp_i] + arr[temp_j] in idx_map:
                    temp_ans += 1
                    k = idx_map[arr[temp_i] + arr[temp_j]]
                    temp_i = temp_j
                    temp_j = k
 
                if temp_ans > ans:
                    ans = temp_ans
 
        if ans > 0:
            return ans + 2
        else:
            return ans
Ý tưởng 2: Phân tích độ phức tạp
  • Độ phức tạp thời gian: , trong đó là số lượng phần tử trong mảng .
  • Độ phức tạp không gian: .
Ý tưởng 3: Quy hoạch động + Bảng băm
1. Phân chia giai đoạn

Phân chia giai đoạn dựa trên hai vị trí kết thúc của dãy con Fibonacci.

2. Định nghĩa trạng thái

Định nghĩa trạng thái là độ dài dãy con Fibonacci dài nhất kết thúc tại vị trí thứ và vị trí thứ trong mảng.

3. Công thức chuyển tiếp trạng thái

Độ dài dãy con Fibonacci dài nhất kết thúc tại vị trí thứ và vị trí thứ = Tìm sao cho và cộng thêm vào độ dài dãy con Fibonacci dài nhất kết thúc tại vị trí thứ và vị trí thứ .

Công thức chuyển tiếp trạng thái: .

4. Điều kiện ban đầu

Mọi cặp phần tử liên tiếp trong mảng có thể tạo thành một dãy con Fibonacci có độ dài là , nên .

5. Kết quả cuối cùng

Dựa trên định nghĩa trạng thái, là độ dài dãy con Fibonacci dài nhất kết thúc tại vị trí thứ và vị trí thứ trong mảng. Để tính toán độ dài dãy con Fibonacci dài nhất, chúng ta sẽ duyệt qua tất cả các trạng thái và tìm giá trị lớn nhất.

Vì đề bài yêu cầu dãy con Fibonacci có độ dài ít nhất là , nên nếu giá trị lớn nhất tìm được là hoặc nhỏ hơn, chúng ta sẽ trả về .

Ý tưởng 3: Code
class Solution:
    def lenLongestFibSubseq(self, arr: List[int]) -> int:
        size = len(arr)
        
        dp = [[0 for _ in range(size)] for _ in range(size)]
        ans = 0
 
        # Khởi tạo dp
        for i in range(size):
            for j in range(i + 1, size):
                dp[i][j] = 2
 
        idx_map = {}
        # Tạo bảng băm để tra cứu giá trị trong mảng arr
        for idx, value in enumerate(arr):
            idx_map[value] = idx
 
        for i in range(size):
            for j in range(i + 1, size):
                if arr[i] + arr[j] in idx_map:    
                    # Lấy chỉ số của arr[i] + arr[j], tức là phần tử tiếp theo trong dãy Fibonacci
                    k = idx_map[arr[i] + arr[j]]
                    
                    dp[j][k] = max(dp[j][k], dp[i][j] + 1)
                    ans = max(ans, dp[j][k])
                    
 
        if ans >= 3:
            return ans
        return 0
Ý tưởng 3: Phân tích độ phức tạp
  • Độ phức tạp thời gian: , trong đó là số lượng phần tử trong mảng .
  • Độ phức tạp không gian: .

3. Vấn đề DP tuyến tính hai chuỗi

Vấn đề DP tuyến tính hai chuỗi: Vấn đề có đầu vào là hai mảng hoặc hai chuỗi duy nhất trong DP tuyến tính. Trạng thái thường được định nghĩa là , biểu thị:

  1. Các giải pháp liên quan đến “mảng con kết thúc tại vị trí thứ trong mảng thứ nhất ()” và “mảng con kết thúc tại vị trí thứ trong mảng thứ hai ()“.
  2. Các giải pháp liên quan đến “mảng con kết thúc tại vị trí thứ trong mảng thứ nhất ()” và “mảng con kết thúc tại vị trí thứ trong mảng thứ hai ()“.
  3. Các giải pháp liên quan đến “mảng con bao gồm phần tử đầu tiên trong mảng thứ nhất ()” và “mảng con bao gồm phần tử đầu tiên trong mảng thứ hai ()“.

Sự khác biệt giữa 3 cách định nghĩa trạng thái này là một phần tử hoặc .

  1. Trạng thái thứ nhất: Độ dài của mảng con là hoặc và không thể rỗng.
  2. Trạng thái thứ hai và trạng thái thứ ba: Hai trạng thái này có cùng mô tả. Độ dài của mảng con là hoặc và có thể rỗng. Khi hoặc , trạng thái này được sử dụng để biểu diễn mảng rỗng (bao gồm phần tử đầu tiên trong mảng).

3.1 Tìm dãy con chung dài nhất

Vấn đề DP tuyến tính trong hai chuỗi có vấn đề cổ điển nhất là “Tìm dãy con chung dài nhất (Longest Common Subsequence, gọi tắt là LCS)“.

3.1.1 Liên kết đề bài

3.1.2 Tóm tắt đề bài

Mô tả: Cho hai chuỗi .

Yêu cầu: Trả về độ dài của dãy con chung dài nhất của hai chuỗi. Nếu không có dãy con chung, trả về .

Giải thích:

  • Dãy con: Chuỗi mới được tạo thành bằng cách xóa một số ký tự từ chuỗi gốc mà không thay đổi thứ tự tương đối của các ký tự.
  • Dãy con chung: Dãy con mà hai chuỗi có chung.
  • .
  • chỉ chứa các ký tự tiếng Anh thường.

Ví dụ:

  • Ví dụ 1:
Input: text1 = "abcde", text2 = "ace" 
Output: 3  
Explanation: Dãy con chung dài nhất là "ace", có độ dài là 3.
  • Ví dụ 2:
Input: text1 = "abc", text2 = "abc"
Output: 3
Explanation: Dãy con chung dài nhất là "abc", có độ dài là 3.

3.1.3 Ý tưởng giải quyết

Ý tưởng 1: Quy hoạch động
1. Phân chia giai đoạn

Phân chia giai đoạn dựa trên vị trí cuối cùng của hai chuỗi.

2. Định nghĩa trạng thái

Định nghĩa trạng thái là: Độ dài của dãy con chung dài nhất của “chuỗi con được tạo thành từ ký tự đầu tiên của ” và “chuỗi con được tạo thành từ ký tự đầu tiên của ” là .

3. Công thức chuyển tiếp trạng thái

Duyệt qua hai vòng lặp để duyệt qua các ký tự của , công thức chuyển tiếp trạng thái như sau:

  1. Nếu , tức là hai chuỗi con có ký tự cuối cùng giống nhau, vậy độ dài của dãy con chung tăng lên . Tức là: .
  2. Nếu , tức là hai chuỗi con có ký tự cuối cùng khác nhau, vậy cần xem xét hai trường hợp sau và lấy trường hợp lớn nhất: .
    1. Độ dài của dãy con chung của “chuỗi con được tạo thành từ ký tự đầu tiên của ” và “chuỗi con được tạo thành từ ký tự đầu tiên của “. Tức là .
    2. Độ dài của dãy con chung của “chuỗi con được tạo thành từ ký tự đầu tiên của ” và “chuỗi con được tạo thành từ ký tự đầu tiên của “. Tức là .
4. Điều kiện ban đầu
  1. Khi , chuỗi con của là chuỗi rỗng, độ dài của dãy con chung giữa chuỗi rỗng và chuỗi , tức là .
  2. Khi , chuỗi con của là chuỗi rỗng, độ dài của dãy con chung giữa chuỗi và chuỗi rỗng là , tức là .
5. Kết quả cuối cùng

Dựa trên định nghĩa trạng thái, trả về (tức là độ dài của dãy con chung dài nhất của ), trong đó lần lượt là độ dài của .

Ý tưởng 1: Code
class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        size1 = len(text1)
        size2 = len(text2)
        dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)]
        for i in range(1, size1 + 1):
            for j in range(1, size2 + 1):
                if text1[i - 1] == text2[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1] + 1
                else:
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
 
        return dp[size1][size2]
Ý tưởng 1: Phân tích độ phức tạp
  • Độ phức tạp thời gian: , trong đó lần lượt là độ dài của chuỗi . Hai vòng lặp lồng nhau có độ phức tạp thời gian là , vậy tổng độ phức tạp thời gian là .
  • Độ phức tạp không gian: . Sử dụng một mảng hai chiều để lưu trữ trạng thái, vậy tổng độ phức tạp không gian là .

3.2 Dãy con chung dài nhất

3.2.1 Liên kết đề bài

3.2.2 Tóm tắt đề bài

Mô tả: Cho hai mảng số nguyên .

Yêu cầu: Tính độ dài của dãy con chung dài nhất giữa hai mảng.

Giải thích:

  • .
  • .

Ví dụ:

  • Ví dụ 1:
Input: nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
Output: 3
Explanation: Dãy con chung dài nhất là [3,2,1].
  • Ví dụ 2:
Input: nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0]
Output: 5

3.2.3 Ý tưởng giải quyết

Ý tưởng 1: Quy hoạch động
1. Phân chia giai đoạn

Phân chia giai đoạn dựa trên vị trí cuối cùng của hai mảng.

2. Định nghĩa trạng thái

Định nghĩa trạng thái là: Độ dài của dãy con chung dài nhất giữa “mảng con gồm phần tử đầu tiên của ” và “mảng con gồm phần tử đầu tiên của “.

3. Công thức chuyển tiếp trạng thái
  1. Nếu , tức là hai phần tử cuối cùng của hai mảng con giống nhau, vậy độ dài của dãy con chung tăng lên . Tức là: .
  2. Nếu , tức là hai phần tử cuối cùng của hai mảng con khác nhau, vậy .
4. Điều kiện ban đầu
  • Khi , mảng con của là mảng rỗng, độ dài của dãy con chung giữa mảng rỗng và mảng , tức là .
  • Khi , mảng con của là mảng rỗng, độ dài của dãy con chung giữa mảng và mảng rỗng là , tức là .
5. Kết quả cuối cùng
  • Dựa trên định nghĩa trạng thái, trả về giá trị lớn nhất trong tất cả các là kết quả cuối cùng.
Ý tưởng 1: Code
class Solution:
    def findLength(self, nums1: List[int], nums2: List[int]) -> int:
        size1 = len(nums1)
        size2 = len(nums2)
        dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)]
        res = 0
        for i in range(1, size1 + 1):
            for j in range(1, size2 + 1):
                if nums1[i - 1] == nums2[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1] + 1
                if dp[i][j] > res:
                    res = dp[i][j]
 
        return res
Ý tưởng 1: Phân tích độ phức tạp
  • Độ phức tạp thời gian: . Trong đó là độ dài của mảng , là độ dài của mảng . Hai vòng lặp lồng nhau có độ phức tạp thời gian là , vậy tổng độ phức tạp thời gian là .
  • Độ phức tạp không gian: . Sử dụng một mảng hai chiều để lưu trữ trạng thái, vậy tổng độ phức tạp không gian là .

3.3 Khoảng cách chỉnh sửa

3.3.1 Liên kết đề bài

3.3.2 Tóm tắt đề bài

Mô tả: Cho hai từ .

Một từ có thể thực hiện ba loại thao tác sau:

  • Thêm một ký tự
  • Xóa một ký tự
  • Thay thế một ký tự

Yêu cầu: Tính số lần thao tác tối thiểu để chuyển từ thành .

Giải thích:

  • .
  • chỉ chứa các ký tự thường.

Ví dụ:

  • Ví dụ 1:
Input: word1 = "horse", word2 = "ros"
Output: 3
Explanation:
horse -> rorse (thay 'h' bằng 'r')
rorse -> rose (xóa 'r')
rose -> ros (xóa 'e')
  • Ví dụ 2:
Input: word1 = "intention", word2 = "execution"
Output: 5
Explanation:
intention -> inention (xóa 't')
inention -> enention (thay 'i' bằng 'e')
enention -> exention (thay 'n' bằng 'x')
exention -> exection (thay 'n' bằng 'c')
exection -> execution (thêm 'u')

3.3.3 Ý tưởng giải quyết

Ý tưởng 1: Quy hoạch động
1. Phân chia giai đoạn

Phân chia giai đoạn dựa trên vị trí cuối cùng của hai từ.

2. Định nghĩa trạng thái

Định nghĩa trạng thái là: Số lần thao tác tối thiểu để chuyển từ “từ con gồm ký tự đầu tiên của ” thành “từ con gồm ký tự đầu tiên của “.

3. Công thức chuyển tiếp trạng thái
  1. Nếu hai ký tự hiện tại giống nhau (), không cần thêm, xóa hoặc thay thế. .
  2. Nếu hai ký tự hiện tại khác nhau (), lấy giá trị nhỏ nhất trong ba trường hợp sau:
    1. Thay thế ( thay thế bằng ): Số lần thao tác tối thiểu phụ thuộc vào “từ con gồm ký tự đầu tiên của ” chuyển thành “từ con gồm ký tự đầu tiên của ”, cộng thêm một lần thay thế, tức là .
    2. Thêm ( thêm một ký tự vào vị trí thứ ): Số lần thao tác tối thiểu phụ thuộc vào “từ con gồm ký tự đầu tiên của ” chuyển thành “từ con gồm ký tự đầu tiên của ”, cộng thêm một lần thêm, tức là .
    3. Xóa ( xóa ký tự thứ ): Số lần thao tác tối thiểu phụ thuộc vào “từ con gồm ký tự đầu tiên của ” chuyển thành “từ con gồm ký tự đầu tiên của ”, cộng thêm một lần xóa, tức là .

Tổng hợp các trường hợp trên, công thức chuyển tiếp trạng thái là:

4. Điều kiện ban đầu
  • Khi , “từ con gồm ký tự đầu tiên của ” là một từ rỗng, cần thực hiện lần thêm để chuyển thành “từ con gồm ký tự đầu tiên của ”, tức là .
  • Khi , “từ con gồm ký tự đầu tiên của ” là một từ rỗng, cần thực hiện lần xóa để chuyển từ “từ con gồm ký tự đầu tiên của ” thành từ rỗng, tức là .
5. Kết quả cuối cùng

Dựa trên định nghĩa trạng thái, trả về giá trị nhỏ nhất trong tất cả các là kết quả cuối cùng.

Ý tưởng 1: Code
class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        size1 = len(word1)
        size2 = len(word2)
        dp = [[0 for _ in range(size2 + 1)] for _ in range(size1 + 1)]
 
        for i in range(size1 + 1):
            dp[i][0] = i
        for j in range(size2 + 1):
            dp[0][j] = j
        for i in range(1, size1 + 1):
            for j in range(1, size2 + 1):
                if word1[i - 1] == word2[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1]
                else:
                    dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1
        return dp[size1][size2]
Ý tưởng 1: Phân tích độ phức tạp
  • Độ phức tạp thời gian: . Trong đó là độ dài của từ , là độ dài của từ . Hai vòng lặp lồng nhau có độ phức tạp thời gian là , vậy tổng độ phức tạp thời gian là .
  • Độ phức tạp không gian: . Sử dụng một mảng hai chiều để lưu trữ trạng thái, vậy tổng độ phức tạp không gian là .