目录
二分查找/排序
二分查找-I
题目
请实现无重复数字的升序数组的二分查找
给定一个 元素升序的、无重复数字的整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标(下标从 0 开始),否则返回 -1
数据范围:0≤𝑙𝑒𝑛(𝑛𝑢𝑚𝑠)≤2×105, 数组中任意值满足 ∣𝑣𝑎𝑙∣≤109
进阶:时间复杂度 𝑂(log𝑛) ,空间复杂度 𝑂(1)
示例1
输入:[-1,0,3,4,6,10,13,14],13
返回值:6
说明:13 出现在nums中并且下标为 6
示例2
输入:[],3
返回值:-1
说明:nums为空,返回-1
示例3
输入:[-1,0,3,4,6,10,13,14],2
返回值:-1
说明:2 不存在nums中因此返回 -1
备注:数组元素长度在[0,10000]之间
数组每个元素都在 [-9999, 9999]之间。
分析
简单的二分查找法,分为左闭右开和左闭右闭
代码
java
左闭右闭
java
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型一维数组
* @param target int整型
* @return int整型
*/
public int search (int[] nums, int target) {
// write code here
int i=0;
int j=nums.length-1;
while(i<=j){
int mid=(i+j)>>>1;
if(nums[mid]>target){
j=mid-1;
}else if(nums[mid]<target){
i=mid+1;
}else{
return mid;
}
}
return -1;
}
}
左闭右开
java
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型一维数组
* @param target int整型
* @return int整型
*/
public int search (int[] nums, int target) {
// write code here
int i=0;
int j=nums.length;
while(i<j){
int mid=(i+j)>>>1;
if(nums[mid]>target){
j=mid;
}else if(nums[mid]<target){
i=mid+1;
}else{
return mid;
}
}
return -1;
}
}
python
左闭右闭
python
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param nums int整型一维数组
# @param target int整型
# @return int整型
#
class Solution:
def search(self , nums: List[int], target: int) -> int:
# write code here
i=0
j=len(nums)-1
while i<=j:
mid=(i+j)//2
if nums[mid]>target:
j=mid-1
elif nums[mid]<target:
i=mid+1
else:
return mid
return -1
左闭右开
python
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param nums int整型一维数组
# @param target int整型
# @return int整型
#
class Solution:
def search(self , nums: List[int], target: int) -> int:
# write code here
i=0
j=len(nums)
while i<j:
mid=(i+j)//2
if nums[mid]>target:
j=mid
elif nums[mid]<target:
i=mid+1
else:
return mid
return -1
二维数组中的查找
题目
在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
\[1,2,8,9\], \[2,4,9,12\], \[4,7,10,13\], \[6,8,11,15
]
给定 target = 7,返回 true。
给定 target = 3,返回 false。
数据范围:矩阵的长宽满足 0≤𝑛,𝑚≤500 , 矩阵中的值满足 −109≤𝑣𝑎𝑙≤109
进阶:空间复杂度 𝑂(1),时间复杂度 𝑂(𝑛+𝑚)
示例1
输入:7,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]
返回值:true
说明:存在7,返回true
示例2
输入:1,[[2]]
返回值:false
示例3
输入:3,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]
返回值:false
说明:不存在3,返回false
分析
这道题如果刚开始建立一个一维数组,把数字都放进去,那么其实空间复杂度是不符合的,所以我们可以直接在原来的二维数组上操作,我们从右上角出发,利用二分法。
那么为什么是右上角呢?
右上角的那个数字是当前行的最大值和当前列的最小值
所以可以根据这个移动。
代码
java
java
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param target int整型
* @param array int整型二维数组
* @return bool布尔型
*/
public boolean Find (int target, int[][] array) {
// write code here
if (array == null || array.length == 0 || array[0].length == 0) {
return false;
}
// 从 右上角 开始
int row = 0;
int col = array[0].length - 1;
while (row < array.length && col >= 0) {
if (array[row][col] == target) {
return true; // 找到了
} else if (array[row][col] > target) {
col--; // 太大了,往左走
} else {
row++; // 太小了,往下走
}
}
return false; // 遍历完都没找到
}
}
python
python
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param target int整型
# @param array int整型二维数组
# @return bool布尔型
#
class Solution:
def Find(self , target: int, array: List[List[int]]) -> bool:
# write code here
if(len(array)==0):
return False
i=0
j=len(array[0])-1
while i<len(array) and j>=0:
if array[i][j]==target:
return True
elif array[i][j]<target:
i+=1
elif array[i][j]>target:
j-=1
return False
寻找峰值
给定一个长度为n的数组nums,请你找到峰值并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个所在位置即可。
1.峰值元素是指其值严格大于左右相邻值的元素。严格大于即不能有等于
2.假设 nums[-1] = nums[n] = −∞−∞
3.对于所有有效的 i 都有 nums[i] != nums[i + 1]
4.你可以使用O(logN)的时间复杂度实现此问题吗?
数据范围:
1≤𝑛𝑢𝑚𝑠.𝑙𝑒𝑛𝑔𝑡ℎ≤2×105
−231<=𝑛𝑢𝑚𝑠[𝑖]<=231−1
如输入[2,4,1,2,7,8,4]时,会形成两个山峰,一个是索引为1,峰值为4的山峰,另一个是索引为5,峰值为8的山峰,如下图所示:

