六月集训(第09天)—二分查找
二分查找
1. 981. 基于时间的键值存储
思路:
找到大于等于timestamp + 1的最小位置,返回其之前一位置即为答案,否则不存在答案。
class TimeMap {
unordered_map<string, map<int, string>> hash;
public:
TimeMap() {
hash.clear();
}
void set(string key, string value, int timestamp) {
hash[key][timestamp] = value;
}
string get(string key, int timestamp) {
auto iter = hash[key].lower_bound(timestamp + 1); // 找到 大于等于时间戳的第一个位置,
if (iter == hash[key].begin()) { // 没有值
return "";
}
--iter;
return iter->second;
}
};
/**
* Your TimeMap object will be instantiated and called as such:
* TimeMap* obj = new TimeMap();
* obj->set(key,value,timestamp);
* string param_2 = obj->get(key,timestamp);
*/
2. 400. 第 N 位数字
思路1: 寻找数字规律
自己根据数字的特性,找出n对应的数字,取出对应的位数。 没用二分,$O(1)$时间解决问题。

class Solution {
public:
int findNthDigit(int n) {
// 对应1位数、2位数,3位数。。。的个数
long long hash[] = {10, 90, 900, 9000, 90000, 900000, 9000000, 90000000, 900000000, 9000000000, 90000000000};
// 数字的位数开始值
long long base_hash[] = {0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000};
int base = 0, mark = 0, ans = 0, bit = 0;
int i = 0;
for (int i = 0; i < 10; ++i) {
if (n < hash[i] * (i + 1)) {
mark = n % (i + 1) + 1; // 对应数字的从高往低数第几位
bit = i + 1; // 对应数字的位数
n = n / (i + 1); // 是位数为i+1的数区间内的第几个数
base = base_hash[i]; // 对应i+1位数字区间的开始值
break;
}
else n -= hash[i] * (i + 1);
}
ans = n + base; // n对应的数字
// 找出n在对应得数字中得位数
while (bit - mark) {
ans /= 10;
mark++;
}
return ans % 10;
}
};
思路1:
再把空间优化一下,其实空间已经够小了,没必要优化了。

class Solution {
public:
int findNthDigit(int n) {
// 对应1位数、2位数,3位数。。。的个数
long long hash = 10;
// 数字的位数开始值
long long base_hash = 0;
int base = 0, mark = 0, ans = 0, bit = 0;
int i = 0;
for (int i = 0; i < 10; ++i) {
if (i == 0) hash = 10, base_hash = 0;
else if (i == 1) hash = 90, base_hash = 10;
else hash *= 10, base_hash *= 10;
if (n < hash * (i + 1)) {
mark = n % (i + 1) + 1; // 对应数字的从高往低数第几位
bit = i + 1; // 对应数字的位数
n = n / (i + 1); // 是位数为i+1的数区间内的第几个数
base = base_hash; // 对应i+1位数字区间的开始值
break;
}
else n -= hash * (i + 1);
}
ans = n + base; // n对应的数字
// 找出n在对应得数字中得位数
while (bit - mark) {
ans /= 10;
mark++;
}
return ans % 10;
}
};
思路2:
在对应位数的区间内用二分查找,偷懒就不写了。
3. 1300. 转变数组后最接近目标值的数组和
思路:
二分枚举value的值,预处理前缀和,利用前缀和计算每个value值对应的结果ans,将ans与target对比,选择差值最小时对应的value值。
class Solution {
public:
int findBestValue(vector<int>& arr, int target) {
int arr_size = arr.size(), i;
sort(arr.begin(), arr.end()); // 数组升序排列
int sum[arr_size + 1];
// 前缀和
sum[0] = 0;
for (i = 1; i <= arr_size; ++i) sum[i] = sum[i - 1] + arr[i - 1];
int l = 0, r = arr[arr_size - 1];
int value = 0;
int ans = INT_MAX;
while (l <= r) { /* 二分枚举value的值,范围是数组中的最小值到最大值 */
int mid = l + ((r - l) >> 1);
int idx = lower_bound(arr.begin(), arr.end(), mid) - arr.begin(); /* 获取arr中大于等于mid最小位置 */
int new_ans = sum[idx] + (arr_size - idx) * mid; /* value == mid 时对应的答案 */
if (abs(ans - target) > abs(new_ans - target)) ans = new_ans, value = mid; /* 更新value值,使结果更接近target */
else if (abs(ans - target) == abs(new_ans - target) && mid < value) ans = new_ans, value = mid; /* 当mid和value得到的结果与target的差值相等时,取较小值 */
// 二分查找框架
if (new_ans > target) r = mid - 1;
else if (new_ans < target) l = mid + 1;
else break;
}
return value;
}
};
4. 1713. 得到子序列的最少操作次数
思路:
最少操作次数为 target.size() - (target和arr的最长公共子序列)
利用下标映射,将问题转化为求公共子序列的最长上升子序列。
class Solution {
public:
int minOperations(vector<int>& target, vector<int>& arr) {
int target_size = target.size(), i;
int arr_size = arr.size();
unordered_map<int, int> mmp;
for (i = 0; i < target_size; ++i) mmp[ target[i] ] = i;
vector<int> dp;
for (i = 0; i < arr_size; ++i) {
if ( mmp.count(arr[i]) ) { /* 取出target与arr相同的元素来寻找最长上升子序列 */
int idx = mmp[arr[i]];
int l = -1, r = dp.size();
while (l + 1 < r) {
int mid = l + ((r - l) >> 1);
if (dp[mid] < idx) {
l = mid;
} else {
r = mid;
}
}
if (r == dp.size()) {
dp.push_back(idx);
} else {
dp[r] = idx;
}
}
}
return target_size - dp.size();
}
};
东方欲晓,莫道君行早。

浙公网安备 33010602011771号