其实我们这是以一个简单的算法题开始的:
一个存在重复数据的有序数组A,现在给定一个数字key,我要找到数组中与该给定值差的绝对值最小的那个数的最开始的位置。
case:
给定数组A:1, 1, 2, 2, 3, 3, 3, 3, 3, 4, 4, 6
给定数值key:5
输出:9(即第一个4的位置)
结题思路 :
题意要求我们返回数组A中与该给定的Key的差的绝对值最小的那个数的最开始的位置。
这里可以从最简单的思路开始(二分):
- 如果当前值等于Key,返回与当前值相同的重复数字最开始的位置
- 如果当前值大于Key,在包含当前值的前半部分继续应用二分,因为后半部分的与Key差的绝对值必然大于等于当前值
- 如果当前值小于Key,这时候我们需要判断当前位置右侧的值ValueRight
1. ValueRight <= Key,取ValueRight位置开始的后半部分继续应用二分
2. ValueRight > Key,注意这里就是一个落差出现的地方,我们需要的值就是当前值与ValueRight 之间
上面的思路看上去有点复杂,我们仔细观察一组数据就会发现,这样的值总是在相等数值的位置或者是一个有落差的位置(A < Key < B)此时判断二者的差的绝对值取最小的那个数即可,最后在向前寻找最开始的位置。
如果基于上面的思路再做一次总结就会发现,程序总共分为两步走:
- 先找到第一个满足A <= Key的值的位置,然后判断A的右侧的值,返回差的绝对值最小的数的位置
- Key = A,再利用一遍1的方法。
程序步骤:
第一步. 先找到第一个满足A <= Key的值的位置,然后判断A的右侧的值,返回差的绝对值最小的数的位置
第二步. Key = A,再利用一遍1的方法。
具体程序如下:
int findFirstOccur(vector<int>& A, int start, int end, int key)
{
int mid;
int pos = 0;
while(start < end)
{
mid = (start + end) / 2;
if(A[mid] < key)
{
start = mid + 1;
pos = start;
}
else
{
end = mid;
pos = end;
}
}
return pos;
}
int solve(vector<int> A, int key)
{
int site1 = findFirstOccur(A, 0, A.size() - 1, key);
if(A[site1] == key || site1 == 0)
return site1;
else
{
if(A[site1] - key < key - A[site1 - 1])
return site1;
else
return findFirstOccur(A, 0, site1 - 1, A[site1 - 1]);
}
}
到这里,我们已经写完了,如果程序已经明确我们的数组A是个肺递减数列的话,其实没有必要再去重复写findFirstOccur的函数,我们家的STL其实已经自带了。现在我们来看下:
STL中的每个算法都非常精妙,接下来的几天我想集中学习一下STL中的算法。
ForwardIter lower_bound(ForwardIter first, ForwardIter last,const _Tp& val)算法返回一个非递减序列[first, last)中的第一个大于等于值val的位置。
ForwardIter upper_bound(ForwardIter first, ForwardIter last, const _Tp& val)算法返回一个非递减序列[first, last)中第一个大于val的位置。
//这个算法中,first是最终要返回的位置
int lower_bound(int *array, int size, int key)
{
int first = 0, middle;
int half, len;
len = size;
while(len > 0) {
half = len >> 1;
middle = first + half;
if(array[middle] < key) {
first = middle + 1;
len = len-half-1; //在右边子序列中查找
}
else
len = half; //在左边子序列(包含middle)中查找
}
return first;
}
int upper_bound(int *array, int size, int key)
{
int first = 0, len = size-1;
int half, middle;
while(len > 0){
half = len >> 1;
middle = first + half;
if(array[middle] > key) //中位数大于key,在包含last的左半边序列中查找。
len = half;
else{
first = middle + 1; //中位数小于等于key,在右半边序列中查找。
len = len - half - 1;
}
}
return first;
}