一维数组的动态和
给出一个数组,计算这个数组的动态和(前缀和)
数组长度为 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 个祖先