《算法》1.4節部分題解

14
4-sum。爲4-sum設計一個算法。

public int fourSum(int[] a) {
    int len = a.length;
    int cnt = 0;
    for (int l = 0; l < len - 3; l++) {
        for (int i = l + 1; i < len - 2; i++) {
            for (int j = i + 1, k = len - 1; j < k;) {
                if (a[l] + a[i] + a[j] + a[k] < 0) {
                    j++;
                } else if (a[l] + a[i] + a[j] + a[k] > 0) {
                    k--;
                } else {
                    cnt++;
                    j++;
                    k--;
                }
            }
        }
    }
    return cnt;
}

15
快速3-sum。作爲熱身,使用一個線性級別的算法實現TwoSumFaster來計算已排序的數組中和爲0的整數對的數量。用相同的思想爲3-sum問題給出一個平方級別的算法。
雙指針法前後夾擊

public class TwoSumFaster {
    public int twoSumFaster(int[] a) {
        int cnt = 0;
        int len = a.length;
        for (int j = 0, k = len - 1; j < k;) {
            if (a[j] + a[k] < 0) {
                j++;
            } else if (a[j] + a[k] > 0) {
                k--;
            } else {
                j++;
                k--;
                ++cnt;
            }
        }
        return cnt;
    }
}

類似的:

public class ThreeSumFaster {   
    public int threeSumFaster(long[] a) {
        int cnt = 0;
        int len = a.length;
        for (int j = 0; j < len - 2 ; j ++) {
            for(int k = j + 1,h = len -1;k < h;){
                if (a[j] + a[k] + a[h] < 0) {
                    k++;
                } else if (a[j] + a[k] + a[h] > 0) {
                    h--;
                } else {
                    k++;
                    h--;
                    ++cnt;
                }
            }
        }
        return cnt;
    }
}

16
最接近的一對(一維)。

public static void ClosestPair(double[] arr) {
	Arrays.sort(arr);
    double min = Double.MAX_VALUE;
    int index1 = 0;
    int index2 = 0;
    for(int i = 0; i < arr.length - 1; i++) {
        if(arr[i + 1] - arr[i] < min) {
            min = arr[i + 1] - arr[i];
            index1 = i;
            index2 = i + 1;
        }
    }
    System.out.println(arr[index1] + " " + arr[index2]);
}

17
最遙遠的一對(一維)。

public static void FarthestPair(double[] arr) {
    int max = 0, min = 0;
    for(int i = 1; i < arr.length; i++) {
        if(arr[i] > arr[max])
            max = i;
        else if(arr[i] < arr[min])
            min = i;
    }
    System.out.println(arr[max] + " " + arr[min]);
}

18
數組的局部最小元素。
1、找到一個局部最小元素即可,不用找全。
2、數組可能含有0個元素。
3、優先檢查區間端點處的單邊極值。

public static int localMin(int[] arr) {
        if(arr == null || arr.length == 0)
            return -1;
        if(arr.length == 1 || arr[0] < arr[1])
            return 0;
        if(arr[arr.length - 1] < arr[arr.length - 2])
            return arr.length - 1;
        int left = 1;
        int right = arr.length - 2;
        while(left < right) {
            int mid = (left + right) / 2;
            if(arr[mid - 1] > arr[mid] && arr[mid + 1] > arr[mid])
                return mid;
            else if(arr[mid - 1] < arr[mid])
                right = mid - 1;
            else
                left = mid + 1;
        }
        return left;
    }

19
矩陣的局部最小元素。
中文版翻譯有誤,此題要求的運行時間和nlognn\log n成正比,否則無法類比上一題。
不過的確有O(n)的實現。
官網原文:
Local minimum in a matrix. Given an n-by-n array a[] of n2n^2 distinct integers, design an algorithm that runs in time proportional to n log n to find a local minimum: an pair of indices i and j such that a[i][j] < a[i+1][j], a[i][j] < a[i][j+1], a[i][j] < a[i-1][j], and a[i][j] < a[i][j-1] (assuming the neighboring entry is in bounds).
Extra credit: Design an algorithm that takes times proportional to n.

找到第n/2行中的最小項,比如a[n/2][j]。如果是局部最小值,則返回。否則,檢查它上下兩個相鄰元素a[n/2-1][j]和a[n/2+1][j],在較小的相鄰元素的半邊中重複。

static class Pos {
    int row;
    int col;
    Pos(int a, int b) {
        row = a;
        col = b;
    }
}
private static int MinPos(int[] a) {
	if(a == null || a.length == 0)
	        return -1;
    int min = 0;
    for(int i = 1; i < a.length; i++) {
        if(a[i] < a[min])
            min = i;
    }
    return min;
}
public static Pos BinarySearch(int[][] arr) {
    if(arr.length == 1)
        return new Pos(0, 0);
    else {
        int min = MinPos(arr[0]);
        if(arr[0][min] < arr[1][min])
            return new Pos(0, min);
        min = MinPos(arr[arr.length - 1]);
        if(arr[arr.length - 1][min] < arr[arr.length - 2][min])
            return new Pos(arr.length - 1, min);
    }
    int up = 1;
    int down = arr.length - 2;
    int mid = 0;
    int min = 0;
    while(up <= down) {
        mid = (up + down) / 2;
        min = MinPos(arr[mid]);
        if(arr[mid][min] < arr[mid - 1][min] && arr[mid][min] < arr[mid + 1][min])
            return new Pos(mid, min);
        else if(arr[mid][min] > arr[mid - 1][min])
            down = mid - 1;
        else
            up = mid + 1;
    }
    return new Pos(mid, min);
}

