(1) 二分查找
链接
二分查找需要序列是有序的,否则二分查找会失效。原理就是如果找的值比mid小,那么[mid,R]的内容就不需要再查找了,反之如果大于mid位置的值,则在[L,mid]内的值也不需要再查找。同时将L/R的值进行修改。注意循环结束的条件为L<=R。
public int search (int[] nums, int target) {
// write code here
if(nums.length==0 || nums.length==1&&nums[0]!=target){
return -1;
}
int L=0, R=nums.length;
while(L<=R){
int mid=(L+R)/2;
if(nums[mid]==target){
return mid;
}else if(nums[mid]<target){
L=mid+1;
}else{
R=mid-1;
}
}
return -1;
}
(2)寻找峰值
链接
最普通的方法就是O(n),即从头到尾遍历,找到山峰就返回。
public int findPeakElement (int[] nums) {
// write code here
if(nums.length==1){
return 0;
}
int i;
for(i=0; i<nums.length; i++){
if(i==0 && nums[i]>nums[i+1]){
break;
}else if(i==nums.length-1 && nums[i]>nums[i-1]){
break;
}else if(i>0 && nums[i]>nums[i-1] && nums[i]>nums[i+1]){
break;
}
}
return i;
}
但如果使用二分查找的方法,就会更快,即O(logN)。当然,我们需要证明一下正确性:
- 如果
nums[mid]
大于nums[mid + 1]
,则在mid
左侧(包括mid
)必定存在一个峰值。这是因为如果nums[mid]
是一个下降点,那么在左侧必定存在一个局部最大值。
例如上图,mid大于mid+1的位置,那么右侧是可能没有峰值的。但左侧必定存在峰值。要么就是mid为峰值,要么就是mid-1更大,而就需要比较mid-2。(有一点递归的意思),而我们的设定是nums[-1] = nums[n] = −∞,因此左侧必定出现峰值。 - 如果
nums[mid]
小于nums[mid + 1]
,则在 mid 右侧(不包括mid
)必定存在一个峰值。这是因为如果nums[mid]
是一个上升点,那么在右侧必定存在一个局部最大值。
正确性可以得到了,那么我们就可以写代码,用二分查找的思路:
public int findPeakElement (int[] nums) {
// write code here
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = (right + left) / 2;
if (nums[mid] > nums[mid + 1]) {
// 峰值在左边
right = mid;
} else {
// 峰值在右边
left = mid + 1;
}
}
return left;
}
注意,一定会存在峰值,因此不需要我们特殊去判断是否为峰值,结束循环返回结果即可,这样可以让代码非常整洁。注意,和二分查找不一样,我们更新right不能为mid-1,否则可能找不到峰值。如下所示:
(3) 数组中的逆序对
链接
最简单的方法自然就是遍历,但这样的时间复杂度会很大,肯定不是优选。
我们可以用归并排序的思路。
假设我们有以下未排序的数组:
[8, 4, 2, 1]
我们想要计算这个数组中的逆序对的数量。逆序对是指数组中的两个数字,如果它们的原始位置是i和j,且i < j但A[i] > A[j],那么它们就构成一个逆序对。
让我们使用归并排序的过程来计算逆序对:
- 第一步:分解
将数组分解为更小的数组:
[8, 4] [2, 1]
继续分解:
[8] [4] [2] [1]
- 第二步:合并并计算逆序对
现在我们开始合并这些数组,并在合并过程中计算逆序对。
合并 [8] 和 [4]:
比较 8 和 4,发现 8 > 4,因此它们构成一个逆序对。我们将逆序对的数量增加 1,得到 1。
合并后的数组是 [4, 8]。
合并 [2] 和 [1]:
比较 2 和 1,发现 2 > 1,因此它们构成一个逆序对。我们将逆序对的数量增加 1,得到 2。
合并后的数组是 [1, 2]。
现在我们有 [4, 8] 和 [1, 2] 两个已排序的数组,接下来合并这两个数组:
比较 4 和 1、4 和 2,逆序数加2。
比较 8 和 1、8 和 2,逆序数夹2.因此最终逆序数为6.
最终合并后的数组是 [1, 2, 4, 8]。
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型一维数组
* @return int整型
*/private static final int MOD = 1000000007;
public int InversePairs(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int[] copy = new int[nums.length];
System.arraycopy(nums, 0, copy, 0, nums.length);
return (int) mergeSort(nums, copy, 0, nums.length - 1);
}
private long mergeSort(int[] nums, int[] copy, int start, int end) {
if (start >= end) {
return 0;
}
int mid = start + (end - start) / 2;
long left = mergeSort(copy, nums, start, mid) % MOD;
long right = mergeSort(copy, nums, mid + 1, end) % MOD;
int i = mid, j = end;
int copyIndex = end;
long count = 0;
while (i >= start && j > mid) {
if (nums[i] > nums[j]) {
count += j - mid;
copy[copyIndex--] = nums[i--];
if (count >= MOD) {
count %= MOD;
}
} else {
copy[copyIndex--] = nums[j--];
}
}
while (i >= start) {
copy[copyIndex--] = nums[i--];
}
while (j > mid) {
copy[copyIndex--] = nums[j--];
}
for (i = start; i <= end; i++) {
nums[i] = copy[i];
}
return (left + right + count) % MOD;
}
}
(4) 旋转数组的最小数字
链接
我们可以把数组分为两部分,如图所示:
旋转数组的特点这两个子数组也是非递减的,这也就可以推断出,mid
的值可能大于最右端的值,也可以小于(先不讨论等于)。
- 如果大于最右端的值,则意味着现在的
mid
指向的位置在第一个子数组中 - 如果小于最右端的值,则意味着现在的
mid
指向的位置在第二个子数组中。
如果是第一种情况,则需要移动left
,这样就可以逼近最小值。如果是第二种情况,就移动right
,这样可以逼近最小值。
如果出现相同元素,例如:
2,2,2,1,2
这样是无法确定怎么移动的,我们可以将right左移。
public int minNumberInRotateArray (int[] nums) {
// write code here
int left = 0, right = nums.length - 1, mid = 0;
while (left <= right) {
mid = (left + right) / 2;
if (nums[mid] > nums[right]) {
left = mid + 1;
} else if (nums[mid] < nums[right]) {
right = mid;
} else {
// 当 nums[mid] 和 nums[right] 相等时,无法确定最小值的位置
// 将右指针左移一位
right--;
}
}
return nums[mid];
}
(5) 比较版本号
链接
可以用双指针,在while
里比较每两个.
之间值的大小。每次都循环直到达到最大长度或者遇到.
,然后计算出两个.
中间值的大小,并且进行比较。如果一个版本已经遍历完但另一个还没有,也可以继续循环,这就相当于遍历完的版本号后面多出的0,因为我们的tmp设置都为0,所以不影响比较。
version1:2.0.0.1
version2:2
比如这个用例,在version2
遍历完后仍可以继续执行while
,version1
中间的两个0在计算时得到的tmp1
都为0,这与tmp2
相等,因此相当于version2
也为2.0.0.0
。
public int compare (String version1, String version2) {
// write code here
int len1=version1.length(), len2=version2.length();
int i=0, j=0;
while(i<len1 || j<len2){
long tmp1=0;
while(i<len1 && version1.charAt(i)!='.'){
tmp1= tmp1*10 + (version1.charAt(i)-'0');
i++;
}
long tmp2=0;
while(j<len2 && version2.charAt(j)!='.'){
tmp2= tmp2*10 + (version2.charAt(j)-'0');
j++;
}
i++;j++; // 跳过'.'
if(tmp1!=tmp2){
return tmp1<tmp2? -1: 1;
}
}
return 0;
}