LeetCode上做題之體會(三)

1、判斷一個數能不能被4開方

Power of Four
Given an integer (signed 32 bits), write a function to check whether it is a power of 4.
Example:
Given num = 16, return true. Given num = 5, return false.
Follow up:
Could you solve it without loops/recursion?

要是題目沒有那侷限制的話相信很多人一看到題目都會直接使用類方法(庫函數)或者用循環/遞歸來解決這道題的吧。要是以前的我的話也會是這樣做的,但是做這種類型的題目多了之後就會發現,自己不會老是喜歡用一種方法來解決一種類型的問題。

以下是我的想法:

    public boolean isPowerOfFour(int num) {
        if(num == 1) return true;
        if((num % 10 == 4 || num % 10 == 6) && Integer.bitCount(num) == 1 && num > 0)
            return true;
        else
            return false;
    }

唉!我還是沒能想到下面這種用二進制數操作來解決問題!)下面確實是一個更快,效率更高的代碼:

    public boolean isPowerOfFour(int num) {
        return num > 0 && (num&(num-1)) == 0 && (num & 0x55555555) != 0;
    }
解釋一下上面爲何還要與一下0x55555555,由於num & (num - 1)只保證了二進制位中只出現一個1,但是沒有保證這個1會出現在奇數位,這個跟我用的bitCount()函數其實是一樣的意思。有了最後num & 0x55555555就保證了這個這個1一定會出現奇數位上。

2、以集合式形式輸出楊輝三角

Pascal’s Triangle
Given numRows, generate the first numRows of Pascal’s triangle.
For example, given numRows = 5,
Return
[
[1],
[1,1],
[1,2,1],
[1,3,3,1],
[1,4,6,4,1]
]

//簡潔的代碼:
    public List<List<Integer>> generate(int numRows){
        List<List<Integer>> a = new ArrayList<List<Integer>>();
        ArrayList<Integer> b = new ArrayList<>();
        for(int i = 0; i < numRows; i++){
            b.add(0,1);
            for(int j = 1; j < b.size() - 1; j++){
                b.set(j,b.get(j) + b.get(j+1));
            }
            a.add(new ArrayList(b));
        }
        return a;
    }
//比較長的代碼:
    public List<List<Integer>> generate(int numRows) {
        List<List<Integer>> finalLs = new ArrayList  <List<Integer>> ();
        if(numRows==0)
            return finalLs;
        List<Integer> ls;
        if(numRows>0) {
            ls = new ArrayList<Integer>();
            ls.add(1);
            finalLs.add(ls);
        }
        if(numRows>1) {
            ls= new ArrayList<Integer>();
            ls.add(1);
            ls.add(1);      
            finalLs.add(ls);
        }
        int j=2;
        if(numRows>2){
            while(numRows>j) {   
                ls= new ArrayList<Integer>();
                ls.add(1);
                List <Integer> lsprev= finalLs.get(j-1);
                for(int i = 1; i < lsprev.size(); i+=1)
                    ls.add(lsprev.get(i-1)+lsprev.get(i));
                ls.add(1);
                finalLs.add(ls);
                j++;
            }
        }
        return finalLs;
    }

以上兩端代碼中,簡潔的代碼在提交的時候運行時間是2ms,後者在提交之後的運行時間是1ms,我認爲後者其實是跟前者是一樣的意思和思路,只是多創建了幾個中間集合而已。而這裏面的運行時間問題,我最後發現,原來這道題LeetCode上的測試用例只有15個,要是多的話,應該還是差不多的時間。對於我來說,還是更傾向於用前者。

3、數素數

Count Prime
Count the number of prime numbers less than a non-negative number, n.

又是一道題目很簡單的題目。最簡單的方法,以前剛學習語言的時候,會遇到判斷一個數是否爲素數的問題。在這個問題中要是用那個方法的話,時間複雜度就爲O(n^2)。
能不能優化一下呢?
其實很容易我們就會想到,由於每個因子的取值都是小於√n的,所以被把循環邊界從n變爲√n之後我們確實能把複雜度降下來,但是降得還是不明顯,最後時間複雜度還是O(n^1.5)這樣的時間複雜度相對來說是小了點。
但是能不能再優化一下呢?
其實還有一種方法可以優化這個問題:篩子法Sieve of Eratosthenes
這是一種最有效的方法之一,下面有個演示圖,根據這個圖看的話,比較容易理解這個方法。
算法規模爲121,此圖來自於維基百科
首先,將2的倍數標記,之後再將3的倍數標記,再接着是5、7、11、13、……,其實就是一個遞歸的方式,每一次找到一個素數之後這個素數的倍數將不再是素數了。就把他們標記掉。這其實是一個用空間來換時間的一個做法。該算法的空間複雜度爲O(n),時間複雜度爲O(nlog log n)。

