这是牛客必刷题第二个知识点(二分查找),大家一起共勉!
17 二分查找-I
题目描述:请实现无重复数字的升序数组的二分查找 要求:给定一个 元素升序的、无重复数字的整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标(下标从 0 开始),否则返回 -1
输入:[-1,0,3,4,6,10,13,14],13 返回值:6 说明:13 出现在nums中并且下标为 6
输入:[],3 返回值:-1 说明:nums为空,返回-1
题目解法:常规的二分查找方法
public int search (int[] nums, int target) {
int left = 0;
int right = nums.length-1;
while(left <=right){
int mid = (right + left)/2;
if(nums[mid] == target)
return mid;
else if(nums[mid]>target)
right = mid-1;
else if(nums[mid]<target)
left = mid+1;
}
return -1;
}
18 二维数组中的查找
题目描述 在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 [[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]] 给定 target = 7,返回 true。 给定 target = 3,返回 false。
方法一:暴力解法 思路分析:直接遍历一遍数组,即可判断目标target是否存在。 复杂度分析 时间复杂度:O(n^2),因为最坏情况下,数组中的元素都需要遍历一次。 空间复杂度:O(1)
public bool Find(int target, vector<vector<int> > array) {
if (array.size() ==0 || array[0].size() ==0) return false;
for (const auto& vec : array) {
for (const int val : vec) {
if (val == target)
return true;
}
}
return false;
}
方法二:二分查找 假设arr数组,val,tar如下图所示: 如果我们把二分值定在右上角或者左下角,就可以进行二分。这里以右上角为例,左下角可自行分析: 具体步骤: 1)我么设初始值为右上角元素,arr[0][5] = val,目标tar = arr[3][1] 2)接下来进行二分操作: 3)如果val == target,直接返回 4)如果 tar > val, 说明target在更大的位置,val左边的元素显然都是 < val,间接 < tar,说明第 0 行都是无效的,所以val下移到arr[1][5] 5)如果 tar < val, 说明target在更小的位置,val下边的元素显然都是 > val,间接 > tar,说明第 5 列都是无效的,所以val左移到arr[0][4] 6)继续步骤2)
public boolean Find(int target, int [][] array) {
int m = array.length;
if(m==0) return false;
int n = array[0].length;
if(n==0) return false;
int r = 0, c = n-1;
while(r < m && c >= 0){
if(target == array[r][c]) return true;
else if(target > array[r][c]) ++r;
else --c;
}
return false;
}
复杂度分析 时间复杂度:O(m+n) ,其中m为行数,n为列数,最坏情况下,需要遍历m+n次。 空间复杂度:O(1)
同理:也可以设置左下角val,然后target值大于val,那么直接列增加【c++】,如果小于val值,那么直接行减小【r–】
19 寻找峰值
题目描述: 给定一个长度为n的数组nums,请你找到峰值并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个所在位置即可。 1.峰值元素是指其值严格大于左右相邻值的元素。严格大于即不能有等于 2.假设 nums[-1] = nums[n] = ?∞ 3.对于所有有效的 i 都有 nums[i] != nums[i + 1]
如输入[2,4,1,2,7,8,4]时,会形成两个山峰,一个是索引为1,峰值为4的山峰,另一个是索引为5,峰值为8的山峰,如下图所示: 输入:[2,4,1,2,7,8,4] 返回值:1 说明:4和8都是峰值元素,返回4的索引1或者8的索引5都可以
分析: nums[mid] < nums[mid + 1]说明在“上坡”,则可以使left = mid +1(因为mid肯定不是峰值),向“峰”处压缩 nums[mid] > nums[mid + 1]说明在“下坡”,则应该使right =mid(mid可能是峰值),往“峰”处压缩
step 1:二分查找首先从数组首尾开始,每次取中间值,直到首尾相遇。 step 2:如果中间值的元素大于它右边的元素,说明往右是向下,我们不一定会遇到波峰,但是那就往左收缩区间。 step 3:如果中间值大于右边的元素,说明此时往右是向上,向上一定能有波峰,那我们往右收缩区间。 step 4:最后区间收尾相遇的点一定就是波峰。
public int findPeakElement (int[] nums) {
int left = 0;
int right = nums.length - 1;
while(left < right){
int mid = (left + right) / 2;
if(nums[mid] > nums[mid + 1])
right = mid;
else
left = mid + 1;
}
return right;}
20 数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P mod 1000000007
示例1 输入:[1,2,3,4,5,6,7,0] 返回值:7 示例2 输入:[1,2,3] 返回值:0
方法一:暴力解法 public int InversePairs(int [] array) { int res = 0; int len = array.length; for(int i = 0;i < len-1;i++){ for(int j=i;j<len;j++){ if(array[i]>array[j]){ res +=1; res =res % 1000000007; } } } return res;
}
但是如果数据量级比较大,就会超时。
方法二:归并方法
先分:分呢,就是将数组分为两个子数组,两个子数组分为四个子数组,依次向下分,直到数组不能再分为止!
后并:并呢,就是从最小的数组按照顺序合并,从小到大或从大到小,依次向上合并,最后得到合并完的顺序数组!
介绍完归并排序,我们来说说归并统计法,我们要在哪个步骤去进行统计呢?
归并统计法,关键点在于合并环节,在合并数组的时候,当发现右边的小于左边的时候,此时可以直接求出当前产生的逆序对的个数。
public class Solution {
int count = 0;
public int InversePairs(int [] array) {
if(array.length < 2)
return 0;
mergeSort(array,0,array.length-1);
return count;
}
public void mergeSort(int[] array,int left,int right){
int mid = left+(right-left)/2;
if(left < right){
mergeSort(array,left,mid);
mergeSort(array,mid+1,right);
merge(array,left,mid,right);
}
}
public void merge(int[] array,int left,int mid,int right){
int[] arr = new int[right-left+1];
int c = 0;
int s = left;
int l = left;
int r = mid+1;
while(l <= mid && r <= right ){
if(array[l] <= array[r]){
arr[c++] = array[l++];
}else{
arr[c++] = array[r++];
count += mid+1-l;
count %= 1000000007;
}
while(l <= mid)
arr[c++] = array[l++];
while(r <= right)
arr[c++] = array[r++];
for(int num:arr){
array[s++] = num;
}
}
}
21 旋转数组的最小数字
题目描述: 有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。
输入:[3,4,5,1,2] 返回值:1 输入:[3,100,200,3] 返回值:3
方法一:暴力解法:直接遍历一遍数组,即可找到最小值。但是本题的附加条件就没有用上。肯定不是面试官所期望的答案
方法二:二分查找 优点:可将遍历法的线性级别 时间复杂度降低至 对数级别 分析:找最小值,array[m] > array[j] 那么范围就是【m+1,j】;array[m] < array[j],那么其中包含m的值,所以范围【i,m】 具体步骤: 1、初始化: 声明 i, j 双指针分别指向 array 数组左右两端 2、循环二分: 设 m = (i + j) / 2 为每次二分的中点( “/” 代表向下取整除法,因此恒有 i≤m+1、当 array[m] > array[j] 时: m 一定在左排序数组中,即旋转点 x 一定在 [m + 1, j] 闭区间内,因此执行 i = m + 1 2、当 array[m] < array[j] 时:m 一定在右排序数组中,即旋转点 x 一定在[i, m]闭区间内,因此执行 j = m 3、当 array[m] = array[j] 时: 无法判断 m 在哪个排序数组中,即无法判断旋转点 x 在 [i, m] 还是 [m + 1, j] 区间中。解决方案: 执行 j = j - 1 缩小判断范围 3、返回值: 当 i = j 时跳出二分循环,并返回旋转点的值 array[i] 即可。
public int minNumberInRotateArray(int [] array) {
if (array.length== 0) {
return 0;
}
int i = 0, j = array.length - 1;
while (i < j) {
int m = (i + j) / 2;
if (array[m] > array[j]) i = m + 1;
else if (array[m] < array[j]) j = m;
else j--;
}
return array[i];
}
22 比较版本号
题目描述:牛客项目发布项目版本时会有版本号,比如1.02.11,2.14.4等等 现在给你2个版本号version1和version2,请你比较他们的大小。
比较规则: 一. 比较版本号时,请按从左到右的顺序依次比较它们的修订号。比较修订号时,只需比较忽略任何前导零后的整数值。比如"0.1"和"0.01"的版本号是相等的 二. 如果版本号没有指定某个下标处的修订号,则该修订号视为0。例如,“1.1"的版本号小于"1.1.1”。因为"1.1"的版本号相当于"1.1.0",第3位修订号的下标为0,小于1 三. version1 > version2 返回1,如果 version1 < version2 返回-1,不然返回0.
示例1 输入:“1.1”,“2.1” 返回值:-1 说明:version1 中下标为 0 的修订号是 “1”,version2 中下标为 0 的修订号是 “2” 。1 < 2,所以 version1 < version2,返回-1
示例2 输入:“1.1”,“1.01” 返回值:0 说明:version2忽略前导0,为"1.1",和version相同,返回0 示例3 输入:“1.1”,“1.1.1” 返回值:-1 说明:“1.1"的版本号小于"1.1.1”。因为"1.1"的版本号相当于"1.1.0",第3位修订号的下标为0,小于1,所以version1 < version2,返回-1 示例4 输入:“2.0.1”,“2” 返回值:1 说明:version1的下标2>version2的下标2,返回1 示例5 输入:“0.226”,“0.36” 返回值:1 说明:226>36,version1的下标2>version2的下标2,返回1
解题分析:双指针,使用v1和v2这两个值来分别计算版本号每个被’.'分割的块的版本号的大小,如果不相等,则进行比较
具体步骤: 1、初始化双指针v1,v2分别为0 2、分别对两个版本字符串进行遍历 1、遍历第一个版本字符串(索引 i),以 ‘.’ 为分割点(循环结束条件),计算每一块的版本号大小,记为 V1; 2、遍历第二个版本字符串(索引 j),以 ‘.’ 为分割点(循环结束条件),计算每一块的版本号大小,记为 V2; 3、对比V1, V2 1、:继续遍历两个版本字符串 , 2、:直接返回 1 3、:直接返回 -1
public class Solution {
public int sgn(int a ,int b){
if(a>b) return 1;
else if(a<b) return -1;
else return 0;
}
public int compare (String version1, String version2) {
int i=0, j=0;
int l1 = version1.length(),l2 = version2.length();
int v1 = 0,v2 = 0;
while (i < l1 || j < l2) {
while (i < l1 && version1.charAt(i) != '.')
v1 = v1*10 + (version1.charAt(i++) - '0');
while (j < l2 && version2.charAt(j) != '.')
v2 = v2*10 + (version2.charAt(j++)- '0');
if (v1 != v2) return sgn(v1, v2);
v1 = v2 = 0;
i++;
j++;
}
return 0;
}
}
时间复杂度。其中 和 指的是输入字符串的长度。遍历版本字符串时间
空间复杂度:使用双指针及其他常数级空间变量
|