最小回文數

/*
 *迴文數
 *題目詳情:
 *如果一個數正着讀和反着讀一樣大,則這個數叫做迴文數,例如121是迴文數,123454321是迴文數。
 *現給定一個正整數x,輸出一個迴文數y,要求y > x,並且組成x的所有數字之和與組成y的所有數字之和相等,以及y > x。
 *x在10^1000以內,因爲數字較大,我們用字符串作爲輸入和輸出。
 *如果無解,請輸出Impossible。如果有多個y,輸出最小的那個。
 *例如:
 *輸入919,輸出14941
 *輸入1,輸出Impossible
 *
 *時間:2014-01-11
 *原題:http://hero.csdn.net/OnlineCompiler/Index?ID=223&ExamID=218&ExamResultID=117157&ExamNum=2&random=1757529427
 */

#include<stdio.h>
#include<stdlib.h>
#define NO_ANS    "Impossible"//無解
#define C2NUM(c)  ((c)-'0')//字符轉十進制數,如'1'->1
#define N2CHAR(n) ((n)+'0')//十進制數字轉字符,如1->'1'

//字符串長度
int strLen(const char*str){
    char*tmp=(char*)str;
    if(!str)
      return 0;
    while(*tmp)tmp++;
    return tmp-str;
}
//比較兩個數的大小
//strA>strB則返回正數,相等則返回0,否則返回負數
int cmpStr(const char*strA,const char*strB){
    int lena,lenb;
    //去除前面的數字0
    while(*strA=='0')strA++;
    while(*strB=='0')strB++;
    if((!*strA)||(!*strB))//至少有一個數等於0
        return *strA-*strB;
//    printf("a=%s,b=%s\n",strA,strB);
    lena=strLen(strA);
    lenb=strLen(strB);
    if(lena!=lenb)
        return lena-lenb;
    //長度相等時數字大小比較
    while(*strA!=0&&*strA==*strB)
        strA++,strB++;
//    printf("a=%s,b=%s\n",strA,strB);
    return *strA-*strB;
}
//作用:數字和=sum,位數=len,首位>=headMin的最小回文數
//返回:無法找到則返回0,否則返回非負數
int minHWA(int sum,int len,int headMin,char*buf){
    char*left,*right;
    //if(!sum&&!len)
      //return 1;
    if(len<=0||headMin<0||headMin>9||sum<2*headMin)
      return 0;
    //和是奇數,位數爲偶數,這樣的條件下不存在解
    if(sum%2==1&&len%2==0)
      return 0;
    buf[0]=buf[len-1]=N2CHAR(headMin);//暫定首尾的數字爲headMin
    sum-=2*headMin;
    left=buf+len/2-1;//迴文數的左側
    right=left+1+len%2;//迴文數的右側
    //長度爲偶數
     if(len%2==0){
        sum/=2;
    }else{
        //確定中間的數字
        *(left+1)=sum%2==0?
          (sum>8?N2CHAR(8):N2CHAR(sum)):
          (sum>9?N2CHAR(9):N2CHAR(sum));
        sum=(sum-C2NUM(*(left+1)))/2;
    }
    if(sum+headMin>len/2*9)
      return 0;
    //從低位到高位依次確定各位的數字,優先選擇最大的數
    while(left>buf){
      *left=sum>9?N2CHAR(9):N2CHAR(sum);//取儘可能大的數
      *right=*left;//對稱的數
      sum-=C2NUM(*left);//左半邊數字和減去相應的數字
      left--;//更高位
      right++;
    }
    *right=*left=*left+sum;//剩餘和加到首位上
    return 1;
}


