题目
一个 k 镜像数字 指的是一个在十进制和 k 进制下从前往后读和从后往前读都一样的 没有前导 0 的 正 整数。
比方说,9 是一个 2 镜像数字。9 在十进制下为 9 ,二进制下为 1001 ,两者从前往后读和从后往前读都一样。
相反地,4 不是一个 2 镜像数字。4 在二进制下为 100 ,从前往后和从后往前读不相同。
给你进制 k 和一个数字 n ,请你返回 k 镜像数字中 最小 的 n 个数 之和 。
示例 1:
输入:k = 2, n = 5
输出:25
解释:
最小的 5 个 2 镜像数字和它们的二进制表示如下:
十进制 二进制
1 1
3 11
5 101
7 111
9 1001
它们的和为 1 + 3 + 5 + 7 + 9 = 25 。
示例 2:
输入:k = 3, n = 7
输出:499
解释:
7 个最小的 3 镜像数字和它们的三进制表示如下:
十进制 三进制
1 1
2 2
4 11
8 22
121 11111
151 12121
212 21212
它们的和为 1 + 2 + 4 + 8 + 121 + 151 + 212 = 499 。
示例 3:
输入:k = 7, n = 17
输出:20379000
解释:17 个最小的 7 镜像数字分别为:
1, 2, 3, 4, 5, 6, 8, 121, 171, 242, 292, 16561, 65656, 2137312, 4602064, 6597956, 6958596
提示:
2 <= k <= 9
1 <= n <= 30
java
import java.util.*;
public class test {
// 核心方法:找前n个k镜像数并返回和
public static long kMirror(int k, int n) {
List<Long> result = new ArrayList<>(n);
long sum = 0;
int length = 1; // 从1位回文数开始构造
System.out.println("寻找前 " + n + " 个 " + k + " 镜像数字 (优化版):");
System.out.println("======================================");
while (result.size() < n) {
// 构造当前长度的所有十进制回文数(按升序)
List<Long> palindromes = generatePalindromes(length);
for (long pal : palindromes) {
if (result.size() >= n) break;
// 检查k进制是否回文
if (isKBasePalindrome(pal, k)) {
result.add(pal);
sum += pal;
// 打印日志
String kStr = Long.toString(pal, k);
System.out.printf("%d. 10进制: %d | %d进制: %s%n",
result.size(), pal, k, kStr);
}
}
length++;
}
System.out.println("======================================");
System.out.println("前" + n + "个" + k + "镜像数字之和: " + sum);
return sum;
}
/**
* 构造指定长度的十进制回文数(升序)
* 例如:length=3 → 101,111,...,191,202,...,999
*/
private static List<Long> generatePalindromes(int length) {
List<Long> palindromes = new ArrayList<>();
long start = (long) Math.pow(10, (length - 1) / 2); // 左半部分起始值
long end = (long) Math.pow(10, (length + 1) / 2) - 1; // 左半部分结束值
for (long left = start; left <= end; left++) {
StringBuilder sb = new StringBuilder();
String leftStr = String.valueOf(left);
sb.append(leftStr);
// 构造右半部分(奇数长度去掉最后一位,偶数长度直接反转)
String rightPart = new StringBuilder(leftStr)
.reverse()
.substring(length % 2); // 奇数长度跳过最后一位
sb.append(rightPart);
// 转换为数字
palindromes.add(Long.parseLong(sb.toString()));
}
return palindromes;
}
/**
* 检查数字在k进制下是否回文(优化版:避免完整转换字符串)
*/
private static boolean isKBasePalindrome(long num, int k) {
if (num < 0) return false;
if (num < k) return true; // 1位数必然回文
// 直接计算k进制的各位数字,同时检查回文(减少字符串操作)
List<Integer> digits = new ArrayList<>();
long temp = num;
while (temp > 0) {
digits.add((int) (temp % k));
temp /= k;
}
// 双指针检查回文
int left = 0, right = digits.size() - 1;
while (left < right) {
if (!digits.get(left).equals(digits.get(right))) {
return false;
}
left++;
right--;
}
return true;
}
public static void main(String[] args) {
// 测试用例
System.out.println("=== 测试 k=2, n=5 ===");
kMirror(2, 5);
System.out.println();
System.out.println("=== 测试 k=3, n=7 ===");
kMirror(3, 7);
System.out.println();
System.out.println("=== 测试 k=7, n=17 ===");
kMirror(7, 17);
System.out.println();
}
}
比较关键的是如何寻找回文数字,该代码是寻找10进制的回文数字,并判断其k进制是否是回文数字。在寻找过程中,如果采用暴力方法,很难应对较大的k和n,所以要优化一下寻找过程。
java
private static List<Long> generatePalindromes(int length) {
List<Long> palindromes = new ArrayList<>();
long start = (long) Math.pow(10, (length - 1) / 2); // 左半部分起始值
long end = (long) Math.pow(10, (length + 1) / 2) - 1; // 左半部分结束值
for (long left = start; left <= end; left++) {
StringBuilder sb = new StringBuilder();
String leftStr = String.valueOf(left);
sb.append(leftStr);
// 构造右半部分(奇数长度去掉最后一位,偶数长度直接反转)
String rightPart = new StringBuilder(leftStr)
.reverse()
.substring(length % 2); // 奇数长度跳过最后一位
sb.append(rightPart);
// 转换为数字
palindromes.add(Long.parseLong(sb.toString()));
}
return palindromes;
}
我采取的是构造一半的数字,再反转到另一半。如果是偶数,可以直接反转,如果是奇数,则不要最后一位数字,反转过去。