[算法]添加最少字符数构成使字符串构成回文

 http://poj.org/problem?id=1159

Description

A palindrome is a symmetrical string, that is, a string read identically from left to right as well as from right to left. You are to write a program which, given a string, determines the minimal number of characters to be inserted into the string in order to obtain a palindrome. 

As an example, by inserting 2 characters, the string "Ab3bd" can be transformed into a palindrome ("dAb3bAd" or "Adb3bdA"). However, inserting fewer than 2 characters does not produce a palindrome. 

Input

Your program is to read from standard input. The first line contains one integer: the length of the input string N, 3 <= N <= 5000. The second line contains one string with length N. The string is formed from uppercase letters from 'A' to 'Z', lowercase letters from 'a' to 'z' and digits from '0' to '9'. Uppercase and lowercase letters are to be considered distinct.

Output

Your program is to write to standard output. The first line contains one integer, which is the desired minimal number.

Sample Input

5

Ab3bd

Sample Output

2

Source

 
我看到这个题目第一时间是用递归算法直接做的,就几行代码,很简单的,但是提交,咦,超时哦,看来递归有很多重复工作,于是就想到用动态规划算法来解决这个问题。
算法如下:
记字符串为 strleft为当前字符串最左边字符的索引下标, right则是当前字符串最右边字符的索引下标
1、如果字符串只有一个字符,返回0
2、如果字符串有两个字符,相同则返回0,否则返回1
3、字符串三个或者三个以上则有如下状态转移方程
    1)、str[left] == str[right];则minAdd(str,left,right) = minAdd(str,left+1,right-1)
    2)、str[left] != str[right];则minAdd(str,left,right) =1+ min( minAdd(str,left,right-1),minAdd(str,left+1,right) );
 
 
用m[][]记录left~right字符串构成回文所需的最少字符数,可以避免重复计算。
具体可以在代码中看到m[][]的初始化与构造。
#include <iostream>

using namespace std;

const short N = 5001;

short m[N][N];

short  foo(char* str,short left,short right)

{

    if(left  == right)

    {

        return m[left][right] = 0;

    }

    else if( left + 1 == right )

    {

        if( str[left] == str[right] )

        {

            return m[left][right] = 0;

        }

        else

        {

            return m[left][right] = 1;

        }

    }

    else

    {

        if( m[left][right] != -1 )

        {

            return m[left][right]; 

        }

        else

        {

            if(str[left] == str[right])

            {

                return m[left][right] = foo(str,left + 1, right - 1);

            } 

            else

            {

                short a = foo(str,left, right - 1) + 1;

                short b = foo(str,left + 1, right) + 1;

                return m[left][right] = a < b ? a : b;    

            }

        } 

    }

}

int main(int argc, char** argv) { 

    char str[N];

    short i, j, n;

    for( i = 0; i < N; ++i)

    {

        for(j =0; j < N; ++j)

        {

            m[i][j] = -1;

        }

    }

    cin>>n;

    i = 0; 

    while(i < n)

    {

        cin>>str[i++];

    }

    str[i]=0;

    cout<<foo(str,0,n - 1); 

    return 0;

}

本来二维数组m[N][N]是int型的,但是OJ报告内存超过限制,转用short即可,可以覆盖5000,因为题目要求是 2<N<5001,或者可以使用滚动数组,可以看如下的另一个代码。

#include <iostream>

#include <cstring>

using namespace std;

const int N = 5001;

short m[2][N];

short foo(char* str, short n)

{

    short i, j, k, k1, k2, a, b;

    for(k = 1; k < n; ++k)

    {

        k1 = k & 1;

        k2 = (k - 1) & 1;

        for(i = 0; i + k < n; ++i)

        {

            j = i + k;

            if(str[i] == str[j])

            {

                m[k1][i] = m[k1][i+1];

            }

            else

            {

                m[k1][i] = 1 + (m[k2][i] < m[k2][i+1] ? m[k2][i] : m[k2][i+1]); 

            }

        }

    }

    return m[(n-1)&1][0];

}

int main(int argc, char** argv) { 

    char str[N];

    short i, n;

    i = 0; 

    cin>>n;

    while(i < n)

    {

        cin>>str[i++];

    }

    str[i]=0;

    cout<<foo(str,n);

    return 0;

}

在这里用到一个滚动数组,妙用了&1,就是轮换使用两个一维数组,降低了一个数量级的空间复杂度。

 

写一次代码就有些心得体会,看来算法还是得通过实现来巩固掌握。

 


本文基于知识共享署名-非商业性使用 3.0 许可协议进行许可。欢迎转载、演绎,但是必须保留本文的署名林羽飞扬,若需咨询,请给我发信

你可能感兴趣的:(字符串)