Hoán vị Giải pháp LeetCode python

Quay lui là một thuật toán chung để tìm tất cả [hoặc một số] giải pháp cho một số vấn đề tính toán, giúp xây dựng dần dần các ứng cử viên cho các giải pháp. Ngay khi nó xác định rằng một ứng cử viên không thể dẫn đến một giải pháp hoàn chỉnh hợp lệ, nó sẽ loại bỏ ứng cử viên một phần này và “quay ngược'' [quay lại mức cao hơn] và đặt lại trạng thái của mức cao hơn để quá trình tìm kiếm có thể tiếp tục khám phá . Quay lui là tất cả về các lựa chọn và hậu quả, đây là lý do tại sao quay lui là thuật toán phổ biến nhất để giải quyết vấn đề thỏa mãn ràng buộc [CSP, CSP là các câu hỏi toán học được định nghĩa là một tập hợp các đối tượng có trạng thái phải đáp ứng một số ràng buộc hoặc giới hạn, truy cập wiki để biết thêm

Thuộc tính và ứng dụng

Để khái quát hóa các ký tự của quay lui

  1. Không lặp lại và hoàn thành. Đó là một phương pháp tạo có hệ thống để tránh lặp lại và bỏ lỡ bất kỳ giải pháp đúng nào có thể. Tính chất này làm cho nó trở nên lý tưởng để giải các bài toán tổ hợp như tổ hợp và hoán vị đòi hỏi chúng ta phải liệt kê tất cả các giải pháp có thể
  2. Cắt tỉa tìm kiếm. Vì giải pháp cuối cùng được xây dựng tăng dần nên trong quá trình làm việc với các giải pháp từng phần, chúng ta có thể đánh giá giải pháp từng phần và loại bỏ các nhánh không bao giờ dẫn đến giải pháp hoàn chỉnh có thể chấp nhận được. hoặc đó là cấu hình không hợp lệ hoặc tệ hơn giải pháp hoàn chỉnh có thể đã biết

Trong blog này, tổ chức như sau

  1. Hiển thị tài sản 1. Trước tiên, chúng tôi sẽ chỉ ra cách quay lui xây dựng giải pháp hoàn chỉnh tăng dần và cách nó quay trở lại trạng thái trước đó
  • Trên đồ thị ẩn. Chúng tôi sử dụng hoán vị và tổ hợp làm ví dụ
  • Trên đồ thị rõ ràng. Liệt kê tất cả các đường dẫn giữa đỉnh nguồn và đích trong bản vẽ biểu đồ

2. Hiển thị tài sản 2. Chúng tôi chứng minh ứng dụng cắt tỉa tìm kiếm trong quay lui thông qua các vấn đề CSP như sudoku

Hoán vị

Trước khi tôi nói với bạn nhiều lý thuyết hơn, chúng ta hãy xem một ví dụ. Cho một tập hợp các số nguyên {1, 2, 3}, hãy liệt kê tất cả các hoán vị có thể có bằng cách sử dụng tất cả các mục từ tập hợp mà không cần lặp lại. Một hoán vị mô tả một sự sắp xếp hoặc thứ tự của các mục. Thật tầm thường khi nhận ra rằng chúng ta có thể có sáu hoán vị sau. [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2] và [3, 2, 1]

hình 1. Biểu đồ Hoán vị có quay lui

Đây là một bài toán tổ hợp điển hình, quá trình tạo ra tất cả các hoán vị hợp lệ được hiển thị trong Hình. 1. Để xây dựng giải pháp cuối cùng, chúng ta có thể bắt đầu từ một thứ tự trống được hiển thị ở cấp độ đầu tiên, [ ]. Sau đó, chúng tôi cố gắng thêm một mục mà chúng tôi có ba lựa chọn. 1, 2 và 3. Ta được ba nghiệm riêng [1], [2], [3] bậc hai. Tiếp theo, đối với mỗi nghiệm bộ phận này, chúng ta có hai lựa chọn, đối với [1], chúng ta có thể đặt 2 hoặc 3 trước. Tương tự, đối với [2], chúng ta có thể thực hiện 1 và 3, v.v. Cho trước

[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
3 phần tử riêng biệt, số hoán vị có thể có là n*[n-1]*…*1 =
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
3

Biểu đồ ẩn

Trong biểu đồ, mỗi nút là một giải pháp một phần hoặc giải pháp cuối cùng. Nếu chúng ta xem nó như một cái cây, nút bên trong là một giải pháp một phần và tất cả các lá là giải pháp cuối cùng. Một cạnh đại diện cho việc tạo giải pháp tiếp theo dựa trên giải pháp hiện tại. Các đỉnh và cạnh không được cung cấp bởi một biểu đồ hoặc cây được xác định rõ ràng, các đỉnh được tạo nhanh chóng và các cạnh là mối quan hệ ngầm giữa các nút này

Quay lui và DFS

Việc thực hiện chuyển trạng thái chúng ta có thể sử dụng BFS hoặc DFS trên các đỉnh ẩn. DFS được ưu tiên hơn vì về mặt lý thuyết, nó mất O[log n. ] không gian được sử dụng bởi ngăn xếp, trong khi nếu sử dụng BFS, số đỉnh được lưu trong hàng đợi có thể gần bằng n. Với DFS đệ quy, chúng ta có thể bắt đầu từ nút [] và duyệt qua [1,2], sau đó [1,2,3]. Sau đó, chúng tôi quay lại [1,2], quay lại [1] và chuyển đến [1, 3], đến [1, 3, 2]. Để làm rõ mối quan hệ giữa backtracking và DFS, có thể nói backtracking là một kỹ thuật tìm kiếm hoàn chỉnh và DFS là một cách lý tưởng để thực hiện nó

Chúng ta có thể khái quát hóa Hoán vị, Hoán vị đề cập đến hoán vị của

[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
3 thứ được lấy
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
6tại một thời điểm không lặp lại, công thức toán học là A_{n}^{k} = n *[n-1]*[n-2]*…*k. Trong bộ lễ phục. 1, ta có thể thấy từ mỗi cấp độ
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
6có tất cả nghiệm của A_{n}^{k}. Việc tạo A_{n}^{k} được hiển thị trong Mã Python sau

Cho input là

[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
8, ta gọi hàm trên với đoạn code sau

a = [1, 2, 3]
n = len[a]
ans = [[None]]
used = [False] * len[a]
ans = []
A_n_k[a, n, n, 0, used, [], ans]
print[ans]

đầu ra là

[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]

Trong quá trình này, chúng tôi thêm

[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
9 trước và sau lệnh gọi hàm đệ quy

[1]
[1, 2]
[1, 2, 3]
backtrack: [1, 2]
backtrack: [1]
[1, 3]
[1, 3, 2]
backtrack: [1, 3]
backtrack: [1]
backtrack: []
[2]
[2, 1]
[2, 1, 3]
backtrack: [2, 1]
backtrack: [2]
[2, 3]
[2, 3, 1]
backtrack: [2, 3]
backtrack: [2]
backtrack: []
[3]
[3, 1]
[3, 1, 2]
backtrack: [3, 1]
backtrack: [3]
[3, 2]
[3, 2, 1]
backtrack: [3, 2]
backtrack: [3]
backtrack: []

Hai đường chuyền

Do đó, chúng ta có thể nói quay lại truy cập các đỉnh ẩn này trong hai lượt. Lượt chuyển tiếp đầu tiên để xây dựng giải pháp tăng dần, lượt chuyển tiếp thứ hai để quay lại trạng thái trước đó. Chúng ta có thể thấy trong hai lượt này, danh sách

[1]
[1, 2]
[1, 2, 3]
backtrack: [1, 2]
backtrack: [1]
[1, 3]
[1, 3, 2]
backtrack: [1, 3]
backtrack: [1]
backtrack: []
[2]
[2, 1]
[2, 1, 3]
backtrack: [2, 1]
backtrack: [2]
[2, 3]
[2, 3, 1]
backtrack: [2, 3]
backtrack: [2]
backtrack: []
[3]
[3, 1]
[3, 1, 2]
backtrack: [3, 1]
backtrack: [3]
[3, 2]
[3, 2, 1]
backtrack: [3, 2]
backtrack: [3]
backtrack: []
0 được sử dụng làm tất cả các đỉnh và nó bắt đầu bằng
[1]
[1, 2]
[1, 2, 3]
backtrack: [1, 2]
backtrack: [1]
[1, 3]
[1, 3, 2]
backtrack: [1, 3]
backtrack: [1]
backtrack: []
[2]
[2, 1]
[2, 1, 3]
backtrack: [2, 1]
backtrack: [2]
[2, 3]
[2, 3, 1]
backtrack: [2, 3]
backtrack: [2]
backtrack: []
[3]
[3, 1]
[3, 1, 2]
backtrack: [3, 1]
backtrack: [3]
[3, 2]
[3, 2, 1]
backtrack: [3, 2]
backtrack: [3]
backtrack: []
1 và kết thúc bằng
[1]
[1, 2]
[1, 2, 3]
backtrack: [1, 2]
backtrack: [1]
[1, 3]
[1, 3, 2]
backtrack: [1, 3]
backtrack: [1]
backtrack: []
[2]
[2, 1]
[2, 1, 3]
backtrack: [2, 1]
backtrack: [2]
[2, 3]
[2, 3, 1]
backtrack: [2, 3]
backtrack: [2]
backtrack: []
[3]
[3, 1]
[3, 1, 2]
backtrack: [3, 1]
backtrack: [3]
[3, 2]
[3, 2, 1]
backtrack: [3, 2]
backtrack: [3]
backtrack: []
1. Đây là đặc điểm của quay lui

Thời gian phức tạp của hoán vị

Trong ví dụ về hoán vị, chúng ta có thể thấy rằng quay lui chỉ truy cập mỗi trạng thái một lần. Độ phức tạp của điều này tương tự như việc duyệt đồ thị của O[. V. +. E. ], ở đâu. V. = \sum_{i=0}^{n}{A_{n}^{k}}, bởi vì nó là một cấu trúc cây,. E. =. v. -1. Điều này thực sự làm cho vấn đề hoán vị NP-hard

Quay lui với các vấn đề về LeetCode - Phần 2. Kết hợp và tất cả các đường dẫn với quay lui

Quay lui với các vấn đề về LeetCode - Phần 3. Các vấn đề về sự hài lòng của ràng buộc với việc cắt xén tìm kiếm

Ví dụ về LeetCode

17. Kết hợp chữ cái của một số điện thoại

Đưa ra một chuỗi chữ số, trả về tất cả các kết hợp chữ cái có thể có mà số đó có thể đại diện

Dưới đây là ánh xạ từ chữ số sang chữ cái [giống như trên các nút điện thoại]

Input:Digit string "23"
Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

Ghi chú
Mặc dù câu trả lời trên là theo thứ tự từ điển, câu trả lời của bạn có thể theo bất kỳ thứ tự nào bạn muốn

Giải pháp. đây không phải là vấn đề quay lui chính xác, tuy nhiên, chúng tôi đệ quy thêm chữ số tiếp theo vào các kết hợp trước đó. Độ phức tạp về thời gian sẽ là O[3^n], xuất phát từ O[3+3²+3³+…+3^n]. Sự khác biệt là chúng tôi biết đó là giải pháp khả thi, nếu chúng tôi tiếp tục tìm kiếm biểu đồ, nó hoạt động [không ràng buộc]

def letterCombinations[self, digits]:
"""
:type digits: str
:rtype: List[str]
"""
mapping = {'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
'6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'}

def combine[rst, remain_digits]:
#end condition
if len[remain_digits]==0:
return rst
if len[rst]==0:
rst=['']
nxt_rst=[]
digit = remain_digits.pop[0]
for r in rst:
for c in mapping[digit]:
nxt_rst.append[r+c]
return combine[nxt_rst,remain_digits] #nxt_rst = r+c

return combine[[],list[digits]] #first is current result

với đường lùi

78. Tập hợp con

Đưa ra một tập hợp các số nguyên riêng biệt, nums, trả về tất cả các tập con có thể [tập lũy thừa]

Ghi chú. Bộ giải pháp không được chứa các tập con trùng lặp

Ví dụ,
Nếu nums =

[1]
[1, 2]
[1, 2, 3]
backtrack: [1, 2]
backtrack: [1]
[1, 3]
[1, 3, 2]
backtrack: [1, 3]
backtrack: [1]
backtrack: []
[2]
[2, 1]
[2, 1, 3]
backtrack: [2, 1]
backtrack: [2]
[2, 3]
[2, 3, 1]
backtrack: [2, 3]
backtrack: [2]
backtrack: []
[3]
[3, 1]
[3, 1, 2]
backtrack: [3, 1]
backtrack: [3]
[3, 2]
[3, 2, 1]
backtrack: [3, 2]
backtrack: [3]
backtrack: []
3, một giải pháp là

[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]

Giải pháp. bởi vì chúng tôi không quan tâm đến thứ tự, nó là một sự kết hợp [không phải là hoán vị]. ở đây chúng tôi chỉ sử dụng chỉ mục + 1 để trỏ đến phần đầu của các đường dẫn có thể. tạm thời đề cập đến cách chữa. để ghi lại những gì chúng tôi sử dụng, nhưng khi chúng tôi quay lại sau cuộc gọi đệ quy, chúng tôi cần bật ra. và tiếp tục thêm phần tử tiếp theo

độ phức tạp thời gian là O[n*2^n], độ phức tạp không gian là O[2^n]. Làm thế nào để có được chúng?
def subsets[self, nums]:
"""
:type nums: List[int]
:rtype: List[List[int]]
"""

#here we need a global wise list, each time we just append to the result
rslt=[]

def dfs[temp, idx]:
rslt.append[temp[:]] #pass temp[:] with shollow copy so that we wont change the result of rslt when temp is changed
for i in range[idx, len[nums]]:
temp.append[nums[i]]
#backtrack
dfs[temp, i+1]
temp.pop[]


dfs[[],0]
return rslt

46. hoán vị

Đưa ra một tập hợp các số riêng biệt, trả về tất cả các hoán vị có thể

Ví dụ,

[1]
[1, 2]
[1, 2, 3]
backtrack: [1, 2]
backtrack: [1]
[1, 3]
[1, 3, 2]
backtrack: [1, 3]
backtrack: [1]
backtrack: []
[2]
[2, 1]
[2, 1, 3]
backtrack: [2, 1]
backtrack: [2]
[2, 3]
[2, 3, 1]
backtrack: [2, 3]
backtrack: [2]
backtrack: []
[3]
[3, 1]
[3, 1, 2]
backtrack: [3, 1]
backtrack: [3]
[3, 2]
[3, 2, 1]
backtrack: [3, 2]
backtrack: [3]
backtrack: []
3 có các hoán vị sau

[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

Giải pháp. Hoán vị tương tự như tập lũy thừa cuối cùng, sự khác biệt là chúng tôi sử dụng mỗi phần tử ít nhất và chỉ một lần và chúng tôi không quan tâm đến thứ tự. Vậy đối với các phần tử còn lại thì khác

def permute[self, nums]:
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
#here we need a global wise list, each time we just append to the result
rslt=[]

def dfs[temp, elements]:
#gather rslt
if len[elements]==0:
rslt.append[temp[:]] #still remember to use temp[:]
for e in elements:
temp.append[e]
#backtrack
next_elements=elements[:]
next_elements.remove[e]
elements.pop[]
dfs[temp, next_elements]
temp.pop[]


dfs[[],nums] #first is the current result
return rslt

Tóm tắt thêm

47. Hoán vị II

Đưa ra một tập hợp các số có thể chứa các số trùng lặp, trả về tất cả các hoán vị duy nhất có thể

Ví dụ,

[1]
[1, 2]
[1, 2, 3]
backtrack: [1, 2]
backtrack: [1]
[1, 3]
[1, 3, 2]
backtrack: [1, 3]
backtrack: [1]
backtrack: []
[2]
[2, 1]
[2, 1, 3]
backtrack: [2, 1]
backtrack: [2]
[2, 3]
[2, 3, 1]
backtrack: [2, 3]
backtrack: [2]
backtrack: []
[3]
[3, 1]
[3, 1, 2]
backtrack: [3, 1]
backtrack: [3]
[3, 2]
[3, 2, 1]
backtrack: [3, 2]
backtrack: [3]
backtrack: []
5 có các hoán vị duy nhất sau

[
[1,1,2],
[1,2,1],
[2,1,1]
]

Giải pháp. Sự khác biệt với hoán vị khác là, mỗi lần, chúng tôi chỉ thêm phần tử duy nhất vào temp

[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
0

301. Xóa dấu ngoặc đơn không hợp lệ

Xóa số lượng dấu ngoặc đơn không hợp lệ tối thiểu để làm cho chuỗi đầu vào hợp lệ. Trả về tất cả các kết quả có thể

Ghi chú. Chuỗi đầu vào có thể chứa các chữ cái khác ngoài dấu ngoặc đơn

[1]
[1, 2]
[1, 2, 3]
backtrack: [1, 2]
backtrack: [1]
[1, 3]
[1, 3, 2]
backtrack: [1, 3]
backtrack: [1]
backtrack: []
[2]
[2, 1]
[2, 1, 3]
backtrack: [2, 1]
backtrack: [2]
[2, 3]
[2, 3, 1]
backtrack: [2, 3]
backtrack: [2]
backtrack: []
[3]
[3, 1]
[3, 1, 2]
backtrack: [3, 1]
backtrack: [3]
[3, 2]
[3, 2, 1]
backtrack: [3, 2]
backtrack: [3]
backtrack: []
6 và
[1]
[1, 2]
[1, 2, 3]
backtrack: [1, 2]
backtrack: [1]
[1, 3]
[1, 3, 2]
backtrack: [1, 3]
backtrack: [1]
backtrack: []
[2]
[2, 1]
[2, 1, 3]
backtrack: [2, 1]
backtrack: [2]
[2, 3]
[2, 3, 1]
backtrack: [2, 3]
backtrack: [2]
backtrack: []
[3]
[3, 1]
[3, 1, 2]
backtrack: [3, 1]
backtrack: [3]
[3, 2]
[3, 2, 1]
backtrack: [3, 2]
backtrack: [3]
backtrack: []
7

ví dụ

[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
1

Giải pháp. lúc đầu kiểm tra số lượng dấu ngoặc trái và dấu ngoặc phải cần bỏ. Sau đó sử dụng DFS [thử tất cả các cách có thể] với theo dõi ngược để có được tất cả các giải pháp có thể [khi l, r giảm về 0, kiểm tra xem nó có hợp lệ không]. Cần lưu ý. chúng ta cần tránh trùng lặp

Chủ Đề