示例1
输入:[2,4,1,2,7,8,4]
返回值:1
说明:4和8都是峰值元素,返回4的索引1或者8的索引5都可以
示例2
输入:[1,2,3,1]
返回值:2
说明:3 是峰值元素,返回其索引 2
题目
分析
为什么右边更大,右边一定有峰值?
- 右边一直在上升 → 一直走,直到不再上升,那个点就是峰值
- 如果一直上升到最后一个元素,因为
nums[n] = -∞最后一个元素天然就是峰值
为什么中间更大,左边一定有峰值?
- 中间比右边大
- 我们往左边搜索
- 如果一直下降到第一个元素,因为
nums[-1] = -∞第一个元素天然就是峰值
代码
java
java
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型一维数组
* @return int整型
*/
public int findPeakElement (int[] nums) {
// write code here
if(nums.length==1){
return 0;
}
int i=0;
int j=nums.length-1;
while(i<j){
int mid=(i+j)>>>1;
if(nums[mid]<nums[mid+1]){
i=mid+1;
}else{
j=mid;
}
}
return j;
}
}
python
python
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param nums int整型一维数组
# @return int整型
#
class Solution:
def findPeakElement(self, nums):
left = 0
right = len(nums) - 1
while left < right:
mid = (left + right) // 2
if nums[mid] < nums[mid + 1]:
# 右边更高 → 峰值一定在右边
left = mid + 1
else:
# 左边更高 → 峰值一定在左边
right = mid
return right
数组中的逆序对
题目
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P mod 1000000007
数据范围: 对于 50%的数据, 𝑠𝑖𝑧𝑒≤104
对于 100% 的数据, 𝑠𝑖𝑧𝑒≤105
数组中所有数字的值满足 0≤𝑣𝑎𝑙≤109
要求:空间复杂度 𝑂(𝑛),时间复杂度 𝑂(𝑛𝑙𝑜𝑔𝑛)
输入描述:
题目保证输入的数组中没有的相同的数字
示例1
输入:[1,2,3,4,5,6,7,0]
返回值:7
示例2
输入:[1,2,3]
返回值:0
分析