//作用:數字和等於x,且大於x的最小回文數
//算法:首先考慮位數相等的時候,是否有解
//先求數字總和sum,如果sum是奇數,位數是偶數,則不存在等長的迴文數滿足條件
//設x=X1_X2,要尋找最小的迴文數y,則y的高位要儘可能和x的高位相等
//設y=y1_y2_y3,y1=y3=x1,y2也是迴文數
//先選擇位數儘可能多的x1,則sum(y2)=sum(x)-2*sum(x1),顯然要保證sum(y2)>=0
//y1=x1,y1選定的情況下,查看是否存在迴文數y2滿足條件,且y2_y3>x2
//若不存在,則x1減最低位,再檢查是否存在y2滿足條件
//若直到x1位數爲0,也無法找到,則說明同等位數下,無法找到滿足條件的迴文數y
//計算位數比x位數多的最小回文數y,y的首尾都是1
char* palindrom (char* x)
{
    int len,sum=0;
    char*result,*left,*right;
    len=strLen(x);
    result=(char*)malloc((4+len)*sizeof(char));
    left=x;
    //求數字和
    while(*left++)
      sum+=C2NUM(*(left-1));
    left=result-1;//result的前一位
    right=left+len+1;//result的末尾
    *(right+2)=*(right+1)=*right='\0';
    if(sum%2==1&&len%2==0){//數字和爲奇數,長度爲偶數,大於此數的迴文數長度必大1
//        printf("x=%s,sum=%d,len=%d\n",a,sum,len+1);
        return minHWA(sum,len+1,1,result)?result:NO_ANS;
    }
    while(left<result-2+len/2){
        left++;
        right--;
        *right=*left=x[left-result];
        sum-=2*C2NUM(*left);
        if(sum<=0){
            sum+=2*C2NUM(*left);
            left--;
            right++;
            break;
        }
    }
    while(left>=result-1){
        ++left;//保證left之前的字符不變
        --right;
        *right=*left=x[left-result];
        sum-=2*C2NUM(*left);
        //*left和*right增到9,檢查是否滿足條件
        while(*left<'9'&&sum>=0){
            if(sum==0){
                minHWA(0,right-left-1,0,left+1);//填充0
                //result是迴文數,如果大於a,則返回
                if(cmpStr(left,left-result+x)>0)
                    return result;
            }
            *right=++*left;
            sum-=2;
            if(minHWA(sum,right-left-1,0,left+1))
                return result;
        }
        sum+=2*C2NUM(*left);
        if(left-1>=result)
            sum+=2*C2NUM(*(left-1));
        left-=2;
        right+=2;
    }
    left=result;
    //數字和是奇數,長度也是奇數,大於該數的最小回文數長度爲奇數
    if(sum%2==1&&len%2==1)
        right=left+len+1;
    else//位數大1的數裏邊有滿足條件的迴文數
        right=left+len;
    *(right+1)='\0';
//    printf("sum=%d,len=%d\n",sum,right-left+1);
    return minHWA(sum,right-left+1,1,left)?result:NO_ANS;
}
//整數轉成字符串,測試用
char* num2Str(int n,char*buf){
    char*tmp=buf,*str=buf;
    char c;
    while(n>9){
        *tmp++=N2CHAR(n%10);
        n/=10;
    }
    *tmp++=N2CHAR(n);
    *tmp--='\0';
    while(tmp>str){
        c=*tmp;
        *tmp=*str;
        *str=c;
        str++;
        tmp--;
    }
    return buf;
}
//start 提示:自動閱卷起始唯一標識,請勿刪除或增加。
int main()
{
      char buf[1024]={0};
      int k;
//      printf("%s\n",palindrom("1"));
//      printf("%s\n",palindrom("3"));
//      printf("%s\n",palindrom("5"));
//      printf("%s\n",palindrom("7"));
//      printf("%s\n",palindrom("9"));
//      printf("%s\n",palindrom("10"));
//      printf("%s\n",palindrom("11"));
//      printf("%s\n",palindrom("12"));
//      printf("%s\n",palindrom("21"));
//      printf("%s\n",palindrom("22"));
      for(k=1;k<1000;k+=13){
          num2Str(k,buf);
          printf("%-4d  %s\n",k,palindrom(buf));
      }
//      printf("%s\n",palindrom("293"));
//      printf("%s\n",palindrom("200"));
//      printf("%s\n",palindrom("999"));
//      printf("%s\n",palindrom("9876"));
}
//end //提示:自動閱卷結束唯一標識,請勿刪除或增加。


 

測試結果如下圖所示:

上述算法有些bug,經過改進之後,代碼如下:

