Đếm số palindromes trong một chuỗi trong python

Một chuỗi là một palindrome khi nó đọc ngược cũng như đọc xuôi và một chuỗi con là một chuỗi các ký tự liền kề trong chuỗi. Trong bài viết này, chúng ta sẽ thảo luận về nhiều cách tiếp cận để tìm số lượng chuỗi con palindromic trong một chuỗi đã cho như

  • Cách tiếp cận vũ phu (cách tiếp cận hai con trỏ)
  • phương pháp đệ quy
  • Phương pháp lập trình động
  • Thuật toán Manacher đã sửa đổi

Hãy để chúng tôi thảo luận về tất cả chúng từng cái một trong bài viết này

Ví dụ.
Đầu vào. str = "bcbbc"
Đầu ra. 8
Giải thích. chuỗi con palindromic.
'b','c','b','b','c','bcb','cbbc','bb'

Đầu vào. str = "eaaebeb"
Đầu ra. 11
Giải thích. chuỗi con palindromic.
'e','a','a','e','b','e','b','eaae','aa','beb','

Cách tiếp cận vũ phu

Trong giải pháp brute force, chúng ta lấy từng chuỗi con theo cách tiếp cận con trỏ và kiểm tra xem nó có phải là một palindrome hay không

Thực hiện

Triển khai phương pháp Brute Force trong C++

#include 
#include
using namespace std;

bool isPalindromic(string s, int i, int j)
{   
    if (i > j)
        return 1;
 
    if (s[i] != s[j])
        return  0;
 
    return isPalindromic(s, i + 1, j - 1);
}