一直这样划分,得到一个数字一组,然后把数组恢复成有序的,如果当前数字大于右半部分的,那么当前数字的后面也会大于右半部分,也能组成逆序对
代码
java
java
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型一维数组
* @return int整型
*/
int MOD=1000000007;
int count=0;
public int InversePairs (int[] nums) {
// write code here
if (nums == null || nums.length <= 1) {
return 0;
}
count = 0;
merge(nums, 0, nums.length - 1);
return count % MOD;
}
public void merge(int [] nums,int left,int right){
if(right<=left){
return;
}
int mid=(left+right)>>>1;
merge(nums,left,mid);
merge(nums,mid+1,right);
merge2(nums,left,mid,right);
}
public void merge2(int [] nums,int left,int mid,int right){
int [] temp=new int[right-left+1];
int i=left;
int j=mid+1;
int index=0;
while(i<=mid && j<=right){
if(nums[i]<=nums[j]){
temp[index++]=nums[i++];
}else{
count = (count + mid - i + 1) % MOD;
temp[index++] = nums[j++];
}
}
while (i <= mid) temp[index++] = nums[i++];
while (j <= right) temp[index++] = nums[j++];
// 复制回原数组
for (int k = 0; k < temp.length; k++) {
nums[left + k] = temp[k];
}
}
}
python
python
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param nums int整型一维数组
# @return int整型
#
class Solution:
MOD=1000000007
def InversePairs(self , nums: List[int]) -> int:
# write code here
if len(nums)==0:
return 0
self.count=0
self.merge(nums,0,len(nums)-1)
return self.count
def merge(self,nums,left,right):
if left>=right:
return
mid=(left+right)//2
self.merge(nums,left,mid)
self.merge(nums,mid+1,right)
return self.merge2(nums,left,mid,right)
def merge2(self,nums,left,mid,right):
temp=[0] * (right-left+1)
i=left
j=mid+1
index=0
while i<=mid and j<=right:
if nums[i]<=nums[j]:
temp[index]=nums[i]
index+=1
i+=1
else:
self.count=(self.count+mid-i+1)%self.MOD
temp[index]=nums[j]
index+=1
j+=1
while i<=mid:
temp[index]=nums[i]
index+=1
i+=1
while j<=right:
temp[index]=nums[j]
index+=1
j+=1
for i in range(len(temp)):
nums[left+i]=temp[i]
旋转数组的最小数字
题目
有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。
数据范围:1≤𝑛≤10000,数组中任意元素的值: 0≤𝑣𝑎𝑙≤10000
要求:空间复杂度:O(1) ,时间复杂度:O(logn)
示例1
输入:[3,4,5,1,2]
返回值:1
示例2
输入:[3,100,200,3]
返回值:3
分析
这道题要找最小的,他反转以后是分阶段的,和中间的数字比,如果中间的数字大于最后一个数字,那么最小的数字一定在右边,不然一定在左边,如果相等的话那就说明是重复 的数字,没有办法判断,只能一点一点缩小范围。以至于为什么要和最后一个数字比,不和第一个数字比,很浅显易懂的是最后一个数字一定比第一个数字小。更接近于最小的数字
代码
java
java
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型一维数组
* @return int整型
*/
public int minNumberInRotateArray (int[] nums) {
// write code here
int i=0;
int j=nums.length-1;
while(i<j){
int mid=(i+j)>>>1;
if(nums[mid]<nums[j]){
j=mid;
}else if(nums[mid]>nums[j]){
i=mid+1;
}else{
j--;//有重复数字
}
}
return nums[i];
}
}
python
python
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param nums int整型一维数组
# @return int整型
#
class Solution:
def minNumberInRotateArray(self , nums: List[int]) -> int:
# write code here
i=0
j=len(nums)-1
while i<j:
mid=(i+j)//2
if nums[mid]>nums[j]:
i=mid+1
elif nums[mid]<nums[j]:
j=mid
else:
j-=1
return nums[i]
比较版本号
题目
有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。
数据范围:1≤𝑛≤10000,数组中任意元素的值: 0≤𝑣𝑎𝑙≤10000
要求:空间复杂度:𝑂(1) ,时间复杂度:𝑂(𝑙𝑜𝑔𝑛)
示例1
输入:[3,4,5,1,2]
返回值:1
示例2
输入:[3,100,200,3]
返回值:3
分析
难点在于java语言如何去掉前导0
代码
java
java
public class Solution {
public int comp(String num1, String num2) {
if (num1.length() > num2.length()) return 1;
else if (num1.length() < num2.length()) return -1;
else
for (int i = 0; i < num1.length(); i++)
if (num1.charAt(i) != num2.charAt(i))
return num1.charAt(i) - num2.charAt(i)>0?1:-1;
return 0;
}
public int compare (String version1, String version2) {
// write code here
String[] v1Arr = version1.split("\\.0*(?!0\\.)");
String[] v2Arr = version2.split("\\.0*(?!0\\.)");
for (int i = 0; i < (v1Arr.length<v2Arr.length?v1Arr.length:v2Arr.length); i++) {
int r = comp(v1Arr[i], v2Arr[i]);
if (r != 0) return r;
}
return v1Arr.length==v2Arr.length?0:(v1Arr.length-v2Arr.length)>0?1:-1;
}
}
python
python
class Solution:
def compare(self, version1: str, version2: str) -> int:
# 分割并转成整数(自动处理前导零)
v1 = list(map(int, version1.split('.')))
v2 = list(map(int, version2.split('.')))
# 补齐末尾的0,让两个版本段数一样
max_len = max(len(v1), len(v2))
v1 += [0] * (max_len - len(v1))
v2 += [0] * (max_len - len(v2))
# 逐段比较
for a, b in zip(v1, v2):
if a > b:
return 1
elif a < b:
return -1
return 0
总结
二分法考查形式:
单纯的写二分法
找数组中最小的数字
总体来说二分法没有什么太难的,弄清楚题意即可