出題人貼的代碼:
public int countPrimes(int n) {
   boolean[] isPrime = new boolean[n];
   for (int i = 2; i < n; i++) {
      isPrime[i] = true;
   }
   for (int i = 2; i * i < n; i++) {
      if (!isPrime[i]) continue;
      for (int j = i * i; j < n; j += i) {
         isPrime[j] = false;
      }
   }
   int count = 0;
   for (int i = 2; i < n; i++) {
      if (isPrime[i]) count++;
   }
   return count;
}
簡潔的代碼實現:
    public int countPrimes(int n) {
        boolean[] notPrime = new boolean[n];
        int count = 0;
        for (int i = 2; i < n; i++) {
            if (notPrime[i] == false) {
                count++;
                for (int j = 2; i*j < n; j++) {
                    notPrime[i*j] = true;
                }
            }
        }
        return count;
    }

(對了,上面邊界判定沒有用根號是爲了避免重複使用“高耗”函數sqrt();)

4、整型斷數(也許從這個名字上很難理解這道題是講什麼的)

Integer Break
Given a positive integer n, break it into the sum of at least two positive integers and maximize the product of those integers. Return the maximum product you can get.
For example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4).
Note: you may assume that n is not less than 2.

它給的兩個提示是這樣的:
1、There is a simple O(n) solution to this problem.
2、You may check the breaking results of n ranging from 7 to 10 to discover the regularities.

對比前面幾個我們就會發現規律4(2+2),5(2+3),6(3+3),7(2+2+3),8(2+3+3),9(3+3+3),10(3+3+4)。仔細觀察就會發現,其實就會發現,從7之後每一個數就是前第三個數多一個數字3,這樣規律是算找到了
以下是我的代碼:

    public int integerbreak(int n){
        if(n <= 2) return 1;
        if(n == 3) return 2;
        if(n == 4) return 4;
        int[] num = new int[n+1];
        num[2] = 2;
        num[3] = 3;
        num[4] = 4;
        for(int i = 5; i <= n; i++){
            num[i] = 3 * num[i-3];
        }
        return num[n];
    }

下面這個是按照另外一種思路來的代碼經過我的測試,下面這段代碼很有可能就是LeetCode用來做參考的代碼(因爲我試了一個結果超出整型範圍的數,結果我上面的代碼和答案不一樣):

    public int integerBreak(int n) {
        int[] dp = new int[n + 1];
        dp[1] = 1;
        for(int i = 2; i <= n; i++) {
            for(int j = 1; 2*j <= i; j++) {
                dp[i] = Math.max(dp[i], (Math.max(j,dp[j])) * (Math.max(i - j, dp[i - j])));
            }
        }
        return dp[n];
    }

這裏的思路是這樣的,舉個例子,6=2+2+2=3+3,這裏面就會比較2*2*2和3*3哪個比較大。這樣到後面超出整型範圍的,還是超不過那最後一個最大的數(2147445847)。

5、翻轉字符串中指定部分

Reverse Vowels of a String
Write a function that takes a string as input and reverse only the vowels of a string.
Example 1:
Given s = “hello”, return “holle”.
Example 2:
Given s = “leetcode”, return “leotcede”.

首先,這道題上來就和之前做過的一道反轉題目有點相像,但是又有點不同,不知爲何,我首先想到的是用另外的空間直接將元音字母提取出來,在將他們反轉之後一個一個地塞回去。我在提交之後再去看別人寫的代碼,纔想到其實直接在字符串上面反轉就ok了。以下是上面兩種方法的實現,其實,第二種雖然有三個循環,但是時間複雜度是小於第一種的。

這個是我的方法實現(87ms):

public class Solution {
    static final String vowels = "aeiouAEIOU";

    public String reverseVowels(String s) {
        StringBuilder anos = new StringBuilder();
        char[] array = s.toCharArray();
        for(int i = 0; i < s.length(); i++){
            if(vowels.indexOf(array[i]) != -1){
                anos.append(array[i]);
            }
        }
        anos.reverse();
        for(int i = 0; i < s.length(); i++){
            if(vowels.indexOf(array[i]) != -1){
                array[i] = anos.charAt(0);
                anos.deleteCharAt(0);
            }
        }
        return new String(array); 
    }
}

別人的方法(6ms):

public class Solution {
    static final String vowels = "aeiouAEIOU";

    public String reverseVowels(String s) {
        int first = 0, last = s.length() - 1;
        char[] array = s.toCharArray();
        while(first < last){
            while(first < last && vowels.indexOf(array[first]) == -1){
                first++;
            }
            while(first < last && vowels.indexOf(array[last]) == -1){
                last--;
            }
            char temp = array[first];
            array[first] = array[last];
            array[last] = temp;
            first++;
            last--;
        }
        return new String(array);
    }
}
本系列未完,下一篇請看:LeetCode上做題之體會(四)。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章