leetcode 263 & 264是關於ugly number的; java
https://leetcode.com/problems/ugly-number/算法
https://leetcode.com/problems/ugly-number-ii/數組
簡單的263,給定一個數,判斷該數是否ugly;這個比較直觀,只須要判斷該數是否只能被2, 3, 5整除便可;學習
public boolean isUgly(int num) { if (num <= 0) { return false; } if (num == 1) { return true; } if (num % 2 == 0) { return isUgly(num / 2); } if (num % 3 == 0) { return isUgly(num / 3); } if (num % 5 == 0) { return isUgly(num / 5); } return false; }
264要求找出第n個ugly number;若是直接使用前面的算法,依次遞增數字,判斷該數字是否ugly,直到第n個爲止,能夠獲得正確的答案,但在n比較大的時候,確定會出現TLE的問題;測試
這個問題有多種解法,從最笨拙的,到精巧的,再到更加精巧和更高效率的;調試
使用一個數組用來存放n個ugly number,假設處理到第i個數字,x,那麼下一個數字必然知足: 假設有三個ugly number, a, b, c, 且 2 * a > x, 3 * b > x, 5 * c > x,那麼下一個數字確定爲2 * a, 3 * b, 5 * c中得最小的值;個人想法是如何找到a,b, c;最簡單的方式是從頭到i得遍歷,那麼這樣話,最後會獲得一個O(n * n)的算法;注意到i以前的數字是升序排列的,因此能夠用二分查找,那麼最後會獲得一個O(n * log n)的算法;code
public int nthUglyNumber(int n) { if (n <= 5) { return n; } int[] zs = {2, 3, 5}; int[] nums = new int[n + 1]; int i = 1; for (; i <= 5; i++) { nums[i] = i; } int x = 5; while (i <= n) { int nextMin = Integer.MAX_VALUE; for (int z : zs) { int y = x / z; int j = nextPosGe(nums, y, 1, i); while (nums[j] * z <= x) { j = j + 1; } nextMin = Math.min(nextMin, nums[j] * z); } x = nextMin; nums[i] = x; i += 1; } return nums[n]; } private int nextPosGe(int[] nums, int y, int start, int end) { int i = start, j = end - 1; while (i <= j) { int mid = (i + j) / 2; if (nums[mid] < y) { i = mid + 1; } else { j = mid - 1; } } return i; }
這個算法是leetcode上面,別人分享的 https://leetcode.com/discuss/52746/java-solution-using-priorityqueue-o-n ,使用PriorityQueue的O(n)的算法;想象一下,若是把全部的(前n個)ugly number都放到了PQ裏面,那麼每次poll,均可以獲得目前爲止最大的(也是PQ中目前最小的)的數字;假設爲x,同時 2 * x, 3 * x, 5 * x,確定也在PQ中;因此這個算法,從PQ不斷的poll,同時把當前number的2, 3, 5倍也放進去,那麼poll了n次之後,獲得的就是第n個ugly number;這個算法直觀且易於理解,代碼簡單優美;由於PQ offer的時間複雜度是O(log n),因此它的時間複雜度也是 O(n * log n);另外由於PQ自己比數組要複雜,因此比前面的算法要慢一些(600ms vs 400ms);leetcode
public int nthUglyNumber(int n) { PriorityQueue<Long> q = new PriorityQueue<Long>(); q.offer(1l); long cur = 0; while(n-->0){ while(q.peek()==cur){ q.poll(); } cur = q.poll(); q.offer(cur*2); q.offer(cur*3); q.offer(cur*5); } return (int)cur; }
第三種是最精巧也最高效的算法;https://leetcode.com/discuss/52716/o-n-java-solutionget
public int nthUglyNumber2(int n) { int[] ugly = new int[n]; ugly[0] = 1; int index2 = 0, index3 = 0, index5 = 0; int factor2 = 2, factor3 = 3, factor5 = 5; for (int i = 1; i < n; i++) { int min = Math.min(Math.min(factor2, factor3), factor5); ugly[i] = min; if (factor2 == min) factor2 = 2 * ugly[++index2]; if (factor3 == min) factor3 = 3 * ugly[++index3]; if (factor5 == min) factor5 = 5 * ugly[++index5]; } return ugly[n - 1]; }
這個算法是O(n)的,固然也是最快的,用了340ms(沒有比第一個快多少,呵呵);簡單的分析如下:對於任何一個ugly number x,能夠表示爲 x = 2 ** a * 3 ** b * 5 ** c; (** 表示power);其中a, b, c 從0開始遞增;我想到了這一點,但我始終沒有想清楚要怎麼樣遞增a,b, c以獲得這個序列;直到我看到了這個算法;it
一點心得:
leetcode對於這種代碼分享要open的多;並且對於測試用例也open的多,當在某個測試上面失敗的時候,它會告訴你輸入是什麼,結果應該是什麼;並不擔憂那些做弊的程序;其實也沒有什麼可擔憂的吧;因此,對於調試程序,不斷改進頗有幫助;不少程序,我就是這樣作出來的;同時,還能夠看到別人分享的代碼,有一些程序真的不會作,像那些要作位與運算的,就能夠參考別人的代碼,學習,也能取得一些進步吧;
看別人的代碼,特別是比本身想法更好的代碼,真的能夠學到很多;也是寫程序的一種樂趣;