六月集训(第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();
    }
};
posted @ 2022-06-09 16:42  番茄元  阅读(27)  评论(0)    收藏  举报