一.问题:
卡拉兹(Callatz)猜想已经在1001中给出了描述。在这个题目里,情况稍微有些复杂。
当我们验证卡拉兹猜想的时候,为了避免重复计算,可以记录下递推过程中遇到的每一个数。例如对 n=3 进行验证的时候,我们需要计算 3、5、8、4、2、1,则当我们对 n=5、8、4、2 进行验证的时候,就可以直接判定卡拉兹猜想的真伪,而不需要重复计算,因为这 4 个数已经在验证3的时候遇到过了,我们称 5、8、4、2 是被 3“覆盖”的数。我们称一个数列中的某个数 n 为“关键数”,如果 n 不能被数列中的其他数字所覆盖。
现在给定一系列待验证的数字,我们只需要验证其中的几个关键数,就可以不必再重复验证余下的数字。你的任务就是找出这些关键数字,并按从大到小的顺序输出它们。
输入格式:
每个测试输入包含 1 个测试用例,第 1 行给出一个正整数 K (<100),第 2 行给出 K 个互不相同的待验证的正整数 n (1<n≤100)的值,数字间用空格隔开。
输出格式:
每个测试用例的输出占一行,按从大到小的顺序输出关键数字。数字间用 1 个空格隔开,但一行中最后一个数字后没有空格。
输入样例:
6
3 5 6 7 8 11
输出样例:
7 6
二.解题思路:
创建数组nums存储输入的待验证数字,从nums第一个数开始遍历每一个待验证数字,每个待验证数字进行卡拉兹运算,每次进行卡拉兹运算得到的结果与nums所有待验证的字进行比较,如果发现有值相同的待验证数,则说明该待验证数不是关键数,将其赋值为1,将所有数检测完毕之后,数组nums中不为1的数即为关键数,将这些数字取出放入新数组key_num,然后排序输出。
三.个人答案(C语言实现):
#include <stdio.h>
// 用于检验检查数组nums里面是否有和n一样的数字nums[i],有则将nums[i]的值改为1
void judge_keyNum(int n, int *nums, int K)
{
for (int j = 0; j < K; j++)
{
if (nums[j] == n)
{
nums[j] = 1;
}
}
}
// 用于将经过judge_keyNum处理后的数组nums中的非1元素取出添加到key_num里面,同时函数返回关键数个数
int add_keyNum(int *nums, int *key_num, int K)
{
int i = 0;
for (int j = 0; j < K; j++)
{
if (nums[j] != 1)
{
key_num[i] = nums[j];
i++;
}
}
return i;
}
// 用于将key_num里面的关键数按从大到小顺序重新排列
void sort(int *key_num, int len)
{
for (int i = 0; i < len - 1; i++)
{
for (int j = 0; j < len - 1 - i; j++)
{
if (key_num[j] < key_num[j + 1])
{
int t;
t = key_num[j + 1];
key_num[j + 1] = key_num[j];
key_num[j] = t;
}
}
}
}
int main()
{
int K; // 用于存储用户输入的待验证的数字数量
int nums[101]; // 用于存储用户输入的一系列待验证的数字
int key_num[101]; // 用于存储处理得到的关键数
// 输入数据,K以及K个待验证的数字,输入时,待验证的数字之间用空格分隔
scanf("%d", &K);
for (int i = 0; i < K; i++)
{
scanf("%d", &nums[i]);
}
// 遍历数组key_num里面的所有待验证的数字
int i = 0;
// 由于待验证的正整数 n 的值满足1< n ≤100,因此便利条件可设为nums[i]不为0
for (; nums[i] != 0; i++)
{
// 进行卡拉兹猜想验证
int n = nums[i];
while (n != 1)
{
if (n % 2 == 0) // n为偶数,n = n/2
{
n /= 2;
}
else
{
n = (3 * n + 1) / 2; // n为奇数,n = (3*n+1)/2
}
// 检查数组nums里面是否有和n一样的数字nums[i],有则说明nums[i]一定不是关键数,nums[i]变为1,没有nums[i]不一定是关键数,继续操作
judge_keyNum(n, nums, K);
}
}
// 将经过judge_keyNum处理后的数组nums中的非1元素取出添加到key_num里面,同时函数返回关键数个数
int len = add_keyNum(nums, key_num, K);
// 将key_num里面的关键数按从大到小顺序重新排列
sort(key_num, len);
// 输出关键数,由于add_keyNum()函数将关键数添加到数组key_num里面,此时key_num里面的非0元素就是关键数
for (int i = 0; i < len; i++)
{
if (i != len - 1)
{
printf("%d ", key_num[i]);
}
else
{
printf("%d\n", key_num[i]);
}
}
return 0;
}
四.2023.11.12新的解题C代码版本:
提刷多了后,反过来看当初做的,发现有点麻烦,可以定义一个哈希表标记数字,简单好多,同时排序用快速排序的算法,速度提升好多
#include <stdio.h>
// 定义一个快速排序的函数用于对结果进行排序。
void quick_sort(int arr[],int left,int right)
{
int i = left;
int j = right;
int temp = arr[left];
int t = 0;
if(i > j)
{
return;
}
while(i != j)
{
// j端 向前搜索,找到第一个比temp小的数
while(arr[j] <= temp && i < j)
{
j--;
}
// i端 向后搜索,找到第一个比temp大的数
while(arr[i] >= temp && i < j)
{
i++;
}
// 如果 i < j,交换 arr[i] 和 arr[j] 的值
if(i < j)
{
t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
// 将 temp 放到根据 arr[i], arr[j] 调整完位置后的准确位置
arr[left] = arr[i];
arr[i] = temp;
// 对 temp 左右两边的序列继续进行上述排序
quick_sort(arr,left,i-1);
quick_sort(arr,i+1,right);
}
}
int main()
{
//输入数据
int K = 0; // K 存储给定的正整数个数
int n = 0; // n 用于存放每次输入的待验证的数字
scanf("%d",&K);
int num[101] = {0}; // num[] 存放输入的待验证的数字
int array[105] = {0}; // array[] 用于标记对应的数被覆盖
for(int i = 0;i < K;i++)
{
scanf("%d",&num[i]); // 接收待验证的数字
n = num[i];
// 对输入的 n 进行卡拉兹运算
while(n != 1)
{
if(n % 2 == 0) // 当 n 为偶数时
{
n /= 2;
// 如果数的范围小于等于100且大于1,则标记为已覆盖
if(n <= 100 && n > 1)
{
array[n]++;
}
}
else // 当 n 为奇数时
{
n = (3 * n + 1) / 2;
// 如果数的范围小于等于100且大于1,则标记为已覆盖
if(n <= 100 && n > 1)
{
array[n]++;
}
}
}
}
int result[101] = {0}; // 用于存储关键数
int count = 0; // 用于记录关键数的数量
for(int j = 0;j < K;j++)
{
// 如果待验证的数字在 array 数组中未被标记(未被覆盖),且值小于等于100,则将其存入 result 中
if(array[num[j]] == 0 && num[j] <= 100)
{
result[count] = num[j];
count++;
}
}
// 对关键数进行排序
quick_sort(result,0,count-1);
// 输出关键数
// 最后一个数后面没有空格
for(int k = 0;k < count;k++)
{
if(k == count - 1)
{
printf("%d\n",result[k]);
}
else
{
printf("%d ",result[k]);
}
}
return 0;
}