/*
 *迴文數
 *題目詳情:
 *如果一個數正着讀和反着讀一樣大,則這個數叫做迴文數,例如121是迴文數,123454321是迴文數。
 *現給定一個正整數x,輸出一個迴文數y,要求y > x,並且組成x的所有數字之和與組成y的所有數字之和相等,以及y > x。
 *x在10^1000以內,因爲數字較大,我們用字符串作爲輸入和輸出。
 *如果無解,請輸出Impossible。如果有多個y,輸出最小的那個。
 *例如:
 *輸入919,輸出14941
 *輸入1,輸出Impossible
 *
 *時間:2014-01-11
 *原題:http://hero.csdn.net/OnlineCompiler/Index?ID=223&ExamID=218&ExamResultID=117157&ExamNum=2&random=1757529427
 */

#include<stdio.h>
#include<stdlib.h>
#include<memory.h>
#include<math.h>
#define NO_ANS    "Impossible"//無解
#define C2NUM(c)  ((c)-'0')//字符轉十進制數,如'1'->1
#define N2CHAR(n) ((n)+'0')//十進制數字轉字符,如1->'1'

//字符串長度
int strLen(const char*str){
    char*tmp=(char*)str;
    if(!str)
      return 0;
    while(*tmp)tmp++;
    return tmp-str;
}
//比較兩個數的大小
//strA>strB則返回正數,相等則返回0,否則返回負數
int cmpStr(const char*strA,const char*strB){
    int lena,lenb;
    //去除前面的數字0
    while(*strA=='0')strA++;
    while(*strB=='0')strB++;
    if((!*strA)||(!*strB))//至少有一個數等於0
        return *strA-*strB;
//    printf("a=%s,b=%s\n",strA,strB);
    lena=strLen(strA);
    lenb=strLen(strB);
    if(lena!=lenb)
        return lena-lenb;
    //長度相等時數字大小比較
    while(*strA!=0&&*strA==*strB)
        strA++,strB++;
//    printf("a=%s,b=%s\n",strA,strB);
    return *strA-*strB;
}
//作用:數字和=sum,位數=len,首位>=headMin的最小回文數
//返回:無法找到則返回0,否則返回非負數
int minHWA(int sum,int len,int headMin,char*buf){
    char*left,*right;
    if(len<=0||headMin<0||headMin>9||sum<headMin)
      return 0;
    //和是奇數,位數爲偶數,這樣的條件下不存在解
    if(sum%2==1&&len%2==0)
      return 0;
    if(len==1){//位數爲1時特別對待
        if(sum<10&&sum>=headMin){
            buf[0]=N2CHAR(sum);
            return 1;
        }
        return 0;
    }
    if(sum<2*headMin||sum>9*len)
        return 0;
    buf[0]=buf[len-1]=N2CHAR(headMin);//暫定首尾的數字爲headMin
    sum-=2*headMin;
    left=buf+len/2-1;//迴文數的左側
    right=left+1+len%2;//迴文數的右側
    //長度爲偶數
     if(len%2==0){
        sum/=2;
    }else{
        //長度爲奇數時先確定中間的數字
        *(left+1)=sum%2==0?
          (sum>8?N2CHAR(8):N2CHAR(sum)):
          (sum>9?N2CHAR(9):N2CHAR(sum));
        sum=(sum-C2NUM(*(left+1)))/2;
    }
    //從低位到高位依次確定各位的數字,優先選擇最大的數
    while(left>buf){
      *left=sum>9?N2CHAR(9):N2CHAR(sum);//取儘可能大的數
      *right=*left;//對稱的數
      sum-=C2NUM(*left);//左半邊數字和減去相應的數字
      left--;//更高位
      right++;
    }
    *right=*left=*left+sum;//剩餘和加到首位上
    return 1;
}


