算法 .二分法
二分法也就是折半查找,在 有序 的數列中查找指定的元素,設定最小索引(low)和最大索引(height-1)還有中間值mid((low+height-1)/2),這種查找,如果中間值比指定元素小讓low=mid+1,如果中間值比指定元素大,讓height=mid-1;
以上是大體思路,下面展示兩個動圖,幫助理解
第一個圖表示了二分法的整體過程;
第二個圖表示了原方法的整體過程;
觀察可得二分法的優越性!
代碼實現
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
public class Main2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int arr[] = { 2, 5, 6, 8, 9, 4, 7 };
Arrays.sort(arr);
int deix(索引) = getxiabiao(arr, 7);
}
public static int getxiabiao(int[] arr, int key) {
int heigh = arr.length-1;
int low = 0;
int mid = 0;
while (low <= heigh) {
mid = low + (heigh - low)/2;
if (arr[mid] == key) {
return mid;
} else if (arr[mid] < key) {
low = mid + 1;
} else if (arr[mid] > key) {
heigh = mid - 1;
}
}
return -1;
}
}
中間值的設定有兩種方法;
算法一: mid = (low + high) / 2
算法二: mid = low + (high – low)/2
乍看起來,算法一簡潔,算法二提取之後,跟算法一沒有什麼區別。但是實際上,區別是存在的。
算法一的做法,在極端情況下,(low + high)存在着溢出的風險,進而得到錯誤的mid結果,導致程序錯誤。
而算法二能夠保證計算出來的mid,一定大於low,小於high,不存在溢出的問題。
例題:
給定三個整數數組
A = [A1, A2, … AN],
B = [B1, B2, … BN],
C = [C1, C2, … CN],
請你統計有多少個三元組(i, j, k) 滿足:
1. 1 <= i, j, k <= N
2. Ai < Bj < Ck
輸入
第一行包含一個整數N。 第二行包含N個整數A1, A2, ... AN。 第三行包含N個整數B1, B2, ... BN。 第四行包含N個整數C1, C2, ... CN。
輸出
一個整數表示答案
樣例輸入
3
1 1 1
2 2 2
3 3 3
題目分析:
就是遍歷A組找小於B【i】並且在C組找大於B【i】的個數,因爲數據很大,所以直接暴力搜索肯定超時,所以需要二分法搜索比指定元素大的第一個元素下標
A組當中比如arr={1,2,3,4,5}中找比4小的第一個元素 返回結果是 2 然後2+1就是小於4的個數;
C組當中比如arr={1,2,3,4,5}中找比3大的第一個元素 返回下標是 3 然後用長度5減去3就是大於3的個數;
知識點一(在一以升序序列中找到第一個比指定元素大的元素位置):
代碼實現:
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int []arr = {3,3,3};
System.out.println(er(arr,4));
}
public static Integer er(int[] C, int a) {
int start = 0;
int end = C.length - 1;
int sum = C.length; //不能是C.length-1,情況如 3 3 3 在這組數據臉面找比四大的如果是C.length-1答案是1.反之 0
while (start <= end) {
int mid = start + (end - start) / 2;
if (C[mid] < a) {
start = mid + 1;
} else if (C[mid] > a) {
sum = mid;
end = mid - 1;
}else if(C[mid]==a) {
sum=mid+1;
start = mid+1;
}
}
return sum;
}
}
代碼原理:
一:
在C組中找比B【i】大的第一個元素:
情況一,指定元素在這組數列中.比如arr中找比5大的第一個元素的下標
arr={2,5,8,9,12,15,16,18,20}
1.當arr【mid】<5,start+=1;證明那個數在右面所以將左端點右移
2.當arr【mid】>5,end-=1;如果大於5,那麼就有可能是第一個大於5的數,將這個下標暫時給sum,又因爲找離指定元素最近的一個,所以要將右端點左移,找還有沒有大於5的數;
3.當arr【mid】=5,那麼那麼5右面的一個數肯定是比5大的第一個元素,所以sum=mid+1;之前想不明白爲什麼mid+1肯定是比5大的第一個元素了,直接return mid+1就好了,爲什麼還有繼續控制start=mid+1;因爲如果arr={5,5,5},那麼返回的下標直接就是1,然後長度(3)減去1就是2,證明大於5的個數是2,明顯是錯誤的
所以!必須讓start=mid+1;這個方法很巧妙,那麼又有一個例子來了
arr={1,4,6,8,18,20} 找比6大的一個元素的下標;start = 0, end =5
第一步:mid=(s+e)/2=2;arr【2】=6 所以sum=mid+1=3;start=mid+1=3;end=5;
第二步:mid=(s+e)/2=4;arr【4】=18並且大於6,所以sum=4,這個時候我就在想這也不對啊,答案肯定是下標3啊,這咋4賦值給sum了啊,再看要找比6大的第一個數,那麼在6右面的肯定都是比6大的,所以比6大的肯定都要走這一步啊end=mid-1;所以end從下標5,到4,到3,又因爲只要比6大就有可能是比6大的第一個數,所以要將下標賦給sum,所以最後start=end
(start+end)/2=start, sum還是等於了3;
再看這個例子 arr={6,6,6} 找比6大的數
因爲每一個數都等於6,所以一直循環sum=mid+1;start=mid+1;最後sum=3 長度-3=0 所以在這組數列中沒有比6大的數
再有細節就是sum的初始化 arr.length ,比如數組arr={6,6,6} 找7大的第一個數,最後sum還是等於arr.length 答案 0
情況二,指定元素不在這組數列中.比如arr中找比5大的第一個元素的下標
arr={2,(5),8,9,12,15,16,18,20}
如果指定元素不在這組數列當中,那麼start和end會無限趨近於5的兩個端點2和8,最後8的下標賦給sum
二:
在A組中找比B【i】小的數:
代碼實現;
import java.math.BigInteger;
import java.util.*;
public class Main2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int []arr = {1,3,4};
System.out.println(er(arr,2));
}
public static Integer er(int[] C, int a) {
int start = 0;
int end = C.length - 1;
int sum = -1;
while (start <= end) {
int mid = start + (end - start) / 2;
if (C[mid] < a) {
sum=mid;
start = mid + 1;
} else if (C[mid] > a) {
end = mid - 1;
}else if(C[mid]==a) {
sum=mid-1;
end = mid-1;
}
}
return sum;
}
}
與在C組中找比指定元素大的數原理上大同小異,
例如arr={1,4,6,8,18,20} 找比6小的第一個元素
異處:當arr【mid】=指定元素時、end = mid-1,sum=mid-1,因爲在指定元素左面肯定都是比指定元素小的,start從 0 到 1 到 2;
返回sum的值等於2;最後sum+1等於arr中有多少個比指定元素小的數;
end = mid-1的作用就是當出現arr中的所有元素等於指定元素例如在arr={6,6,6,6}中找比6小的數;
end=mid-1之後 因爲每一個數都等於指定元素,最後sum=-1;sum+1等於答案
細節sum的初始值-1,arr{6,6,6}中找比5小的數,sum返回-1,sum+1=0,所以無比5小的數
題目代碼:
import java.math.BigInteger;
import java.util.*;
public class Main{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] A = new int[n];
int[] B = new int[n];
int[] C = new int[n];
for (int i = 0; i < n; i++) {
A[i] = sc.nextInt();
}
for (int i = 0; i < n; i++) {
B[i] = sc.nextInt();
}
for (int i = 0; i < n; i++) {
C[i] = sc.nextInt();
}
BigInteger jk = BigInteger.valueOf(0);
Arrays.sort(A);
Arrays.sort(B);
Arrays.sort(C);
for (int i = 0; i < B.length; i++) {
int x = fen(A, B[i]) + 1;
int y =er(C, B[i]);
jk =jk.add(BigInteger.valueOf(x).multiply(BigInteger.valueOf(y)));
}
System.out.println(jk);
}
public static Integer fen(int[] A, int a) {
int start = 0;
int end = A.length - 1;
int sum = -1;//注意sum的初始值
while (start <= end) {
int mid = start + (end - start) / 2;
if (A[mid] > a) {
end = mid - 1;
} else if (A[mid] < a) {
sum = mid;
start = mid + 1;
}else if(A[mid]==a) {
sum=mid-1;
end = mid-1;
}
}
return sum;
}
public static Integer er(int[] C, int a) {
int start = 0;
int end = C.length - 1;
int sum = C.length;
while (start <= end) {
int mid = start + (end - start) / 2;
if (C[mid] < a) {
start = mid + 1;
} else if (C[mid] > a) {
sum = mid;
end = mid - 1;
}else if(C[mid]==a) {
sum = mid+1;
start = mid+1;
}
}
return C.length-sum;
}
}
若有錯誤請指出,大家共同學習謝謝