自从开始作公众号开始,就一直在思考,怎么把算法的训练作好,由于思海同窗在算法这方面的掌握确实还不够。所以,我如今想作一个“365算法每日学计划”。java
“计划”的主要目的: 一、想经过这样的方式监督本身更努力的学习算法。 二、想和小伙伴们“组团”一块儿来学习交流学习算法过程当中的点点滴滴。 “ 计划”的主要内容: 一、数据结构和算法的基础知识巩固。 二、逐步进阶的oj算法训练。 “计划”的时间安排:每周三和周六 文章有点长,但愿能耐心的看完。 ——说在前面 “算法每日学”计划01打卡: 问题描述 已知一个正整数N,问从1~N中任选出三个数,他们的最小公倍数最大能够为多少。 输入格式 输入一个正整数N。 输出格式 输出一个整数,表示你找到的最小公倍数。 样例输入 9 样例输出 504 数据规模与约定 1 <= N <= 106。
解题思路与实现面试
下面这个思路是“算法每日学交流社区”的小伙伴给出的,感谢小伙伴们的支持与关注。算法
思路分析:数据结构
最大 最小公倍数,联想到两个数的求最大最小公倍数,即两个数的乘积(注:连续的两个天然数是互斥的)。并发
一样,咱们能够拿最后三个数来作考虑。框架
1.当n为奇数时,n,n-1,n-2为奇偶奇,里面只有一个偶数,因此不会有2这个因子。这三个数相差不到3,因此也不会有因子3,故符合题意。数据结构和算法
2.当n为偶数时,n,n-1,n-2为偶奇偶,此时n,n-2确定含有因子2,因此除于2不值得。因此考虑将n-2 换成n-3,变成奇偶奇,此时也有一个问题,函数
n和n-3,若是n%3==0,则除于3更不值得。仍根据奇偶奇的原则,变更偶数n为n-2,此时换成n-1,n-2,n-3和1状况同样。故此时符合题意。学习
因此根据上面的分析,咱们能够写出下面的代码:测试
1import java.util.Scanner;
2public class Main{
3 public static void main(String[] args) {
4 Scanner scanner = new Scanner(System.in);
5 long n = scanner.nextLong();
6 long result;
7 if(n<3)
8 result=n;
9 else{
10 if(n%2!=0)
11 result=n(n-1)(n-2);
12 else if(n%3!=0)
13 result=n(n-1)(n-3);
14 else
15 result=(n-1)(n-2)(n-3);
16 }
17 System.out.println(result);
18 }
19}
其实,上面的算法是用到了贪心的思想,大概是这样的思路:
从最大的三个数开始考虑,若是最大的数为奇数,那么相邻的三个数中有两个奇数,最大公约数为1,最小公倍数就为n(n-1)(n-2). 若是为偶数,那么日后移,考虑n(n-1)(n-3),这时n和n-3相差3,式子知足条件的前提是n不能被3整除,不然结果只能是(n-1)(n-2)(n-3).
这个题目由于用到了贪心的思想,因此下面介绍一下贪心算法。
贪心算法
1、基本概念:
所谓贪心算法是指,在对问题求解时,老是作出在当前看来是最好的选择。也就是说,不从总体最优上加以考虑,他所作出的仅是在某种意义上的局部最优解。
贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对全部问题都能获得总体最优解,选择的贪心策略必须具有无后效性,即某个状态之后的过程不会影响之前的状态,只与当前状态有关。
因此对所采用的贪心策略必定要仔细分析其是否知足无后效性。
2、贪心算法的基本思路:
1.创建数学模型来描述问题。 2.把求解的问题分红若干个子问题。 3.对每一子问题求解,获得子问题的局部最优解。 4.把子问题的解局部最优解合成原来解问题的一个解。
3、贪心算法适用的问题
贪心策略适用的前提是:局部最优策略能致使产生全局最优解。
实际上,贪心算法适用的状况不多。通常,对一个问题分析是否适用于贪心算法,能够先选择该问题下的几个实际数据进行分析,就可作出判断。
4、贪心算法的实现框架
1 从问题的某一初始解出发;
2 while (能朝给定总目标前进一步)
3 {
4 利用可行的决策,求出可行解的一个解元素;
5 }
6 由全部解元素组合成问题的一个可行解;
5、贪心策略的选择
由于用贪心算法只能经过解局部最优解的策略来达到全局最优解,所以,必定要注意判断问题是否适合采用贪心算法策略,找到的解是否必定是问题的最优解。
6、例题分析
下面是一个能够试用贪心算法解的题目,贪心解的确不错,惋惜不是最优解。
例题① [背包问题] 有一个背包,背包容量是M=150。有7个物品,物品能够分割成任意大小。 要求尽量让装入背包中的物品总价值最大,但不能超过总容量。 物品 A B C D E F G 重量 35 30 60 50 40 10 25 价值 10 40 30 50 35 40 30
分析: 目标函数: ∑pi最大 约束条件是装入的物品总重量不超过背包容量:∑wi<=M( M=150) (1)根据贪心的策略,每次挑选价值最大的物品装入背包,获得的结果是否最优? (2)每次挑选所占重量最小的物品装入是否能获得最优解? (3)每次选取单位重量价值最大的物品,成为解本题的策略。
值得注意的是,贪心算法并非彻底不可使用,贪心策略一旦通过证实成立后,它就是一种高效的算法。 贪心算法仍是很常见的算法之一,这是因为它简单易行,构造贪心策略不是很困难。
惋惜的是,它须要证实后才能真正运用到题目的算法中。
通常来讲,贪心算法的证实围绕着:整个问题的最优解必定由在贪心策略中存在的子问题的最优解得来的。 对于例题中的3种贪心策略,都是没法成立(没法被证实)的,解释以下:
(1)贪心策略:选取价值最大者。反例:
W=30 物品:A B C 重量:28 12 12 价值:30 20 20
根据策略,首先选取物品A,接下来就没法再选取了,但是,选取B、C则更好。
(2)贪心策略:选取重量最小。它的反例与第一种策略的反例差很少。 (3)贪心策略:选取单位重量价值最大的物品。反例:
W=30 物品:A B C 重量:28 20 10 价值:28 20 10
根据策略,三种物品单位重量价值同样,程序没法依据现有策略做出判断,若是选择A,则答案错误。
例题②[均分纸牌]有N堆纸牌,编号分别为1,2,…,n。每堆上有若干张,但纸牌总数必为n的倍数.能够在任一堆上取若干张纸牌,而后移动。移牌的规则为:在编号为1上取的纸牌,只能移到编号为2的堆上;在编号为n的堆上取的纸牌,只能移到编号为n-1的堆上;其余堆上取的纸牌,能够移到相邻左边或右边的堆上。如今要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都同样多。例如:n=4,4堆纸牌分别为:① 9 ② 8 ③ 17 ④ 6 移动三次能够达到目的:从③取4张牌放到④ 再从③区3张放到②而后从②去1张放到①。
输入输出样例:4
9 8 17 6
屏幕显示:3
算法分析:设a[i]为第I堆纸牌的张数(0<=I<=n),v为均分后每堆纸牌的张数,s为最小移动次数。
咱们用贪心算法,按照从左到右的顺序移动纸牌。如第I堆的纸牌数不等于平均值,则移动一次(即s加1),分两种状况移动:
1.若a[i]>v,则将a[i]-v张从第I堆移动到第I+1堆; 2.若a[i]<v,则将v-a[i]张从第I+1堆移动到第I堆。
为了设计的方便,咱们把这两种状况统一看做是将a[i]-v从第I堆移动到第I+1堆,移动后有a[i]=v; a[I+1]=a[I+1]+a[i]-v.
在从第I+1堆取出纸牌补充第I堆的过程当中可能回出现第I+1堆的纸牌小于零的状况。
如n=3,三堆指派数为1 2 27 ,这时v=10,为了使第一堆为10,要从第二堆移9张到第一堆,而第二堆只有2张能够移,这是否是意味着刚才使用贪心法是错误的呢?
咱们继续按规则分析移牌过程,从第二堆移出9张到第一堆后,第一堆有10张,第二堆剩下-7张,在从第三堆移动17张到第二堆,恰好三堆纸牌都是10,最后结果是对的,咱们在移动过程当中,只是改变了移动的顺序,而移动次数不便,所以此题使用贪心法可行的。
Java源程序
1public class Greedy {
2 public static void main(String[] args) {
3 int n = 0, avg = 0, s = 0;
4 Scanner scanner = new Scanner(System.in);
5 ArrayList<Integer> array = new ArrayList<Integer>();
6 System.out.println("Please input the number of heaps:");
7 n = scanner.nextInt();
8 System.out.println("Please input heap number:");
9 for (int i = 0; i < n; i++) {
10 array.add(scanner.nextInt());
11 }
12 for (int i = 0; i < array.size(); i++) {
13 avg += array.get(i);
14 }
15 avg = avg / array.size();
16 System.out.println(array.size());
17 System.out.println(avg);
18 for (int i = 0; i < array.size() - 1; i++) {
19 s++;
20 array.set(i + 1, array.get(i + 1) + array.get(i) - avg);
21 }
22 System.out.println("s:" + s);
23 }
24}
利用贪心算法解题,须要解决两个问题:
一是问题是否适合用贪心法求解。咱们看一个找币的例子,若是一个货币系统有三种币值,面值分别为一角、五分和一分,求最小找币数时,能够用贪心法求解;若是将这三种币值改成一角一分、五分和一分,就不能使用贪心法求解。用贪心法解题很方便,但它的适用范围很小,判断一个问题是否适合用贪心法求解,目前尚未一个通用的方法,在信息学竞赛中,须要凭我的的经验来判断。
二是肯定了能够用贪心算法以后,如何选择一个贪心标准,才能保证获得问题的最优解。在选择贪心标准时,咱们要对所选的贪心标准进行验证才能使用,不要被表面上看似正确的贪心标准所迷惑,以下面的例子。
例题③[最大整数]设有n个正整数,将它们链接成一排,组成一个最大的多位整数。
例如:n=3时,3个整数13,312,343,连成的最大整数为34331213。
又如:n=4时,4个整数7,13,4,246,连成的最大整数为7424613。
输入:n N个数 输出:连成的多位数
算法分析:此题很容易想到使用贪心法,在考试时有不少同窗把整数按从大到小的顺序链接起来,测试题目的例子也都符合,但最后测试的结果却不全对。按这种标准,咱们很容易找到反例:12,121应该组成12121而非12112,那么是否是相互包含的时候就从小到大呢?也不必定,如12,123就是12312而非12123,这种状况就有不少种了。是否是此题不能用贪心法呢?
其实此题能够用贪心法来求解,只是刚才的标准不对,正确的标准是:先把整数转换成字符串,而后在比较a+b和b+a,若是a+b>=b+a,就把a排在b的前面,反之则把a排在b的后面。
java源程序
1public static void main(String[] args) {
2 String str = "";
3 ArrayList<String> array = new ArrayList<String>();
4 Scanner in = new Scanner(System.in);
5 System.out.println("Please input the number of data:");
6 int n = in.nextInt();
7 System.out.println("Please input the data:");
8 while (n-- > 0) {
9 array.add(in.next());
10 }
11 for (int i = 0; i < array.size(); i++)
12 for (int j = i + 1; j < array.size(); j++) {
13 if ((array.get(i) + array.get(j)).compareTo(array.get(j) + array.get(i)) < 0) {
14 String temp = array.get(i);
15 array.set(i, array.get(j));
16 array.set(j, temp);
17 }
18 }
19 for (int i = 0; i < array.size(); i++) {
20 str += array.get(i);
21 }
22 System.out.println("str=:" + str);
23 }
24}
贪心算法所做的选择能够依赖于以往所做过的选择,但决不依赖于未来的选择,也不依赖于子问题的解,所以贪心算法与其余算法相比具备必定的速度优点。若是一个问题能够同时用几种方法解决,贪心算法应该是最好的选择之一。
这就是贪心的一些思想和基本的应用了,若是还有什么问题,能够留言或者私信我!
参考资料
https://blog.csdn.net/a925907195/article/details/41314549
往期推荐
“面试不败计划”: java语言基础面试题(二) ”365算法每日学计划”:02打卡-线性表(赠书活动第①期预告) 并发基础篇(一): 线程介绍