代码:
public class BasicBinarySearch {
// 时间复杂度:O(logn)
// 空间复杂度:O(1)
// 遍历写法
public boolean contains(int[] nums, int target) {
if (nums == null || nums.length == 0) return false;
int left = 0;
int right = nums.length - 1;
while (left <= right) {
// bug : left + right 会溢出
// 整型的最大值是 (2^31) - 1 = 2147483647
int mid = left + (right - left) / 2;
// int mid = (left + right) >>> 1;
if (target == nums[mid]) {
return true;
} else if (target < nums[mid]) {
right = mid - 1; // 下一次搜索区间:[left...mid - 1]
} else { // target > nums[mid]
left = mid + 1; // 下一次搜索区间:[mid + 1...right]
}
}
// left > right :没有元素
return false;
}
// 时间复杂度:O(logn)
// 空间复杂度:O(logn)
// 递归写法
public boolean containsR(int[] nums, int target) {
if (nums == null || nums.length == 0) return false;
return contains(nums, 0, nums.length - 1, target);
}
private boolean contains(int[] nums, int left, int right, int target) {
if (left > right) return false;
int mid = left + (right - left) / 2;
if (nums[mid] == target) return true;
if (target < nums[mid]) {
return contains(nums, left, mid - 1, target);
} else {
return contains(nums, mid + 1, right, target);
}
}
}
这里会有一个bug
中间索引溢出的问题
解决方法
第二种,无符号右移一位
2. 变型的二分查找
2.1 查找第一个等于目标值的下标
代码
public int firstTargetElement(int[] nums, int target) {
if (nums == null || nums.length == 0) return -1;
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (target == nums[mid]) {
// 符合下面的两个条件之一就返回 mid :
// 1. mid 是数组的第一个元素
// 2. mid 前面的那个元素不等于 target
if (mid == 0 || nums[mid - 1] != target) return mid;
else right = mid - 1;
} else if (target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
2.2 查找第一个大于等于目标值下标的代码
public int firstGETargetElement(int[] nums, int target) {
if (nums == null || nums.length == 0) return -1;
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (target <= nums[mid]) {
// 符合下面的两个条件之一就返回 mid :
// 1. mid 是数组的第一个元素
// 2. mid 前面的那个元素小于 target
if (mid == 0 || nums[mid - 1] < target) return mid;
else right = mid - 1;
} else { // target > nums[mid]
left = mid + 1;
}
}
return -1;
}
2.3 查找最后一个等于目标值的下标
public int lastTargetElement(int[] nums, int target) {
if (nums == null || nums.length == 0) return -1;
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (target == nums[mid]) {
// 符合下面的两个条件之一就返回 mid :
// 1. mid 是数组的最后一个元素
// 2. mid 后面的那个元素不等于 target
if (mid == nums.length - 1 || nums[mid + 1] != target) return mid;
else left = mid + 1;
} else if (target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
2.4 查找最后一个小于等于目标值的下标
代码
public int lastLETargetElement(int[] nums, int target) {
if (nums == null || nums.length == 0) return -1;
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (target >= nums[mid]) {
if (mid == nums.length - 1 || nums[mid + 1] > target) return mid;
else left = mid + 1;
} else { // target < nums[mid]
right = mid - 1;
}
}
return -1;
}
3. 二分查找应用
3.1 如何定位ip对应的城市
public class IpLocationParser {
private static class IpLocation {
public long startIp;
public long endIp;
public String locationCity;
}
private static ArrayList<IpLocation> sortedIpLocations = new ArrayList<>();
static {
try {
// 1. 读取文件,解析 ip 地址段
BufferedReader reader =
new BufferedReader(new FileReader("data\\ip_location.txt"));
String line = null;
while ((line = reader.readLine()) != null) {
String[] temps = line.split(" ");
IpLocation ipLocation = new IpLocation();
ipLocation.startIp = ip2Score(temps[0]);
ipLocation.endIp = ip2Score(temps[1]);
ipLocation.locationCity = temps[2];
sortedIpLocations.add(ipLocation);
}
} catch (IOException e) {
throw new RuntimeException("解析 ip 地址库出错" + e);
}
// 2. 按照起始 ip 进行升序排列
// 时间复杂度:O(nlogn)
Collections.sort(sortedIpLocations, new Comparator<IpLocation>() {
@Override
public int compare(IpLocation o1, IpLocation o2) {
if (o1.startIp < o2.startIp) return -1;
else if (o1.startIp > o2.startIp) return 1;
else return 0;
}
});
}
// 将ip转成长整型
public static Long ip2Score(String ip) {
String[] temps = ip.split("\\.");
Long score = 256 * 256 * 256 * Long.parseLong(temps[0])
+ 256 * 256 * Long.parseLong(temps[1])
+ 256 * Long.parseLong(temps[2])
+ Long.parseLong(temps[3]);
return score;
}
// 二分查找指定 ip 对应的城市
// 时间复杂度:O(logn)
public static String getIpLocation(String ip) {
long score = ip2Score(ip);
// 3. 在 sortedIpLocations 中找到最后一个 startIp 小于等于 score 的这个 ip 段
int left = 0;
int right = sortedIpLocations.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (score >= sortedIpLocations.get(mid).startIp) {
if (mid == sortedIpLocations.size() - 1
|| sortedIpLocations.get(mid + 1).startIp > score) {
if (score <= sortedIpLocations.get(mid).endIp) {
return sortedIpLocations.get(mid).locationCity;
}
} else {
left = mid + 1;
}
} else { // target < nums[mid]
right = mid - 1;
}
}
return null;
}
public static void main(String[] args) {
System.out.println(getIpLocation("202.101.48.198"));
}
}
4.二分查找的两种思路
4.1 不断在循环体中查找目标元素
4.2 再循环体中排除一定不存在的目标元素区间
public int search2(int[] nums, int target) {
if (nums == null || nums.length == 0) return -1;
int left = 0;
int right = nums.length - 1;
// 搜索区间是 [left...right] 中的每个元素
while (left < right) {
int mid = left + (right - left) / 2;
if (target > nums[mid])
left = mid + 1;
else
right = mid;
}
// 循环结束后:left == right
// 需要后处理,因为在循环中,还有一个元素没有处理
if (nums[left] == target) return left;
return -1;
}
思路二第二种实现方式
public int search3(int[] nums, int target) {
if (nums == null || nums.length == 0) return -1;
int left = 0;
int right = nums.length - 1;
// 搜索区间是 [left...right] 中的每个元素
while (left < right) {
int mid = left + (right - left + 1) / 2;
if (target < nums[mid])
right = mid - 1;
else
left = mid;
}
// 循环结束后:left == right
// 需要后处理,因为在循环中,还有一个元素没有处理
if (nums[left] == target) return left;
return -1;
}
代码
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] result = new int[2];
int fIndex = getfirstIndex(nums,target);
int lIndex = getlastIndex(nums,target);
result[0] = fIndex;
result[1] = lIndex;
return result;
}
private int getfirstIndex(int[] nums,int target){
int left = 0;
int right = nums.length - 1;
// 第一步先算最小下标
while(left <= right){
int mid = left + (right - left) / 2 ;
if(nums[mid] == target){
// 需要往前看一下
//这块出错了
if((mid==0) || nums[mid-1] < target){
//这块出错了
return mid;
}else{
right = mid-1;
}
}else if(nums[mid] < target){
left = mid +1;
}else{
right = mid -1;
}
}
return -1;
}
private int getlastIndex(int[] nums,int target){
int left = 0;
int right = nums.length - 1;
// 第二步先算最大下标
while(left <= right){
int mid = left + (right - left) / 2 ;
if(nums[mid] == target){
// 需要往后看一下
if((mid == nums.length -1) || nums[mid+1] > target){
return mid;
}else{
left = mid+1;
}
}else if(nums[mid] < target){
left = mid +1;
}else{
right = mid -1;
}
}
return -1;
}
}
public int[] searchRange(int[] nums, int target) {
if (nums == null || nums.length == 0)
return new int[]{-1, -1};
int firstTargetIndex = searchFirstTargetIndex(nums, target);
if (firstTargetIndex == -1) {
return new int[]{-1, -1};
}
int lastTargetIndex = searchLastTargetIndex(nums, target);
return new int[]{firstTargetIndex, lastTargetIndex};
}
private int searchFirstTargetIndex(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (target > nums[mid]) {
left = mid + 1;
} else {
right = mid;
}
}
if (nums[left] == target) return left;
return -1;
}
private int searchLastTargetIndex(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = left + (right - left + 1) / 2;
if (target < nums[mid]) {
right = mid - 1;
} else {
left = mid;
}
}
if (nums[left] == target) return left;
return -1;
}
思路:先画图穷举所有可能性,在找规律
public int searchInsert1(int[] nums, int target) {
if (nums == null) return -1;
if (nums.length == 0) return 0;
//如果target都大于最后一个元素了,那么就将他放到最后一个元素上
if (target > nums[nums.length - 1]) return nums.length;
// 二分查找第一个大于等于 target 的索引
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (target <= nums[mid]) {
if (mid == 0 || nums[mid - 1] < target) return mid;
else right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
思路二
public int searchInsert(int[] nums, int target) {
if (nums == null) return -1;
if (nums.length == 0) return 0;
// 二分查找第一个大于等于 target 的索引
int left = 0;
int right = nums.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (target > nums[mid]) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}