六月集训(09)二分

1.LeetCode:981. 基于时间的键值存储

原题链接


        设计一个基于时间的键值数据结构,该结构可以在不同时间戳存储对应同一个键的多个值,并针对特定时间戳检索键对应的值。

        实现 TimeMap 类:

        TimeMap() 初始化数据结构对象

        void set(String key,
        String value, int timestamp) 存储键 key、值 value,以及给定的时间戳 timestamp。

        String get(String key, int timestamp)

        返回先前调用 set(key, value, timestamp_prev) 所存储的值,其中 timestamp_prev <= timestamp 。

        如果有多个这样的值,则返回对应最大的 timestamp_prev 的那个值。
如果没有值,则返回空字符串(“”)。
示例:
输入:
[“TimeMap”, “set”, “get”, “get”, “set”, “get”, “get”]
[[], [“foo”, “bar”, 1], [“foo”, 1], [“foo”, 3], [“foo”, “bar2”, 4], [“foo”, 4], [“foo”, 5]]
输出:
[null, null, “bar”, “bar”, null, “bar2”, “bar2”]
提示:
1 <= key.length, value.length <= 100
key 和 value 由小写英文字母和数字组成
1 <= timestamp <= 1e7
set 操作中的时间戳 timestamp 都是严格递增的
最多调用 set 和 get 操作 2 * 1e5 次


        这道题可以在map里面套个map,第一个map的key为题目key,第二个map的key为时间戳,值为val,这样我们对于get操作就只需要利用map中的lower_bound来找到time+1,在让他自减即可。

class TimeMap {
    unordered_map<string,map<int,string>> map;
public:
    TimeMap() {
        map.clear();
    }
    
    void set(string key, string value, int timestamp) {
        map[key][timestamp]=value;
    }
    
    string get(string key, int timestamp) {
        auto tmp=map[key].lower_bound(timestamp+1);
        //因为要找到小于等于timestamp的最大key,这里可以找timesta+1然后让他自减
        if(tmp==map[key].begin()){
            return "";
        }//找不到就返回空
        tmp--;
        return tmp->second;
    }
};

2.LeetCode:400. 第 N 位数字

原题链接


        给你一个整数 n ,请你在无限的整数序列 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, …] 中找出并返回第 n 位上的数字。

        输入:n = 3

        输出:3

        提示:

        1 <= n <= 2^31 - 1


        首先明确n的意义,他指的是将1-n的所有数字拼接成一个字符串后,数字的位数。一个比较简单且效率高的做法是模拟.

        1-9,10范围内的数字有九个,位数是9.

        10-99,100范围内的数字有90个,位数是90 * 2

        100-999,1000范围内的数字有900个,位数是900 * 3

        找到了这个规律之后,我们要做的就是先确定n的范围,然后找到具体的数字,之后再确定具体的位数即可

class Solution {
    #define  ll long long
public:
    int findNthDigit(int n) {
        int cnt=1;//某个范围开始的数字
        int ncnt=1;//某范围的位数
        while(n>9*(ll)cnt*ncnt){
            n-=9*cnt*ncnt;
            cnt*=10;
            ++ncnt;
        }
        int count=(n-1)/ncnt;
        //n-1是为了防止位数和n相等的情况,比如n =3,ncnt=3,这样count=1而不是0
        cnt+=count;
        n-=count*ncnt;
        n=ncnt-n;
        while(n){
            cnt/=10;
            n--;
        }//拿到倒数第n位的数字返回即可
        return cnt%10;
    }
};

3.LeetCode:1300. 转变数组后最接近目标值的数组和

