题目
试题 算法训练 最大最小公倍数
资源限制
内存限制:256.0MB C/C++时间限制:1.0s Java时间限制:3.0s Python时间限制:5.0s
问题描述
已知一个正整数N,问从1~N中任选出三个数,他们的最小公倍数最大可以为多少。
输入格式
输入一个正整数N。
输出格式
输出一个整数,表示你找到的最小公倍数。
样例输入
9
样例输出
504
数据规模与约定
1 <= N <= 106。
分析
题目的意思是在一系列的数字里面任意找三个数,使这三个数的公倍数在所有其他组合中最大;
我写代码的时候称这三个数为最优数;
思路
怎么找这三个最优数呢?
暴力法
首先肯定是万能的暴力法,但是这数据规模,也不是不行。。
暴力首先要有个合理的下限,
于是就想到了找三个最大的质数,因为质数之间没有共同的非1公因子;
但可惜,从样例中就可以发现504 = 9 * 8 * 7
,其中9、8都不是质数,所以肯定只找质数肯定不是最优解;
不过三个最大的质数肯定是一个查找下限了,然后结合暴力,好,完结撒花!!
怎么可能暴力
很明显,上面找三个质数作为下限是很繁琐的,在没找到三个质数之前,需要判断每个数字是否是质数,就比如样例中,要先找到7,5,3这三个质数才能以9为上限,3为下限进行查找,结果解是 9、8、7,这5和3根本没用
于是思考为什么9、8、7会是最优解?开始对其中进行因子分解
- 9 => 9、3、1
- 8 => 8、4、2、1
- 7 => 7、1
明显,其中的因子都不相同,这就是一个很好的切入点,质数其实只是其中的一个特例。开敲!
第一版代码(有误)
第一次编写并没有进行暴力,所以有点问题
import java.util.ArrayList;
import java.util.Scanner;
class Main {
private static ArrayList<Integer> yinziList = new ArrayList<Integer>();
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int nextInt = scanner.nextInt();
scanner.close();
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = nextInt; i >= 2; i --) {
if (arrayList.size() == 3) {
break;
}
if (fun(i)) {
arrayList.add(i);
}
}
long max = 1;
for (Integer integer : arrayList) {
max *= integer;
}
System.out.println(max);
}
/**
* 返回true说明 num 需要作为最好选择,否则视为有更好选择
* @param num
* @return
*/
public static boolean fun(int num) {
int end = (int) Math.sqrt(num);
ArrayList<Integer> arrayList = new ArrayList<>();
// 判断1 没有意义
for (int i = 2; i < end + 1; i++) {
if (num % i == 0) {
if (yinziList.contains(i)) {
// sorry, 你这个数字不行
return false;
} else {
arrayList.add(i);
}
}
}
// 合并两个数组
yinziList.addAll(arrayList);
return true;
}
}
写完很开心哈,一提交。。。问题来了,60的通过率!!!
错误的思路
在第一版的时候,只是单纯的将因子不相同的最大三个数作为最优解了,这样就会导致最大的数字会直接被取,因为最大的数字包含了一些因子,导致中间一连串的数字都去不了,
比如说 N = 12
时,会默认先取12,导致10、9、8因为其中包含的12的因子都不能取,最后只取了 12、11、7
但明显最优解是11、10、9
于是我开始使用了暴力,把12、11、7作为下限,在12到7之间执行同样的算法,即
修改后(只贴main函数):
private static ArrayList<Integer> yinziList = new ArrayList<Integer>();
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int nextInt = scanner.nextInt();
scanner.close();
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = nextInt; i >= 2; i --) {
if (arrayList.size() == 3) {
break;
}
if (fun(i)) {
arrayList.add(i);
}
}
long max = 1;
for (Integer integer : arrayList) {
max *= integer;
}
/**
* 修复第一次取得并不是不是最佳数字
*/
int start = arrayList.get(0);
int end = arrayList.get(arrayList.size()-1);
arrayList.clear();
yinziList.clear();
for (int i = start - 1; i > end; i--) {
if (arrayList.size() == 3) {
long temp = 1;
for (Integer integer : arrayList) {
temp *= integer;
}
arrayList.clear();
yinziList.clear();
if (temp>max) {
max = temp;
}
}
if (fun(i)) {
arrayList.add(i);
}
}
System.out.println(max);
}
果然提交了,通过率100
代码好冗余,查找下限和暴力的算法大差不差,就合并一下
private static ArrayList<Integer> yinziList = new ArrayList<Integer>();
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
scanner.close();
int start = N;
int end = 1;
long max = 1;
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = start; i > end; i --) {
if (arrayList.size() == 3) {
long temp = 1;
for (Integer integer : arrayList) {
temp *= integer;
}
max = temp > max ? temp : max;
// 缩小搜索范围
start = arrayList.get(0);
end = arrayList.get(arrayList.size() - 1);
i = start - 1;
// 清除缓存
arrayList.clear();
yinziList.clear();
}
if (fun(i)) {
arrayList.add(i);
}
}
System.out.println(max);
}
写完后,思路就更清晰了
正确的思路
同样的,还是以
明显,其中的因子都不相同,这就是一个很好的切入点,质数其实只是其中的一个特例。
作为主要思路,通过上限减少,不断搜索下限,然后缩小下限
如N = 12
开始上限是12 、下限是1
经过第一次搜索
找到 12、11、7,修改上限为11、下限为7
第二次搜索,找到11、10、9,范围已经最小,则这个为最优解
代码可以参考上面修改的,当然其中,有很多能提前结束搜索的条件没细划
优化
搜索逻辑
其实就是我的fun函数,写代码的时候用的查找质数的思维,再放入了自己的逻辑
/**
* 返回true说明 num 需要作为最好选择,否则视为有更好选择
* @param num
* @return
*/
public static boolean fun(int num) {
int end = (int) Math.sqrt(num);
ArrayList<Integer> arrayList = new ArrayList<>();
// 判断1 没有意义
for (int i = 2; i < end + 1; i++) {
if (num % i == 0) {
if (yinziList.contains(i)) {
// sorry, 你这个数字不行
return false;
} else {
arrayList.add(i);
}
}
}
// 合并两个数组
yinziList.addAll(arrayList);
return true;
}
写完代码后发现,只要对第一个数字进行因子搜索,而后面的数字可以通过前面已列入的因子数列判断是否是最优数,于是可以在查找因子前进行一次简单的过滤
如下:
/**
* 返回true说明 num 需要作为最好选择,否则视为有更好选择
* @param num
* @return
*/
public static boolean fun(int num) {
for (Integer yinzi : yinziList) {
if (num % yinzi == 0) {
// 说明存在共同因子
return false;
}
}
int end = (int) Math.sqrt(num);
ArrayList<Integer> arrayList = new ArrayList<>();
// 判断1 没有意义
for (int i = 2; i < end + 1; i++) {
if (num % i == 0) {
if (yinziList.contains(i)) {
// sorry, 你这个数字不行
return false;
} else {
arrayList.add(i);
}
}
}
// 合并两个数组
yinziList.addAll(arrayList);
return true;
}
最后代码
import java.util.ArrayList;
import java.util.Scanner;
class Main {
private static ArrayList<Integer> yinziList = new ArrayList<Integer>();
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
scanner.close();
int end = 1;
long max = 1;
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = N; i > end; i --) {
// 搜索结束,判断结果
if (arrayList.size() == 3) {
long temp = 1;
for (Integer integer : arrayList) {
temp *= integer;
}
max = temp > max ? temp : max;
// 缩小搜索范围
i = arrayList.get(0) - 1;
end = arrayList.get(arrayList.size() - 1);
// 清除缓存
arrayList.clear();
yinziList.clear();
}
if (fun(i)) {
arrayList.add(i);
}
}
System.out.println(max);
}
/**
* 返回true说明 num 需要作为最好选择,否则视为有更好选择
* @param num
* @return
*/
public static boolean fun(int num) {
int end = (int) Math.sqrt(num);
ArrayList<Integer> arrayList = new ArrayList<>();
for (Integer yinzi : yinziList) {
if (num % yinzi == 0) {
// 说明存在共同因子
return false;
}
}
// 判断1 没有意义
for (int i = 2; i < end + 1; i++) {
if (num % i == 0) {
if (yinziList.contains(i)) {
// sorry, 你这个数字不行
return false;
} else {
arrayList.add(i);
}
}
}
// 合并两个数组
yinziList.addAll(arrayList);
return true;
}
}