1. 程式人生 > >JAVA入門演算法題(六)

JAVA入門演算法題(六)

不只為了餬口,還要有抱負。你要想:在這個行業中,我要成為什么樣的人。

一、最大的時間

題目:

給定一個由 4 位數字組成的陣列,返回可以設定的符合 24 小時制的最大時間。

最小的 24 小時制時間是 00:00,而最大的是 23:59。從 00:00 (午夜)開始算起,過得越久,時間越大。

以長度為 5 的字串返回答案。如果不能確定有效時間,則返回空字串。

示例:
輸入:[1,2,3,4]
輸出:"23:41"
輸入:[5,5,5,5]
輸出:""

這道題我有兩種思路,一種是把所有組合情況列出來,在列的過程中把不符合條件的去掉,然後找最大的,另一種是找到最大的小時,然後找分鐘,找不到就找次大的小時,再找分鐘,都找不著就返回空字串。

把所有情況列出來的問題叫做全排列問題,最簡單的全排列實現方式便是for迴圈

public String largestTimeFromDigits(int[] A) {
        StringBuilder stringBuilder = new StringBuilder();

        for (int sum = 23; sum >= 0; sum--) {
            for (int i = 0; i < A.length; i++) {
                for (int j = 0; j < A.length; j++) {
                    if (i != j && A[i] * 10 + A[j] == sum) {
                        // 找到小時數後找分鐘數
                        for (int fen = 59; fen >= 0; fen--) {
                            for (int m = 0; m < A.length; m++) {
                                for (int n = 0; n < A.length; n++) {
                                    if (m != n && A[m] * 10 + A[n] == fen && m != i && m != j && n != i && n != j) {
                                        stringBuilder.append(A[i]);
                                        stringBuilder.append(A[j]);
                                        stringBuilder.append(':');
                                        stringBuilder.append(A[m]);
                                        stringBuilder.append(A[n]);
                                        return stringBuilder.toString();
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return stringBuilder.toString();
    }

那這種寫法呢,首先就很low,其次還很累,況且寫出來也很難看,我在之前的部落格中有寫過全排列問題的解法,我們只要在最後去除一下不符合的情況就能得到所有符合條件的時間了。對於怎麼找到最大的時間也有兩種方案,一種是使用list.sort(),另一種就是自己寫for迴圈了。注意一下過濾時間,小時的最大是23,當第一位為1時第二位的範圍是0到4,當第一位為2時,第二位的範圍是0到3。下面這種

public static List<String> stringList=new ArrayList<>();
    private static String largestTimeFromDigits(int[] A) {
        disorder(A,0,A.length);
        stringList.sort(new SortByString());
        integerList.sort(new SortByInteger());
        if (stringList.size()==0)return "";
        return stringList.get(stringList.size()-1);
    }
    static class SortByString implements Comparator {
        public int compare(Object o1, Object o2) {
            return o1.toString().compareTo(o2.toString());
        }
    }

public static void disorder(int array[],int m,int n){
        if (m==n){
            if (array[0]<=2&&array[1]<=4&&array[2]<=6){
                if (array[0]*1000+array[1]*100!=2400){
                    stringList.add(array[0]+""+array[1]+":"+array[2]+""+array[3]);
                }
            }
            return;
        }else {
            for (int i=m;i<n;i++){
                swap(array,m,i);
                disorder(array,m+1,n);
                swap(array,m,i);
            }
        }
    }

    private static void swap(int[] array,int m,int n){
        int temp=array[m];
        array[m]=array[n];
        array[n]=temp;
    }

另一種實現方式便是組合小時和分鐘,因為組合方式比較少,所以可以直接手動寫

static String ans;
    public static String largestTimeFromDigits(int[] A) {
        ans = "";
        check(A[0], A[1], A[2], A[3]);
        check(A[0], A[2], A[1], A[3]);
        check(A[0], A[3], A[1], A[2]);
        check(A[1], A[2], A[0], A[3]);
        check(A[1], A[3], A[0], A[2]);
        check(A[2], A[3], A[0], A[1]);

        return ans;
    }
    
    public static void check(int h1, int h2, int m1, int m2) {
        String hour = best(h1, h2, 24);
        String minute = best(m1, m2, 60);
        if (hour.isEmpty() || minute.isEmpty()) return;

        String cand = hour + ":" + minute;
        if (cand.compareTo(ans) > 0) ans = cand;
    }
    
    public static String best(int d1, int d2, int limit) {
        int ans = Math.max(10*d1 + d2 < limit ? 10*d1 + d2 : -1,
                10*d2 + d1 < limit ? 10*d2 + d1 : -1);
        return ans >= 0 ? String.format("%02d", ans) : "";
    }

但這種方式我測試了一下執行100次的所需時間,幾次平均下來大概在90毫秒左右,我還一種更快的演算法。其實和第二種寫法差別不大,只是把對字串的操作變成了對數字的操作,因為幾乎在所有的程式語言中對數字的操作永遠比字串快。但要注意的一點就是對於0的處理,這一步差不多增加了15%的運算時間。對於列表求最大值的求解也沒啥變動,感覺換成for迴圈更快一些,感興趣的試一下。下面方式執行1000次的耗時平均在68毫秒左右。


    public static List<Integer> integerList=new ArrayList<>();
    private static String largestTimeFromDigits(int[] A) {
        disorder(A,0,A.length);
        integerList.sort(new SortByInteger());
        if (integerList.size()==0)return "";
        int number=integerList.get(integerList.size()-1);
        String hour="0"+number/100;
        String minute="0"+number%100;
        return  hour.substring(hour.length()-2)+":"+minute.substring(minute.length()-2);
    }

    static class SortByInteger implements Comparator {
        public int compare(Object o1, Object o2) {
            int a=(Integer) o1;
            int b=(Integer)o2;
            return a-b;
        }
    }

    public static void disorder(int array[],int m,int n){
        if (m==n){
            if (array[0]<=2&&array[1]<=4&&array[2]<=6){
                if (array[0]*1000+array[1]*100!=2400){
                    integerList.add(array[0]*1000+array[1]*100+array[2]*10+array[3]);
                }
            }
            return;
        }else {
            for (int i=m;i<n;i++){
                swap(array,m,i);
                disorder(array,m+1,n);
                swap(array,m,i);
            }
        }
    }

    private static void swap(int[] array,int m,int n){
        int temp=array[m];
        array[m]=array[n];
        array[n]=temp;
    }

二、最長公共字首

題目:

編寫一個函式來查詢字串陣列中的最長公共字首。如果不存在公共字首,返回空字串 ""。

示例:

輸入: ["flower","flow","flight"]
輸出: "fl"

輸入: ["dog","racecar","car"]
輸出: ""
解釋: 輸入不存在公共字首。

看到這道題我第一反應就是全排列加排序查詢,但後來想了想,不對,想複雜了,他只是讓求公共字首,這裡的公共是大家都有,不是其中幾個共有,所以只要拿一個和其它所有字串進行對比就好了。在對比之前還可以進行一些過濾,陣列為0的返回空字串,長度是1的返回本身,陣列中有空字串的直接返回空字串,這樣可以省去很多計算。

public static String longestCommonPrefix(String[] strs) {
        if (strs.length == 0) {
            return "";
        }else if (strs.length==1){
            return strs[0];
        }
        String ret = strs[0];
        if (ret.length()==0)return "";
        for (int i = 0; i< strs.length-1; i++) {
            if (strs[i+1].length()==0)return "";
            ret = compare(ret, strs[i+1]);
            if (ret.length() == 0) {
                return "";
            }
        }
        return ret;
    }

    public static String compare(String s1, String s2) {
        int min = Math.min(s1.length(), s2.length());
        for (int i = 0; i< min; i++) {
            if (s1.charAt(i) != s2.charAt(i)) {
                return s1.substring(0, i);
            }
        }
        return s1.length() == min ? s1 : s2;
    }

三、羅馬數字轉換

題目:

羅馬字元對照表

字元          數值
I             1
V             5
X             10
L             50
C             100
D             500
M             1000

規律:
例如, 羅馬數字 2 寫做 II ,即為兩個並列的 1。12 寫做 XII ,即為 X + II 。 27 寫做  XXVII, 即為 XX + V + II 。

通常情況下,羅馬數字中小的數字在大的數字的右邊。但也存在特例,例如 4 不寫做 IIII,而是 IV。數字 1 在數字 5 的左邊,所表示的數等於大數 5 減小數 1 得到的數值 4 。同樣地,數字 9 表示為 IX。這個特殊的規則只適用於以下六種情況:

    I 可以放在 V (5) 和 X (10) 的左邊,來表示 4 和 9。
    X 可以放在 L (50) 和 C (100) 的左邊,來表示 40 和 90。
    C 可以放在 D (500) 和 M (1000) 的左邊,來表示 400 和 900。

給定一個羅馬數字,將其轉換成整數。輸入確保在 1 到 3999 的範圍內。
示例:
輸入: "III"
輸出: 3

輸入: "LVIII"
輸出: 58
解釋: L = 50, V= 5, III = 3.

輸入: "MCMXCIV"
輸出: 1994
解釋: M = 1000, CM = 900, XC = 90, IV = 4.

題目很長,意思很簡單,就是給你一個羅馬數字表示的字串讓你轉換成阿拉伯數字

你可能想上來就判斷判斷判斷,其實不用,你找找規律,凡是大數放到小數前面的就加上,大數放到小數後面的就減去,你就從前往後迴圈字串取字元判斷大小就好了,寫個switch把羅馬單字元轉換成數字。

public static int romanToInt(String s) {
        int result=0;
        int last=99999;
        int current=0;
        for (int i=0;i<s.length();i++){
            current=singleRomanToInt(s.charAt(i));
            if (last<current){
                result-=2*last;
                result+=current;
            }else {
                result+=current;
            }
            last=current;
        }
        return result;
    }

    public static int singleRomanToInt(char c){
        switch (c) {
            case 'I':
                return 1;
            case 'V':
                return 5;
            case 'X':
                return 10;
            case 'L':
                return 50;
            case 'C':
                return 100;
            case 'D':
                return 500;
            case 'M':
                return 1000;
            default:
                return 0;
        }
    }

四、符合規則的括號

題目:

給定一個只包括 '(',')','{','}','[',']' 的字串,判斷字串是否有效。
有效字串需滿足:
   左括號必須用相同型別的右括號閉合。
   左括號必須以正確的順序閉合。
注意空字串可被認為是有效字串。

示例:
輸入: "()[]{}"
輸出: true

輸入: "([)]"
輸出: false

這題就比上面那題簡單明瞭,給你一堆括號,你判斷一下是不是都有開有合。

給你的字串可能是這樣的“([{}])”,也可能是這樣的“{()[]}”,還可能更復雜,對於這種問題最適合的資料結構就是棧了,從字串不斷取值,如果棧頂有和它匹配的就彈出,沒有就壓入。需要注意的是這題的演算法時間是有要求的,它可能會給你一個幾千長度的字串讓你判斷,如果你要是用迴圈什麼的肯定是來不及了,從字串取字元也儘量不要用String.toCharArray(),這種方式比String.charAt()慢,因為轉換成字元陣列是用的System.arraycopy(),而charAt()是直接取值,可能一次比較差不了多少時間,但是一旦字串長起來就差多了。

public static boolean isValid(String s) {
        if (s.length()==0)return true;
        if (s.length()%2!=0)return false;
        Stack<Character> stack=new Stack<>();
        for (int i=0;i<s.length();i++){
            char a=s.charAt(i);
            if (!stack.empty()){
                char b=stack.pop();
                if (a!=getTargetChar(b)){
                    stack.push(b);
                    stack.push(a);
                }
            }else {
                stack.push(a);
            }
        }
        if (stack.empty()){
            return true;
        }else {
            return false;
        }
    }

    public static char getTargetChar(char a){
        switch (a){
            case '(':
                return ')';
            case '[':
                return ']';
            case '{':
                return '}';
        }
        return 'X';
    }