原题链接


        给你一个整数数组 arr 和一个目标值 target ,请你返回一个整数 value ,使得将数组中所有大于 value 的值变成 value 后,数组的和最接近 target (最接近表示两者之差的绝对值最小)。

        如果有多种使得和最接近 target 的方案,请你返回这些整数中的最小值。

        请注意,答案不一定是 arr 中的数字。

        示例 1:

        输入:arr = [4,9,3], target = 10

        输出:3

        示例 2:

        输入:arr = [2,3,5], target = 10

        输出:5

        示例 3:

        输入:arr = [60864,25176,27249,21296,20204], target = 56803

        输出:11361

        提示:

        1 <= arr.length <= 10^4

        1 <= arr[i], target <= 10^5


        先对arr排序,由于题目要求找到某个数,使得让数组中大于他的全部变为他后数组和与target最接近。

        那么我们就以数组中的每个数字为依据。每次检测如果以当前数字为标准,数组的和是否大于target,如果大于了就说明不能再往下继续选择需要求出答案。

        我们想做的是找出一个最接近的数字,那么很自然的想到平均值。比如arr[0]*n就已经大于了target了,那么数组剩下的数字就没有检查的必要了。接下来我们去检查target/n这个数字,如果能够除尽正好是我们想要的结果,但如果没有除尽,就代表着他有余数,我们这时候去看吧剩下未检查的数字都变为target/n和(target/n)+1这两个哪个距离target最接近,找出来并且返回他即可。

        具体的操作就是我们先把已经检查过的数字的和存起来,然后不断遍历数组直到arr[i] * (n-i)大于等于target-prev,之后对i进行判断即可。

class Solution {
public:
    int findBestValue(vector<int>& arr, int target) {
        sort(arr.begin(),arr.end());
        int sum=0;
        for(auto i:arr){
            sum+=i;
        }
        int n=arr.size();
        if(sum<=target){
            return arr.back();
        }
        if(arr[0]*n>=target){
            int ans=target/n;
            if(target-ans*n<=(ans+1)*n-target){
                return ans;
            }else return ans+1;
        }
        int prev=arr[0];
        for(int i=1;i<n;++i){
            int nsum=prev+arr[i]*(n-i);
            if(nsum>=target){
                int ans=(target-prev)/(n-i);
                if(target-prev-ans*(n-i)<=(ans+1)*(n-i)-target+prev){
                    return ans;
                }else 
                    return ans+1;
            }
            prev+=arr[i];
        }
        return 0;
    }
};

4.LeetCode:1713. 得到子序列的最少操作次数

原题链接


        给你一个数组 target ,包含若干 互不相同 的整数,以及另一个整数数组 arr ,arr 可能 包含重复元素。

        每一次操作中,你可以在 arr 的任意位置插入任一整数。比方说,如果 arr = [1,4,1,2] ,那么你可以在中间添加 3 得到 [1,4,3,1,2] 。你可以在数组最开始或最后面添加整数。

        请你返回 最少 操作次数,使得 target 成为 arr 的一个子序列。

        一个数组的 子序列 指的是删除原数组的某些元素(可能一个元素都不删除),同时不改变其余元素的相对顺序得到的数组。比方说,[2,7,4] 是 [4,2,3,7,2,1,4] 的子序列(加粗元素),但 [2,4,2] 不是子序列。

        示例 1:

        输入:target = [5,1,3], arr = [9,4,2,3,4]

        输出:2

        示例 2:

        输入:target = [6,4,8,1,3,2], arr = [4,7,6,2,3,8,6,1]

        输出:3
提示:

        1 <= target.length, arr.length <= 1e5

        1 <= target[i], arr[i] <= 1e9

        target 不包含任何重复元素。


        这道题目还是比较简单的,一旦找到了思路就很容易突破了。先在arr中找到target的元素,并且每找到一个就存下他们在target的下标,之后利用找到这些下标的最长递增序列。这样就代表着我们在arr中找到了target除去了某些数字的最长子序列(可能并不连续)。

        那么要在arr中找到一个可以不连续的子数组使得他们为target,就只需要在对应位置增添上剩余的数字即可,这一定是次数最小的方法。

class Solution {
    unordered_map<int,int> pos;
    vector<int> lcs;
public:
    int minOperations(vector<int>& target, vector<int>& arr) {
        for(int i=0;i<target.size();++i){
            pos[target[i]]=i;
        }
        for(int i=0;i<arr.size();++i){
            if(pos.find(arr[i])!=pos.end()){
                lcs.push_back(pos[arr[i]]);
                cout<<lcs.back()<<" ";
            }
        }
        int a[100005],asize=0;
        for(int i=0;i<lcs.size();++i){
            int l=1,r=asize;
            int ans=-1;
            while(l<=r){
                int mid=l+((r-l)>>1);
                if(lcs[i]<=a[mid]){
                    r=mid-1;
                    ans=mid;
                }else l=mid+1;
            }
            if(ans==-1){
                a[++asize]=lcs[i];
            }else a[ans]=lcs[i];
        }
        return target.size()-asize;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值