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
矩陣的局部最小元素。
中文版翻譯有誤,此題要求的運行時間和成正比,否則無法類比上一題。
不過的確有O(n)的實現。
官網原文:
Local minimum in a matrix. Given an n-by-n array a[] of 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
扔兩個雞蛋。和上一題相同,但現在你只有兩個雞蛋,而你的成本模型則是扔雞蛋的次數。設計一種策略,最多扔次雞蛋即可判斷F的值,然後想辦法把這個成本降低到~次。
前者實現:把一個雞蛋從第層、第層、第層……扔下去,直到它在第層破碎爲止,然後把另一個雞蛋從第層開始逐層往上向下丟,直到破碎位置,那一層就是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層開始找 的層,直到雞蛋破碎,然後線性搜索即可。
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;
}
}