2016-11-02 25 views
5

Tôi đang cố gắng giải quyết Câu hỏi phân vùng Palindrome. Bạn có thể tìm câu hỏi trong https://leetcode.com/problems/palindrome-partitioning/.Hiệu suất chuỗi chậm chạp

Và tôi đã đưa ra giải pháp:

func partition(_ s: String) -> [[String]] { 

    var result: [[String]] = [] 

    func dfs(string: String, partiton: [String]) { 

     if string.characters.count == 0 { 
      result.append(partiton) 
      return 
     } 

     for length in 1...string.characters.count { 

      let endIndex = string.index(string.startIndex, offsetBy: length-1) 
      let part = string[string.startIndex...endIndex] 


      if isPalindrome(part) { 
       let leftPart = string[string.index(after: endIndex)..<string.endIndex] 
       print("string: \(string) part: \(part) leftpart: \(leftPart)") 
       dfs(string: leftPart, partiton: partiton + [part]) 
      } 
     } 
    } 

    func isPalindrome(_ s: String) -> Bool { 
     if String(s.characters.reversed()) == s { 
      return true 
     } else { 
      return false 
     } 
    } 

    dfs(string: s, partiton: []) 
    return result 
} 

Nhưng hiệu suất là Xấu. Đã vượt quá giới hạn thời gian.

Nhưng ý tưởng cùng với thực hiện Python có thể vượt qua:

def partition(self, s): 
    res = [] 
    self.dfs(s, [], res) 
    return res 

def dfs(self, s, path, res): 
    if not s: 
     res.append(path) 
     return 
    for i in range(1, len(s)+1): 
     if self.isPal(s[:i]): 
      self.dfs(s[i:], path+[s[:i]], res) 

def isPal(self, s): 
    return s == s[::-1] 

Nó làm cho tôi tự hỏi rằng làm thế nào để cải thiện việc thực hiện nhanh chóng và lý do tại sao việc thực hiện nhanh chóng là chậm hơn so với trăn.

Trả lời

19

A Swift String là tập hợp Character s và Character đại diện cho một cụm đồ thị mở rộng duy nhất, có thể là một hoặc nhiều vô hướng Unicode. Điều đó làm cho một số hoạt động chỉ mục như "bỏ qua ký tự đầu tiên" chậm.

Nhưng cải tiến đầu tiên là "ngắn mạch" chức năng isPalindrome() . Thay vì xây dựng chuỗi đảo ngược hoàn toàn, so sánh chuỗi ký tự với chuỗi đảo ngược của nó và dừng lại càng sớm như một sự khác biệt được tìm thấy:

func isPalindrome(_ s: String) -> Bool { 
    return !zip(s.characters, s.characters.reversed()).contains { $0 != $1 } 
} 

s.characters.reversed() không tạo ra một bộ sưu tập mới trong ngược trật tự, nó chỉ liệt kê các nhân vật từ sau ra trước. Tuy nhiên, Với String(s.characters.reversed()) như trong phương pháp của bạn, bạn bắt buộc tạo bộ sưu tập mới cho chuỗi bị đảo ngược, làm cho nó chậm.

Đối với chuỗi 110 ký tự

let string = String(repeating: "Hello world", count: 10) 

này làm giảm thời gian tính toán từ khoảng 6 giây đến 1,2 giây trong thử nghiệm của tôi.

Tiếp theo, tránh tính toán chỉ số như

let endIndex = string.index(string.startIndex, offsetBy: length-1) 

và lặp qua các chỉ số nhân vật riêng của mình thay vì:

func partition(_ s: String) -> [[String]] { 

    var result: [[String]] = [] 

    func dfs(string: String, partiton: [String]) { 
     if string.isEmpty { 
      result.append(partiton) 
      return 
     } 

     var idx = string.startIndex 
     repeat { 
      string.characters.formIndex(after: &idx) 
      let part = string.substring(to: idx) 
      if isPalindrome(part) { 
       let leftPart = string.substring(from: idx) 
       dfs(string: leftPart, partiton: partiton + [part]) 
      } 
     } while idx != string.endIndex 
    } 

    func isPalindrome(_ s: String) -> Bool { 
     return !zip(s.characters, s.characters.reversed()).contains { $0 != $1 } 
    } 

    dfs(string: s, partiton: []) 
    return result 
} 

thời gian tính toán hiện nay 0,7 giây là.

Bước tiếp theo là tránh lập chỉ mục chuỗi hoàn toàn và làm việc với một mảng ký tự, vì lập chỉ mục mảng nhanh. Thậm chí tốt hơn, sử dụng mảng lát mà là nhanh chóng để tạo ra và tham khảo các yếu tố mảng ban đầu:

func partition(_ s: String) -> [[String]] { 

    var result: [[String]] = [] 

    func dfs(chars: ArraySlice<Character>, partiton: [String]) { 

     if chars.isEmpty { 
      result.append(partiton) 
      return 
     } 

     for length in 1...chars.count { 
      let part = chars.prefix(length) 
      if isPalindrome(part) { 
       let leftPart = chars.dropFirst(length) 
       dfs(chars: leftPart, partiton: partiton + [String(part)]) 
      } 
     } 
    } 

    func isPalindrome(_ c: ArraySlice<Character>) -> Bool { 
     return !zip(c, c.reversed()).contains { $0 != $1 } 
    } 

    dfs(chars: ArraySlice(s.characters), partiton: []) 
    return result 
} 

thời gian tính toán hiện nay 0,08 giây là.

Nếu chuỗi của bạn chỉ chứa các ký tự trong "mặt phẳng đa ngôn ngữ cơ bản" (tức là< = U + FFFF) sau đó bạn có thể làm việc với các điểm UTF-16 mã thay vì:

func partition(_ s: String) -> [[String]] { 

    var result: [[String]] = [] 

    func dfs(chars: ArraySlice<UInt16>, partiton: [String]) { 

     if chars.isEmpty { 
      result.append(partiton) 
      return 
     } 

     for length in 1...chars.count { 
      let part = chars.prefix(length) 
      if isPalindrome(part) { 
       let leftPart = chars.dropFirst(length) 
       part.withUnsafeBufferPointer { 
        dfs(chars: leftPart, partiton: partiton + [String(utf16CodeUnits: $0.baseAddress!, count: length)]) 
       } 
      } 
     } 
    } 

    func isPalindrome(_ c: ArraySlice<UInt16>) -> Bool { 
     return !zip(c, c.reversed()).contains { $0 != $1 } 
    } 

    dfs(chars: ArraySlice(s.utf16), partiton: []) 
    return result 
} 

thời gian tính toán tại là 0,04 giây cho chuỗi kiểm tra 110 nhân vật.


Vì vậy, một số lời khuyên mà khả năng có thể cải thiện hiệu suất khi làm việc với chuỗi Swift là

  • lặp qua các ký tự/chỉ số tuần tự. Tránh "nhảy" đến vị trí thứ n.
  • Nếu bạn cần quyền truy cập "ngẫu nhiên" vào tất cả các ký tự, trước tiên hãy chuyển đổi chuỗi thành mảng.
  • Làm việc với chế độ xem UTF-16 của chuỗi có thể nhanh hơn khi đang hoạt động với chế độ xem ký tự.

Tất nhiên nó phụ thuộc vào trường hợp sử dụng thực tế. Trong ứng dụng này, , chúng tôi có thể giảm thời gian tính toán từ 6 giây xuống 0.04 giây, là hệ số 150.

Các vấn đề liên quan