LeetCode 1480 - 1483

一维数组的动态和

给出一个数组,计算这个数组的动态和(前缀和)

前缀和、差分_菜徐鸭的博客-CSDN博客

数组长度为 1000,直接枚举,时间复杂度 O(n^2)

相邻两个 Si 的递推关系:Si = Si-1 + ai,用递推式可以做到 O(n) 的时间复杂度

前缀和可以用于快速计算 a 数组中某一段连续区间的和,计算 aL + . . . + aR = SR - SL-1

class Solution {
public:
    vector<int> runningSum(vector<int>& nums) {
        //定义答案数组
        vector<int> sum(nums.size());
        sum[0] = nums[0];
        //数组最小长度是1,不会出现空数组的情况
        //i 从 1 开始一直循环到 num.size()
        for(int i = 1;i < nums.size();i++)
            sum[i] = sum[i - 1] + nums[i];
        return sum;
    }
};

不同整数的最少数目

 

给出一个数组 arr 和一个整数 k,需要从数组中恰好移除 k 个元素,使得移除后数组中不同整数的个数最少

移走 k 个数,并且需要移走更多种类,因此从个数最少的数开始移走

示例 1:

数组中的数为:5  4  4

需要移除 1 个数,移除 4 后,不同的数就只有 5

示例 2:

数组中的数为:4  3  1  1  3  3  2

需要移除 3 个数,先移除 4、2,再从 1 或 3 中选择一个数移走,不同的数就只有 1、3

数据范围为 10^5,时间复杂度要控制在 nlogN

需要移走一些数,要求剩余的数中不同的数最少:尽量移走更多种类的数

需要先统计所有数出现的个数,按它们出现的次数排序,排序后,从个数最少的开始移,统计最多可以移走多少个数,剩下的数的个数就是答案

class Solution {
public:
    int findLeastNumOfUniqueInts(vector<int>& arr, int k) {
        //哈希表 统计所有数出现的次数
        unordered_map<int,int> hash;
        //枚举所有的数
        for(auto x : arr) hash[x] ++ ;
        //将所有的次数排序
        vector<int> cnt;
        for(auto x : hash) cnt.push_back(x.second);
        //从小到大排序
        sort(cnt.begin(),cnt.end());
        //从总数开始移动
        int res = cnt.size();
        //从小到大枚举所有的个数
        for(auto c : cnt) {
            if(k >= c) {
                //如果 k >= c,可以把 c 个数全部移走
                k -= c;
                //种类数--
                res --;
            } else {
                //当前 k 不足 c 个,说明当前数不能完全移走
                break;
            }
        }
        return res;
    }
};

制作 m 束花所需的最少天数

给出一个数组, 以及两个整数 m 和 k

m 表示一共要制作 m 束花,k 表示每一束花至少需要连续相邻的 k 朵

数组中的第 i 个数表示第 i 朵花盛开的时间

示例 1:

一共有 5 朵花,给出了 5 朵花盛开的时间,第 1 朵花在第 1 天开,第 2 朵花在第 10 天开,第 3 朵花在第 3 天开,第 4 朵花在第 10 天开,第 5 朵花在第 2 天开. . .

一共要凑 3 束花,每束花有 1 朵(连续的一段)

第 1 天只有 1 朵花盛开,可以凑出一束花;第 2 天也有 1 朵花盛开,可以凑出两束花;第 3 天又有 1 朵花盛开,从而凑出 3 束花

只需要 3天就可以把 3 束花找出来

示例 2:

一共只有 5 朵花,无法凑出 3 束花

示例 3:

一共有 7 朵花,前 4 朵花都是在第 7 天开,第 5 朵花在第 12 天开,第 6、7 朵花都是在第 7 天开

一共需要两束花,每束花需要连续的 3 朵花盛开

第 7 天虽然盛开了 6 朵花,但是只能在第一段中凑出一束花,第二段中的 3 朵花不连续,不能使用

在第 12 天后,最后一朵花也开了,才能把 m 束花找出来

数据范围:花的数量为 10^5,需要把时间复杂度控制在 nlogN 或者 O( n ) 的范围内

开花时间为:10^9

m 的范围为 10^6:最终需要凑齐 10^6 束花

二分:具有二段性