22
僅使用加減實現的二分查找。(斐波那契查找)

思路:與二分查找類似,首先把整個數組擴充到一個斐波那契數的長度,多出來的部分用原數組的最後一個元素填充。假設擴充後數組長度爲F[k],則該數組可以被劃分成長度爲F[k-2]和F[k-1]的兩個部分,劃分點我們設爲left + F[k-2],像二分查找那樣,先檢查這個位置上的元素是不是查找目標,如果是則直接返回,否則根據與目標的大小比較縮小區間繼續搜索。

private static int[] F;
public static int Init(int n) {
    F = new int[20];
    F[0] = 0;
    F[1] = 1;
    int i = 2;
    while (i < 20) {
        F[i] = F[i - 1] + F[i - 2];
        if(F[i] > n)  //找到一個最接近原數組長度的斐波那契數
            break;
        i++;
    }
    return i;
}
public static int FibonacciSearch(int key, int[] arr) {
    int k = Init(arr.length);
    int[] tmp = Arrays.copyOf(arr, F[k]);   //擴充原數組
    for(int i = arr.length; i < tmp.length; i++)
        tmp[i] = arr[arr.length - 1];   //用最後一個元素填充後面的位置
    int low = 0;
    int high = tmp.length - 1;
    while(low <= high) {
        int mid = low + F[k-2];
        if(key < tmp[mid]) {
            high = mid;
            k -= 2;
        }
        else if(key > tmp[mid]) {
            low = mid + 1;
            k -= 1;
        }
        else {
            if(mid < arr.length)
                return mid;
            else
                return arr.length - 1;
        }
    }
    return -1;
}

24
扔雞蛋。假設你面前有一棟N層的大樓和許多雞蛋,假設將雞蛋從F層或更高的地方扔下,雞蛋纔會碎,否則不會碎。首先,設計一種成本爲摔碎雞蛋的數量爲~lgN的策略來確定F的值,然後想辦法將成本降低到~2lgF。

思路:前者的實現就是二分查找,這裏就不寫了。後者的實現是O(lgF)的,而我們並不知道F是多少,所以只能從1開始加倍,直到我們能讓雞蛋破碎的高度up爲止,這裏的成本是~lgF的,然後在這個高度和它的一半的位置down之間用二分查找來找F,這裏的成本也是~lgF的,所以加起來就是~2lgF。

需要注意的是,我們只知道能否打碎雞蛋,也就是我們只知道mid是否>=F,所以這裏的二分查找要精確到up和down重合爲止。

public static int throwEgg(int n) {
    int F = (int)(Math.random() * n + 1);
    int up = 1;
    while(up < F)
        up <<= 1;
    int down = up >> 1;
    while (down < up) {
        int mid = (down + up) / 2;
        if(mid >= F)
            up = mid;
        else
            down = mid + 1;
    }
    return up;
}

25
扔兩個雞蛋。和上一題相同,但現在你只有兩個雞蛋,而你的成本模型則是扔雞蛋的次數。設計一種策略,最多扔2N2\sqrt{N}次雞蛋即可判斷F的值,然後想辦法把這個成本降低到~cFc\sqrt{F}次。

前者實現:把一個雞蛋從第N\sqrt{N}層、第2N2\sqrt{N}層、第3N3\sqrt{N}層……扔下去,直到它在第kNk\sqrt{N}層破碎爲止,然後把另一個雞蛋從第(k1)N(k-1)\sqrt{N}層開始逐層往上向下丟,直到破碎位置,那一層就是F層。

public static int throwTwoEggs(int n) {
    int F = (int)(Math.random() * n + 1);
    int m = (int)Math.sqrt(n);
    int up = 1;
    while (up * m < F)
        up++;
    int down = up - 1;
    int high = down * m + 1;
    while (high < F)
        high++;
    return high;
}

後者實現:和第24題的第二種實現類似,我們從第1層開始找 i2i^2 的層,直到雞蛋破碎,然後線性搜索即可。

public static int throwTwoEggs(int n) {
    int F = (int)(Math.random() * n + 1);
    int up = 1;
    while (up * up < F)
        up++;
    int down = up - 1;
    int high = down * down + 1;
    while (high < F)
        high++;
    return high;
}

34
熱還是冷。設計一個算法在~2lgN之內猜出1到N之間的一個祕密的整數,再設計一個算法在~1lgN之內找到這個數。
~2lgN:每次猜區間端點來縮小區間

public static int HotOrCold(int n) {
    int[] a = new int[n + 1];
    for(int i = 1; i <= n; i++)
        a[i] = i;
    int num = (int)(Math.random() * n + 1);
    int guess1 = 1;
    int guess2 = n;
    while (true) {
        if(guess1 == num)
            return guess1;
        else if(guess2 == num)
            return guess2;
        else if(Math.abs(num - guess2) < Math.abs(num - guess1))
            guess1 = (guess1 + guess2) / 2;
        else
            guess2 = (guess1 + guess2) / 2;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章