int main() {
    string str;
    cin>>str;
    
    int count =0;
    
    for(int i=0;i

Đối với một chuỗi đầu vào có độ dài n, sẽ có tổng số chuỗi con O(n^2). Việc kiểm tra từng chuỗi con để xem nó có phải là một bảng màu hay không sẽ mất thời gian tuyến tính vì chúng ta sẽ phải xem xét từng ký tự của chuỗi con chính xác một lần nếu đó là một bảng màu. Do đó, việc kiểm tra từng chuỗi con mất O(n) thời gian

Do đó, để kiểm tra tất cả các chuỗi con và trả về tổng số chuỗi con palindromic trong chuỗi đầu vào ban đầu, phương thức brute force sẽ mất O(n^3) thời gian

  • Thời gian phức tạp. O(N^3)
  • Độ phức tạp không gian. Ô(1)

Bây giờ chúng ta hãy thảo luận nếu có một giải pháp tốt hơn cho vấn đề này

phương pháp đệ quy

Nếu i và j là chỉ số bắt đầu và chỉ số kết thúc của chuỗi thì,

thuật toán

  1. Nếu i>j thì không thể có chuỗi con như vậy, do đó chúng tôi trả về 0;
  2. Nếu i==j thì chỉ có một ký tự duy nhất trong chuỗi con và như chúng ta biết mọi ký tự đều là một chuỗi con đối xứng do đó chúng ta trả về 1;
  3. chúng tôi có một chuỗi con từ i đến j và bây giờ chúng tôi kiểm tra xem chuỗi con có phải là màu nhạt hay không
  4. Nếu chuỗi con từ i đến j là một chuỗi đối xứng thì chúng ta tăng số chuỗi con đối xứng lên 1 và kiểm tra đệ quy các chuỗi con (i, j-1) và (i+1, j) và loại bỏ chuỗi con đối xứng chung (i+
  5. Nếu chuỗi con từ i đến j không phải là một đối xứng thì chúng ta kiểm tra đệ quy các chuỗi con đối xứng còn lại (i, j-1) và (i+1, j) và loại bỏ chuỗi con đối xứng phổ biến (i+1 , j-1)

Thực hiện

Triển khai phương thức đệ quy của chúng tôi trong C++

#include 
#include
using namespace std;

bool isPalindromic(string s, int i, int j)
{   
    if (i > j)
        return 1;
 
    if (s[i] != s[j])
        return  0;
 
    return isPalindromic(s, i + 1, j - 1);
}

int PalindromicSubstrings(int i, int j,string str){
   if(i>j) return 0;

   if(i==j) return 1;
   
   if(isPalindromic(str,i,j)) return PalindromicSubstrings(i+1,j,str)+ PalindromicSubstrings(i,j-1,str)+1-PalindromicSubstrings(i+1, j-1,str);

   else return  PalindromicSubstrings(i+1,j,str)+ PalindromicSubstrings(i,j-1,str)-PalindromicSubstrings(i+1, j-1,str);

}   

int main() {

    string str;
    cin>>str;
    cout<

Đối với cách tiếp cận đệ quy này, chúng tôi quan sát thấy rằng trong cây đệ quy có các bài toán con chồng chéo và do đó, chúng tôi có thể giải quyết nó một cách hiệu quả bằng Quy trình động

Phương pháp lập trình động từ dưới lên

Để triển khai phương pháp lập trình động từ dưới lên, chúng tôi xem xét hai mảng 2 chiều dp và p có kích thước n X n , trong đó n là độ dài chuỗi.
dp[i][j] lưu trữ số chuỗi con palindromic từ chỉ mục i tới j và p [i][j] giá trị boolean tùy thuộc vào việc chuỗi con từ chỉ mục i tới j là palindromic hay .

thuật toán

  1. Trường hợp cơ bản là mỗi ký tự đơn lẻ là một bảng màu

  2. Đối với độ dài là hai, nếu hai ký tự bằng nhau thì dp[i][i+1]=3 và p[i][i+1]=true, ngược lại nếu các ký tự khác nhau thì dp[i][i+1

  3. Bây giờ chúng tôi tính toán cho các chuỗi con có độ dài cao hơn. Nếu phần đầu và phần cuối khớp nhau và phần còn lại của chuỗi con đã là màu nhạt thì chúng ta trả về 1 + số đếm từ phần còn lại và lưu kết quả này vào dp[start][end] và cũng đặt p[start][end] true.
    Nếu phần đầu và phần cuối không khớp nhau hoặc phần còn lại của chuỗi con không phải là màu nhạt thì chúng tôi trả về số đếm từ phần còn lại và lưu kết quả này vào dp[start][end] và cũng tạo p[ .

  4. Cuối cùng trả về giá trị được lưu trữ trong dp[0][n-1] để lấy số chuỗi con palindromic;

Thực hiện

#include 
using namespace std;

int countPalindromicSubstrings(string str)
{

    int n = str.length();

    if (n == 0 || n == 1)
        return n;

    vector > dp(n);
    vector > p(n);

    for (int i = 0; i < n; i++) {
        dp[i] = vector(n);
        p[i] = vector(n);
    }
   

    for (int i = 0; i < n; i++) {
        dp[i][i] = 1;
        p[i][i] = true;
        if (i == n - 1)
            break;
        if (str[i] == str[i + 1]) {
            dp[i][i + 1] = 3;
            p[i][i + 1] = true;
        }
        else {
            dp[i][i + 1] = 2;
            p[i][i + 1] = false;
        }
    }
    for (int l = 3; l <= n; l++) {
        for (int start = 0; start <= n - l; start++) {
            int end = start + l - 1;
            if (str[start] == str[end] && p[start + 1][end - 1]) {
                dp[start][end] = dp[start + 1][end] + dp[start][end - 1] - dp[start + 1][end - 1] + 1;
                p[start][end] = true;
            }
            else {
                dp[start][end] = dp[start + 1][end] + dp[start][end - 1] - dp[start + 1][end - 1];
                p[start][end] = false;
            }
        }
    }
    return dp[0][n - 1];
}

int main()
{
    
    string str;

    cin >> str;

    cout << countPalindromicSubstrings(str) ;
    

    return 0;
}

Độ phức tạp về thời gian. O(n^2)

Ở đây khi chúng ta sử dụng hai mảng 2 chiều để lập trình động, do đó, không gian phụ cần có là O(n^2) và do đó,

Độ phức tạp của không gian. O(n^2)

Chúng ta cũng có thể có cách tiếp cận lập trình động từ trên xuống như được thảo luận bên dưới

Phương pháp tiếp cận từ trên xuống DP

Top Down DP là phiên bản ghi nhớ của phương pháp đệ quy

Để triển khai cách tiếp cận từ trên xuống, chúng tôi khởi tạo hai mảng 2D. dp[n][n] và p[n][n] trong đó n là độ dài của chuỗi

dp[i][j] lưu trữ số chuỗi con palindromic từ i đến j và p[i][j] lưu trữ 1 nếu chuỗi con từ i đến j là palindromic và 0 nếu không

thuật toán

  1. Nếu i>j thì không thể có chuỗi con như vậy, do đó chúng tôi trả về 0;
  2. Nếu i==j thì chỉ có một ký tự duy nhất trong chuỗi con và như chúng ta biết mọi ký tự đều là một chuỗi con đối xứng do đó chúng ta trả về 1;
  3. mặt khác, chúng tôi có một chuỗi con có độ dài cao hơn từ i đến j. Chúng tôi kiểm tra xem chúng tôi đã thực hiện các tính toán cho các giá trị này của i và j chưa và nếu có thì trả về giá trị dp[i][j]
  4. khác nếu không bây giờ chúng tôi kiểm tra xem chuỗi con có phải là màu nhạt hay không
  5. Nếu chuỗi con từ i đến j là một chuỗi đối xứng thì chúng ta tăng số chuỗi con đối xứng lên 1 và kiểm tra các chuỗi con (i, j-1) và (i+1, j) và loại bỏ chuỗi con đối xứng chung (i+1
  6. Nếu chuỗi con từ i đến j không phải là một chuỗi đối xứng thì chúng tôi kiểm tra các chuỗi con đối xứng còn lại (i, j-1) và (i+1, j) và xóa chuỗi con đối xứng phổ biến (i+1 , j-1) và lưu trữ

Ngoài ra, trong khi tìm xem chuỗi con từ i đến j có thuộc loại xuôi ngược hay không, chúng ta có một mảng 2 chiều p, trong đó p[i][j] lưu trữ kết quả nếu chuỗi con từ i đến j có thuộc dạng xuôi ngược hay không

Thực hiện

#include 
#include
using namespace std;

int p[100][100];
int dp[100][100];

bool isPalindrome(string str,int i,int j)
{  
    if (i > j)
        return 1;
    
    if (p[i][j] != -1)
        return p[i][j];
   
    if (str[i] != str[j])
        return p[i][j] = 0;
 
    return p[i][j] = isPalindrome(str, i + 1, j - 1);
}

int PalindromicSubstrings(int i, int j,string str){
   if(i>j) return 0;

   if(i==j) return 1;
   
   if(dp[i][j]!=-1) return dp[i][j];
  
   if(isPalindrome(str,i,j)) return dp[i][j]=PalindromicSubstrings(i+1,j,str)+ PalindromicSubstrings(i,j-1,str)+1-PalindromicSubstrings(i+1, j-1,str);

   else return dp[i][j]= PalindromicSubstrings(i+1,j,str)+ PalindromicSubstrings(i,j-1,str)-PalindromicSubstrings(i+1, j-1,str);
}   
int main() {

    string str;
    cin>>str;
    memset(p,-1,sizeof(p));
    memset(dp,-1,sizeof(dp));

    cout<

Như chúng ta đã thấy rằng sử dụng phương pháp lập trình động, chúng ta có độ phức tạp không gian là O(n^2). Vì vậy, bây giờ chúng ta hãy xem liệu có thể có cùng độ phức tạp thời gian nhưng độ phức tạp không gian tốt hơn bằng cách sử dụng một số thuật toán khác không

Sử dụng thuật toán Manacher đã sửa đổi

Thuật toán của người quản lý.
Thuật toán Manacher được sử dụng để tìm chuỗi con palindromic dài nhất trong một chuỗi đã cho.

Để tìm một bảng màu, chúng ta bắt đầu từ tâm của chuỗi và so sánh từng ký tự theo cả hai hướng. Nếu các ký tự tương ứng ở cả hai bên (trái và phải của tâm) khớp nhau, thì chúng sẽ tạo thành một bảng màu. Để tìm Chuỗi con Palindromic dài nhất của một chuỗi có độ dài n, chúng tôi lấy mỗi tâm 2n + 1 có thể, thực hiện khớp ký tự theo cả hai hướng trái và phải tại mỗi tâm và theo dõi LPS. Nếu chúng ta cần tính Chuỗi con Palindromic dài nhất tại mỗi vị trí trục từ trái sang phải, thì thuộc tính đối xứng của palindromic có thể giúp tránh một số tính toán không cần thiết. Nếu có một bảng màu có độ dài L nào đó có tâm ở bất kỳ vị trí P nào, thì chúng ta có thể không cần so sánh tất cả các ký tự ở bên trái và bên phải ở vị trí P+1 vì chúng ta đã tính LPS ở các vị trí trước P và chúng có thể giúp tránh . Thuật toán này được gọi là thuật toán manacher

Thuật toán manacher đã sửa đổi

Ý tưởng ở đây là chúng tôi sửa đổi thuật toán của người quản lý theo cách mà chúng tôi coi mỗi ký tự của chuỗi đầu vào là trục đóng vai trò là trung điểm của một bảng màu và mở rộng nó theo cả hai hướng để tìm số đếm tất cả các bảng màu có độ dài chẵn và lẻ thay vì chỉ

thuật toán

  1. Chúng tôi coi mọi ký tự trong chuỗi con (str[i]) là điểm xoay
  2. Đối với chuỗi con có độ dài lẻ, chúng ta coi str[i] là trung điểm và đối với chuỗi con có độ dài chẵn, chúng ta coi str[i] và str[i+1] là trung điểm
  3. Chúng tôi kiểm tra xem chuỗi con này có phải là một đối xứng hay không, nếu có, hãy tăng số đếm và tiếp tục mở rộng chúng theo cả hai hướng cho đến khi các chuỗi con là đối xứng

Thực hiện

Triển khai thuật toán của chúng tôi bằng thuật toán Manacher đã sửa đổi trong C ++

#include 
#include 
#include 
using namespace std;

set s;
int count=0;

void func(string str, int start, int end)
{
    
    while (start >= 0 && end < str.length() && str[start] == str[end])
    {
        
        count++;
 
        // Expand in both directions
        start--, end++;
    }
}
 

int countPalindromicSubstrings(string str)
{
 
    for (int i = 0; i < str.length(); i++)
    {
        // find all odd length palindrome with `str[i]` as a midpoint
        func(str, i, i);
 
        // find all even length palindrome with `str[i]` and `str[i+1]` as
        // its midpoints
        func(str, i, i + 1);
    }
 
    return count;
}
 
int main()
{
    string str ;
    cin>>str;
 
    cout<
Phân tích độ phức tạp của Thời gian và Không gian

Trong thuật toán này, chúng ta có 2n – 1 trục (n trục cho các chuỗi con có độ dài lẻ và n-1 trục cho các chuỗi con có độ dài chẵn) đóng vai trò là điểm giữa cho các chuỗi con palindromic có thể. Đối với mỗi điểm giữa, chúng tôi mở rộng sang trái và phải một ký tự tại một thời điểm và mở rộng chuỗi con cho đến khi chuỗi con mới nhất không phải là một bảng màu. Do đó, đối với mỗi điểm trục, chúng tôi xem xét tối đa n ký tự. Do đó, thuật toán của chúng tôi mất O(n) thời gian cho mỗi trục và có O(n) trục trong chuỗi đầu vào. Do đó, độ phức tạp về thời gian của giải pháp của chúng tôi là O(n^2)

Vì chúng tôi không sử dụng bất kỳ không gian phụ trợ nào nên độ phức tạp của không gian là O(1)

Độ phức tạp về thời gian. O(n^2)

Độ phức tạp của không gian. Ô(1)

Với bài viết này tại OpenGenus, bạn phải có ý tưởng đầy đủ về cách tìm số chuỗi con palindromic một cách hiệu quả