//作用:數字和等於x,且大於x的最小回文數
//算法:首先考慮位數相等的時候,是否有解
//先求數字總和sum,如果sum是奇數,位數是偶數,則不存在等長的迴文數滿足條件
//設x=X1_X2,要尋找最小的迴文數y,則y的高位要儘可能和x的高位相等
//設y=y1_y2_y3,y1=y3=x1,y2也是迴文數
//先選擇位數儘可能多的x1,則sum(y2)=sum(x)-2*sum(x1),顯然要保證sum(y2)>=0
//y1=x1,y1選定的情況下,查看是否存在迴文數y2滿足條件,且y2_y3>x2
//若不存在,則x1減最低位,再檢查是否存在y2滿足條件
//若直到x1位數爲0,也無法找到,則說明同等位數下,無法找到滿足條件的迴文數y
//計算位數比x位數多的最小回文數y,y的首尾都是1
char* palindrom (char* x)
{
    int len,sum=0,r;
    char*result,*left,*right;
    len=strLen(x);
    result=(char*)malloc((4+len)*sizeof(char));
    memset(result,'0',(len)*sizeof(char));
    memset(result+len,'\0',4*sizeof(char));
    left=x;
    //求數字和
    while(*left++)
      sum+=C2NUM(*(left-1));
    if(sum<=1){//數字和小於2的,必無滿足條件的迴文數
        free(result);
        return NO_ANS;
    }
    left=result-1;//result的前一位
    right=left+len+1;//result的末尾
    if(sum%2==1&&len%2==0){//數字和是奇數
        len+=1;
        if(minHWA(sum,len,1,result))
            return result;
        free(result);
        return NO_ANS;
    }
    while(left<result-2+len/2){
        left++;
        right--;
        *right=*left=x[left-result];
        sum-=2*C2NUM(*left);
        if(sum<=0){
            sum+=2*C2NUM(*left);
            left--;
            right++;
            break;
        }
    }
//    printf("x=%s,sum=%d\n",x,sum);
    while(left>=result-1){
        ++left;//保證left之前的字符不變
        --right;
        *right=*left=x[left-result];
        sum-=2*C2NUM(*left);
//        printf("result=%s,sum=%d\n",result,sum);
        //*left和*right增到9,檢查是否存在滿足條件的解
        if(sum>0){
            if(minHWA(sum,right-left-1,C2NUM(x[left-result+1]),left+1)
                    &&cmpStr(left,left-result+x)>0)
                return result;
            while(*left<'9'&&sum>=2){
                *right=++*left;
                sum-=2;
//                printf("result=%s,sum=%d\n",result,sum);
                if(minHWA(sum,right-left-1,0,left+1)||sum==0)
                    return result;
            }
        }else if(sum==0&&C2NUM(x[left-result+1])==0){
            minHWA(sum,right-left-1,C2NUM(x[left-result+1]),left+1);
            if(cmpStr(left,left-result+x)>0)
                return result;
        }
        sum+=2*C2NUM(*left);
        if(left-1>=result)
            sum+=2*C2NUM(*(left-1));
        left-=2;
        right+=2;
    }
    left=result;
    if(minHWA(sum,len+1,1,left))//位數大1時是否有解
        return result;
    if(minHWA(sum,len+2,1,left))//位數大2時是否有解
        return result;
    free(result);
    return NO_ANS;//無解
}
//整數轉成字符串,測試用
char* num2Str(int n,char*buf){
    char*tmp=buf,*str=buf;
    char c;
    while(n>9){
        *tmp++=N2CHAR(n%10);
        n/=10;
    }
    *tmp++=N2CHAR(n);
    *tmp--='\0';
    while(tmp>str){
        c=*tmp;
        *tmp=*str;
        *str=c;
        str++;
        tmp--;
    }
    return buf;
}
//測試,數x的最小回文數是ans
#define TEST(x,ans) printf("n=%-5d ans=%-12s assert="#ans"\n",(x),palindrom(#x))
//start 提示:自動閱卷起始唯一標識,請勿刪除或增加。
int main()
{
      char buf[1024]={0};
      int k;
      TEST(1,Impossible);
      TEST(10,Impossible);
      TEST(3,111);
      TEST(101,1001);
      TEST(2,11);
      TEST(103,121);
      TEST(11,101);
      TEST(12,111);
      TEST(13,22);
      TEST(998,4994);
      TEST(121,202);
      TEST(131,212);
      for(k=1;k<10000;k+=k%11+13){
          num2Str(k,buf);
          printf("%-4d  %s\n",k,palindrom(buf));
      }
      buf[0]=N2CHAR(1+rand()%9);
      for(k=1;k<1000;k++)
          buf[k]=N2CHAR(rand()%10);
      printf("x=%s\nresult=%s\n",buf,palindrom(buf));
//      printf("%s\n",palindrom("293"));
//      printf("%s\n",palindrom("200"));
//      printf("%s\n",palindrom("999"));
//      printf("%s\n",palindrom("9876"));
}
//end //提示:自動閱卷結束唯一標識,請勿刪除或增加。

測試結果如下:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章