最小回文数

GitHub跳转原题关系图返回列表

先把左半边镜像成回文;若还不够大,就给中间位置进位,再重新镜像,得到严格大于原数的最小回文数。

OJ: luogu

题目 ID: P1609

难度:普及/提高-

标签:字符串模拟高精度

日期: 2026-06-20 11:53

题意

给定一个十进制整数 N,要求找到一个最小的回文数 P,满足:

  • P > N

由于 N 最长可以有 100 位,所以不能用普通整数类型直接处理,应该把它当成字符串来做。

思路

先看一个最直接的小数据暴力:

cpp
#include <bits/stdc++.h>
using namespace std;

string s;

bool is_palindrome(const string &str) {
    int len = str.size();
    for (int i = 0; i < len / 2; i++) {
        if (str[i] != str[len - 1 - i]) {
            return false;
        }
    }
    return true;
}

// 字符串形式加一,适合小数据暴力。
void add_one(string &str) {
    int pos = str.size() - 1;
    while (pos >= 0 && str[pos] == '9') {
        str[pos] = '0';
        pos--;
    }

    if (pos >= 0) {
        str[pos]++;
    } else {
        str = "1" + str;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> s;

    // brute.cpp:不断把数字加一,直到遇到下一个回文数。
    // 这个做法很直观,但只适合小数据验证。
    do {
        add_one(s);
    } while (!is_palindrome(s));

    cout << s << '\n';

    return 0;
}

brute.cpp 的想法很直白:把原数不断加一,直到遇到第一个回文数。

这个做法虽然正确,但如果下一个回文数离当前数字很远,就会做很多次无用加法。

关键想法:答案的左半边基本已经确定

如果我们想构造“最小的、并且大于 N 的回文数”,那么最自然的做法就是:

  1. 尽量保留高位
  2. 只在必要的时候才把中间往上调

于是先把原串的左半边直接复制到右半边,得到一个回文串。

例如:

  • 12345 -> 12321
  • 80801 -> 80808

这个回文串有两种情况:

情况 1:镜像后已经大于原数

如果镜像后的回文串已经满足 > N,那它一定就是最小答案。

原因很简单:

  • 它的前半部分没有变大
  • 又已经是回文
  • 再想更小,就只能改动更高位,这反而更不可能保持 > N 且更小

情况 2:镜像后不够大

如果镜像后:

  • 小于原数
  • 或者等于原数

那就说明光靠“直接镜像”不够,必须把中间部分进位。

做法是:

  • 如果长度是奇数,就从正中间那一位开始进位
  • 如果长度是偶数,就从左中位开始进位

把它加一后,再重新把左边镜像到右边。

例如:

  • 12932

    • 先镜像得到 12921,不够大
    • 中间进位后变成 13021
  • 1991

    • 先镜像还是 1991
    • 左中位进位后得到 2002

特殊情况:全是 9

如果原数形如:

  • 9
  • 99
  • 999

那么答案一定是:

  • 11
  • 101
  • 1001

也就是:

1 + 若干个 0 + 1

这个情况单独处理最简单。

代码

cpp
#include <bits/stdc++.h>
using namespace std;

string s, t;

bool all_nine(const string &str) {
    for (int i = 0; i < (int)str.size(); i++) {
        if (str[i] != '9') {
            return false;
        }
    }
    return true;
}

// 把左半边镜像到右半边,生成一个回文串。
void make_palindrome(string &str) {
    int len = str.size();
    for (int i = 0; i < len / 2; i++) {
        str[len - 1 - i] = str[i];
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> s;

    // 全是 9 时,答案一定是 100...001。
    if (all_nine(s)) {
        cout << '1';
        for (int i = 1; i < (int)s.size(); i++) {
            cout << '0';
        }
        cout << '1' << '\n';
        return 0;
    }

    t = s;
    make_palindrome(t);

    // 直接镜像后已经比原数大,就是最小答案。
    if (t > s) {
        cout << t << '\n';
        return 0;
    }

    // 否则需要把中间这一位(或左中位)往前进位,
    // 再重新镜像一次。
    int pos = ((int)t.size() - 1) / 2;
    while (pos >= 0 && t[pos] == '9') {
        t[pos] = '0';
        pos--;
    }
    t[pos]++;

    make_palindrome(t);
    cout << t << '\n';

    return 0;
}

复杂度

  • 时间复杂度:O(len)O(len)
  • 空间复杂度:O(len)O(len)

其中 len 是数字串长度。

总结

这题最核心的思路是:

  1. 先直接镜像左半边
  2. 如果还不够大,再给中间做一次进位
  3. 然后重新镜像

它本质上是一道字符串构造题,不需要真的做高精度大整数运算。