You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

159 lines
4.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package class_2023_03_1_week;
// 来自学员问题
// 给定一个1~N的排列每次将相邻两数相加可以得到新的序列长度是N-1
// 再对新的序列每次将相邻两数相加可以得到新的序列长度是N-2
// 这样下去可以最终只剩一个数字
// 比如 :
// 3 1 2 4
// 4 3 6
// 7 9
// 16
// 现在如果知道N和最后的数字sum反推最原始的序列是什么
// 如果有多个答案,返回字典序最小的那个
// 字典序看做所有数字拼起来的字符串字典序
// 比如
// 1, 10, 2... 拼起来是 1102...
// 1, 2, 3, 4... 拼起来是 1234...
// 认为 1, 10, 2...的字典序更小
// 如果给定的n和sum有答案返回一个N长度的答案数组
// 如果给定的n和sum无答案返回一个1长度的数组{ -1 }
// 输入 : N = 4, sum = 16
// 输出 : 3 1 2 4
// 输入 : N = 10, sum = 4116
// 输出 : 1 3 5 7 10 9 8 6 4 2
// 0 < n <= 10, sum随意
public class Code01_LexicographicalSmallestPermutation {
// 准备好杨辉三角形,作为每一项系数
public static int[][] moduluses = {
{},
{ 1 },
{ 1, 1 },
{ 1, 2, 1 },
{ 1, 3, 3, 1 },
{ 1, 4, 6, 4, 1 },
{ 1, 5, 10, 10, 5, 1 },
{ 1, 6, 15, 20, 15, 6, 1 },
{ 1, 7, 21, 35, 35, 21, 7, 1 },
{ 1, 8, 28, 56, 70, 56, 28, 8, 1 },
{ 1, 9, 36, 84, 126, 126, 84, 36, 9, 1 } };
// sums[1] = 只有1这个数字整出来的最大和不会超过sums[1]
// sum[i] = 只有1~i这些数字整出来的最大和不会超过sums[i]
public static int[] sums = { 0, 1, 3, 9, 24, 61, 148, 350, 808, 1837, 4116 };
public static int[] lsp(int n, int sum) {
if (n < 1 || n > 10 || sum > sums[n]) {
return new int[] { -1 };
}
int[][] dp = new int[1 << (n + 1)][sums[n] + 1];
if (!process(((1 << (n + 1)) - 1) ^ 1, sum, 0, n, moduluses[n], dp)) {
return new int[] { -1 };
}
int[] ans = new int[n];
int index = 0;
// n = 7
// 000..000 100000000
// 000..000 011111111
// 000..000 011111110
int status = ((1 << (n + 1)) - 1) ^ 1;
int rest = sum;
while (status != 0) {
ans[index] = dp[status][rest];
status ^= 1 << ans[index];
rest -= ans[index] * moduluses[n][index];
index++;
}
return ans;
}
// 一开始给你1 ~ 7
// 一开始的status : 000000..000011111110
// 1、2、3、4、5、6
// 当前还有2、4、5可用
// 6 5 4 3 2 1 0
// status : 0 1 1 0 1 0 0
// status : 还能用的数字都在status里
// rest : 还剩多少和,需要去搞定!
// index : 当前可能把status里剩下的某个数字拿出来用需要 * 系数的!
// modulus[index]就是当前的系数!
// 剩下的过程能不能搞定rest这个和
// 能搞定返回true不能搞定返回false
// 递归最终要的目的不仅仅是获得返回值dp表填好!
public static boolean process(int status, int rest, int index, int n, int[] modulus, int[][] dp) {
if (rest < 0) {
return false;
}
if (status == 0) { // 0000000000..000000000
return rest == 0 ? true : false;
}
// dp[status][rest] == 0 (status,rest)之前没算过!
// dp[status][rest] == -1 (status,rest)之前算过返回了false
// dp[status][rest] != -1 (status,rest)之前算过返回了true
// dp[status][rest] :
// 从字典序小的数字开始试,一旦试出来,记录当前是哪个数字试出来的!
if (dp[status][rest] != 0) {
return dp[status][rest] != -1;
}
// n < 10 1 2 3 4 5 6... status里得有
// n == 10 10 1 2 3 4 ... status里得有
// ans : 哪个数字试出来的!
int ans = -1;
if (n == 10 && (status & (1 << 10)) != 0) {
// 真的可以先试10
if (process(status ^ (1 << 10), rest - modulus[index] * 10, index + 1, n, modulus, dp)) {
ans = 10;
}
}
// ans == 10
if (ans == -1) {
for (int i = 1; i <= n; i++) {
// i : 1 2 3 ... n status得有才可以!
if ((status & (1 << i)) != 0) {
if (process(status ^ (1 << i), rest - modulus[index] * i, index + 1, n, modulus, dp)) {
ans = i;
break;
}
}
}
}
dp[status][rest] = ans;
return ans != -1;
}
public static void main(String[] args) {
int N1 = 4;
int sum1 = 16;
int[] ans1 = lsp(N1, sum1);
for (int num : ans1) {
System.out.print(num + " ");
}
System.out.println();
int N2 = 10;
int sum2 = 4116;
int[] ans2 = lsp(N2, sum2);
for (int num : ans2) {
System.out.print(num + " ");
}
System.out.println();
int N3 = 10;
int sum3 = 3688;
int[] ans3 = lsp(N3, sum3);
for (int num : ans3) {
System.out.print(num + " ");
}
System.out.println();
int N4 = 10;
int sum4 = 4013;
int[] ans4 = lsp(N4, sum4);
for (int num : ans4) {
System.out.print(num + " ");
}
System.out.println();
}
}