有序数组中找x[i]==i的数i
若数组中无重复值,索引值以1增加、数组值以>=1增加。当midVal<midIdx时搜右边[midIdx+1, u],当midVal>midIdx时搜左边[l, midIdx-1]。
若数组中有重复值,索引值以1增加、数组值以>=0增加。当midVal<midIdx时除了搜右边[midIdx+1, u],因为左边可以同值重复,也要搜左边[l, midVal]。同理,midVal>midIdx时除了搜左边[l, midIdx-1],也要搜右边[midVal, u]。
简化:当midVal!=midIdx时,搜索的左边范围可统一成[l, min{midIdx-1, midVal}]。因为当midVal<midIdx时,min{midIdx-1, midVal}=midVal;当midVal>midIdx时,min{midIdx-1, midVal}=midIdx-1。同理,右边范围可统一成[max{ midIdx+1, midVal }, u]。
所以,当midVal!=midIdx时,要搜[l, min{midIdx-1, midVal}]和[max{ midIdx+1, midVal }, u]。直观上,合并使搜索范围尽量小。
有序数组中找值t,该有序数组不知size()只知elementAt()
不知size()就没法取得中位数,可假设该数组后部空位上都是∞,令elementAt()返回-1表示该位置的值太大。见ctci p400
int search(Listy list, int value) {
int idx = 1;
while (list.elementAt(idx) != -1 && list.elementAt(idx) < value) {
idx *= 2;
}
return binarySearch(list, value, idx / 2, idx);
}
int binarySearch(Listy list, int target, int l, int u) {
while (l <= u) {
int m = l + (u - l) / 2;
int val = list.elementAt(m);
if (val == -1 || val > target) {
u = m - 1;
} else if (val < target) {
l = m + 1;
} else {
return m;
}
}
return -1;
}
有序数组中散布着无用数据
若尝试的mid位置是无用数据,则mid一圈圈往左右扩展,用最近的有用元素替代。见ctci p401
int mid = l + (u - l) / 2;
if (strs[mid].isEmpty()) { // mid是无用数据
int left = mid - 1, right = mid + 1;
while (true) {
if (left < l && right > u) { // 找不到有用数据
return -1;
}
if (left >= l && !strs[left].isEmpty()) { // 左看一步
mid = left;
break;
}
if (right <= u && !strs[right].isEmpty()) { // 右看一步
mid = right;
break;
}
left--;
right++;
}
}
...
x的平方根
int mySqrt(int x) {
if (x <= 1) return x;
// 找满足r^2<=x的最大整数r,就是找满足r^2>x的最小整数r的前一个数
int l = 1, u = x;
while (l + 1 < u) {
int mid = l + (u - l) / 2;
if (mid > x / mid) { // mid*mid > x
u = mid;
} else {
l = mid;
}
}
return l;
}
num是完全平方数
bool isPerfectSquare(int num) {
// 找到使x*x>=num的第一个x,二分搜索
int l = 1, u = num;
while (l <= u) {
int mid = l + (u - l) / 2;
if (mid >= num / mid) { // mid*mid >= num
u = mid - 1;
} else {
l = mid + 1;
}
}
return l * l == num;
}
找数组中任一峰值
int findPeakElement(vector<int>& nums) {
// 只要不断往高处走,就一定有波峰,这是一种单调性
// 找波峰,找第一个nums[i]>nums[i+1]的位置i
// i二分搜索的范围是[0..N-2]
const int N = nums.size();
int l = -1, u = N - 1;
while (l + 1 < u) {
int mid = l + (u - l) / 2;
if (nums[mid] > nums[mid+1]) {
u = mid;
} else {
l = mid;
}
}
return u;
}
在先升后降的山坡数组中找数
int findInMountainArray(int target, MountainArray &mountainArr) {
// 二分搜索,先找峰值,再分别找左右半数组
// 找第一个A[m]>A[m+1]的位置m,m在[1..N-2]
const int N = mountainArr.length();
int l = 0, u = N - 1;
while (l + 1 < u) {
int m = l + (u - l) / 2;
if (mountainArr.get(m) > mountainArr.get(m + 1)) {
u = m;
} else {
l = m;
}
}
// u是波峰
int ans = binarySsearch(target, mountainArr, 0, u, true);
if (ans != -1) return ans;
return binarySsearch(target, mountainArr, u, N - 1, false);
}
int binarySsearch(int target, MountainArray &mountainArr,
int lower, int upper, bool asc) {
int l = lower, u = upper;
while (l <= u) {
int m = l + (u - l) / 2;
int val = mountainArr.get(m);
if (target == val) return m;
if ((target < val) == asc) {
u = m - 1;
} else {
l = m + 1;
}
}
return -1;
}
满足x!末尾刚好K个0的数x有多少个
int preimageSizeFZF(int K) {
// 题目:满足x!末尾刚好K个0的数x有多少个?
// x!末尾0的个数等于因子5的个数,因子5的个数每连续5个数相等。
// 因此,满足条件的x若存在就有5个,若不存在就0个。
// 二分搜索猜x是否存在,x的上限取5K,因为5K可至少贡献5K/5=K个因子5。
long l = 0, u = 5l * K;
while (l <= u) {
long mid = l + (u - l) / 2;
int count = countZeros(mid);
if (count == K) return 5;
if (count > K) u = mid - 1;
else l = mid + 1;
}
return 0;
}
int countZeros(long n) {
int ans = 0;
while (n /= 5) {
ans += n;
}
return ans;
}
有序数组中找k个最接近x的元素
vector<int> findClosestElements(vector<int>& arr, int k, int x) {
// 设要找的子段为 arr[i..i+k-1],对位置i做二分搜索
// 比较 arr[i] 和 子段后的arr[i+k]:
// 若 x - arr[i] > arr[i+k] - x,右移子段起始i
int lo = 0, hi = (int)arr.size() - k;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (x - arr[mid] > arr[mid + k] - x) {
lo = mid + 1;
} else {
hi = mid;
}
}
return vector<int>(begin(arr) + lo, begin(arr) + lo + k);
}