[英雄星球六月集训LeetCode解题日报] 第九日 二分

一、 327. 981. 基于时间的键值存储

链接: 981. 基于时间的键值存储

1. 题目描述

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

实现 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 的那个值。
如果没有值,则返回空字符串(“”)。

2. 思路分析

题目限制时间戳是严格递增的,不存在重复,因此内层不需要有序表,本身是有序的。
字典搜到直接二分即可。

3. 代码实现

class TimeMap:
    def __init__(self):
        self.db = collections.defaultdict(list)
    def set(self, key: str, value: str, timestamp: int) -> None:
        self.db[key].append((timestamp,value))
    def get(self, key: str, timestamp: int) -> str:
        strs = self.db.get(key)
        if strs is None:
            return ''
        idx = bisect_right(strs,timestamp,key=lambda x:x[0])
        if idx == 0:
            return ''
        return strs[idx-1][1]

二、 400. 第 N 位数字

链接: 400. 第 N 位数字

1. 题目描述

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

2. 思路分析

找到这个数在几位数上。
这题数据规模小,可以打表。
今天的每日一题类似,但不能打标。
497. 非重叠矩形中的随机点

3. 代码实现

class Solution:
    def findNthDigit(self, n: int) -> int:
        if n <= 9:
            return n
        DL = [0, 9, 189, 2889, 38889, 488889, 5888889, 68888889, 788888889]
        d = bisect_left(DL,n)  # n在d位数上
        n -= DL[d-1]+1  # 砍掉位数少于d的前边数字,问题转化为求在d位数上的第n位,从0开始
        x = 10**(d-1) + n//d
        return int(str(x)[n%d])

三、1300. 转变数组后最接近目标值的数组和

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

1. 题目描述

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

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

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

2. 思路分析

  1. 先把数组排序,这样每次把大于value的数处理转变的时候可以直接操作一个区间。
  2. 既然要算区间和,先计算前缀和。那么转变后的和change=前半段和+后半段长度*value。
  3. 这样先二分value的pos,再二分转变后的和即可。
  4. 排序nlgn,计算前缀和n,两层二分lgnlgn.,总复杂度O(nlgn)

3. 代码实现

class Solution:
    def findBestValue(self, arr: List[int], target: int) -> int:
        n = len(arr)
        top = max(arr)+1
        arr.sort()
        presum = list(accumulate(arr,initial=0))
        def sum_interval(i,j):
            return presum[j+1]-presum[i]
        s = presum[n]
        def change(x):
            # 如果答案是x,计算改编后的数组和
            pos = bisect_right(arr,x)
            if pos >= n:
                return presum[n]
            if pos == 0:
                return n*x
            return sum_interval(0,pos-1)+x*(n-pos)
        
        ans = bisect_right(range(top),target,key=change)
        if abs(change(ans-1)-target) <= abs(change(ans)-target):
            ans -= 1
        return ans

四、 1713. 得到子序列的最少操作次数

链接: 1713. 得到子序列的最少操作次数

1. 题目描述

给你一个数组 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] 不是子序列。

2. 思路分析

求出最长公共子序列(LCS),然后补充剩下字符即可。答案=len(target)-len(lcs)。
可以直接套板
普通LCS的DP做法是n^2的,需要二分优化版本。
二分优化版的思路是通过下标转化后,问题转化为LIS,LIS存在二分优化版本。
这题由于s1本身是去重的,因此更简单。
参考我的刷题模板:
[python刷题模板] 最长递增子序列LIS、最长公共子序列 LCS

3. 代码实现

class Solution:
    def minOperations(self, target: List[int], arr: List[int]) -> int:
        '''
        题意一看是找LCS(最长公共子序列)。然后给arr中补上target中剩下的数就行。
        但是LCS标准DP是O(nm),过不了。
        需要转化成LIS(最长上升子序列)来二分优化。
        本题最长公共子序列的意思是target中的数,在arr中尽量找到的多,且相对位置保持不变。
        位置就是下标,位置有序转换过去就是下标递增。
        由于target本身是去重的,因此可以翻转下标,用下标唯一来代表这个数。
        也就是说把target中每个数的下标映射到arr中,寻找子序列,下标是递增的,问题转化为LIS
        arr中的不在target中的字符是没用的,可以先干掉。
        '''
                
        def lis_nlgn(nums):
            # 最长上升序列的二分优化版本
            n = len(nums)
            # dp = [float('inf')] * (n+1)  # dp[i] 为长度为i的上升子序列的可以的最小尾巴  
            dp = []          
            for num in nums:
                if not dp or num > dp[-1]:
                    dp.append(num)
                else:
                    pos = bisect_left(dp,num)
                    dp[pos] = num            
            return len(dp)

        def lcs_nlgn_when_s1_unique(s1,s2):
            # 当s1本身是去重的,用这个,下标不用拼list
            s1_poses = {v:pos for pos,v in enumerate(target)}
            cleaned = [s1_poses[x] for x in arr if x in s1_poses]           
            return lis_nlgn(cleaned)
        def lcs_nlgn(s1,s2):
            # 转lis利用二进制优化。
            s1_poses = collections.defaultdict(list)
            for i in range(len(s1)-1,-1,-1):  # 需要逆序
                s1_poses[s1[i]].append(i)
            cleaned_s2_pos = list(itertools.chain.from_iterable([s1_poses[x] for x in s2 if x in s1_poses]))
            return lis_nlgn(cleaned_s2_pos)
             
        return len(target)-lcs_nlgn_when_s1_unique(target,arr)

五、附每日一题 497. 非重叠矩形中的随机点

链接: 497. 非重叠矩形中的随机点

1. 题目描述

给定一个由非重叠的轴对齐矩形的数组 rects ,其中 rects[i] = [ai, bi, xi, yi] 表示 (ai, bi) 是第 i 个矩形的左下角点,(xi, yi) 是第 i 个矩形的右上角点。设计一个算法来随机挑选一个被某一矩形覆盖的整数点。矩形周长上的点也算做是被矩形覆盖。所有满足要求的点必须等概率被返回。

在给定的矩形覆盖的空间内的任何整数点都有可能被返回。

请注意 ,整数点是具有整数坐标的点。

实现 Solution 类:

Solution(int[][] rects) 用给定的矩形数组 rects 初始化对象。
int[] pick() 返回一个随机的整数点 [u, v] 在给定的矩形所覆盖的空间内。

2. 思路分析

这题和今天第二题差不多,但是不能打表了。
每个方块能覆盖的整数点个数为(y2-y1+1)*(x2-x1+1)。
由于每个方块不重叠,因此可以计算前缀和。
得出一共有多少个点,在这个点范围随机一个数,然后找出这个点即可。

3. 代码实现

class Solution:

    def __init__(self, rects: List[List[int]]):
        self.rects = rects
        self.rect_cnt = [(y2-y1+1)*(x2-x1+1) for x1,y1,x2,y2 in rects ]  # 第i个矩形有几个点
        self.presum = list(accumulate(self.rect_cnt,initial=0))
        
    def pick(self) -> List[int]:
        k = randrange(self.presum[-1])  # [0,n) 从n个点中随机取一个。
        d = bisect_right(self.presum,k)  # k 在第d个箱子上
        k -= self.presum[d-1]  
        # 找这个箱子的第k个点(从0开始)
        x1,y1,x2,y2 = self.rects[d-1]        
        return [x1+k//(y2-y1+1),y1+k%(y2-y1+1)]

六、参考链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值