二分后,看每一段盛开的长度,假设每一段盛开的长度为 len,这一段最多可以凑出  len / k 下取整  束花

扫描所有连续的、完全盛开的区间,统计最多可以凑出来多少束花,检验是否大于等于 m 即可

法二:

先将所有的花按照盛开的时间从小到大排序,按照时间从小到大枚举每一朵盛开的花,在区间中每次有一朵花盛开,盛开的过程当中,前面连续的区间中有 i 朵花盛开,后面连续的区间中有 j 朵花盛开,中间有一朵没有盛开的花作为间隔,当中间没有盛开的花盛开的时候,整段区间的花都盛开了

动态维护当前哪些区间的花都盛开了:假设某个区间的右端有一朵新的花盛开了,就把当前区间的右边延长,假设两个区间中间的一朵新的花盛开了,就把这两个区间合并

合并两个区间之前,需要把两个区间能够凑出来的花束的数量减去,再加上整个区间能够凑出来的花束的数量

用哈希表维护区间

维护每一个区间的左端点它对应的右端点是谁,维护区间的右端点它对应的的左端点是谁,开一个 L 哈希表和一个 R 哈希表

 L:以 L 为左端点的区间它的右端点是谁

R:以 R 为右端点的区间它的左端点是谁

每次有一朵新的花 x 盛开的时候,就可以快速判断这朵花 x 的左边是否有区间( x - 1 是否在 L 中出现过 ),右边是否有区间( x + 1 是否在 R 中出现过 )

动态修改 / 合并区间也是类似的( 如图 ) 时间复杂度 O( 1 ),最终算法时间复杂度为 O( n )

把 x 插入后,怎么把两个区间合并成一个区间:

找 x - 1 的左端点和 x + 1 的右端点,然后让x - 1 的左端点 的右边指向 x + 1 的右端点,让 x + 1 的右端点 的左边指向 x - 1 的左端点

双关键字排序:不仅要存下标,而且要存天数(在第几天盛开)

class Solution {
public:
    //花束
    int get(int l,int r,int k) {
        return (r - l + 1) / k;
    }
    int minDays(vector<int>& bloomDay, int m, int k) {
        //先将所有花排序
        vector<pair<int,int>> q;
        int n = bloomDay.size();
        //放入当前盛开的天数 盛开的时间从 1 开始
        for(int i = 0 ;i < bloomDay.size();i++ ) q.push_back({bloomDay[i],i + 1});
        //定义两个哈希表 下标从1开始 需要用到从 1 → n + 1 的位置
        vector<int> l(n + 2),r(n + 2);
        //从小到大排序
        sort(q.begin(),q.end());
        //按盛开的时间顺序 从小到大枚举所有的花 sum 用于记录能够凑出来的花束的数量
        int sum = 0;
        for(auto x : q) {
            //找出当前花的下标
            int i = x.second;
            //i的左右两边都有区间 需要把左右两边区间合并
            if(l[i - 1] && r[i + 1]) {
                //合并前需要更新sum 减去左边区间花束的数量、右边区间花束的数量 再加上左右两边花束的数量 
                sum = sum - get(l[i - 1],i - 1, k) - get(i + 1,r[i + 1], k) + get(l[i - 1],r[i + 1], k);
                //合并
                //左端点的右边指向右端点
                r[l[i - 1]] = r[i + 1];
                //右端点的左边指向左端点
                l[r[i + 1]] = l[i - 1];
            } //左边有区间 右边没有区间
              else if(l[i - 1]) {
                sum = sum - get(l[i - 1],i - 1, k ) + get(l[i - 1], i, k);
                //同理需要更新合并之后的左右端点
                //左边区间的右边等于i
                r[l[i - 1]] = i;
                l[i] = l[i - 1];
            } else if(r[i + 1]) {
                sum = sum - get(i + 1,r[i + 1], k) + get(i,r[i + 1], k);
                r[i] = r[i + 1];
                l[r[i + 1]] = i;
            } //当前点是一个单独的区间
              else {
                sum = sum + get(i,i,k);
                //当前区间中只有一个元素
                r[i] = l[i] = i;
            }
            //返回盛开的天数
            if(sum >= m) return x.first;
        }
        return -1;
        }
};

树节点的第 K 个祖先

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qiuqiuyaq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值