「365算法每日學計劃」:03打卡-貪心算法

自從開始作公衆號開始,就一直在思考,怎麼把算法的訓練作好,由於思海同窗在算法這方面的掌握確實還不夠。所以,我如今想作一個「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打卡-線性表(贈書活動第①期預告)
併發基礎篇(一): 線程介紹

圖片描述

相關文章
相關標籤/搜索