目录
一,循环数组、实现模板
循环数组是一种简单的数据结构,通过id的mod实现循环。
template<typename T>
class CircleArray { //只关心最后push的n个数
public:
CircleArray(int n) {
v.resize(n);
id = 0, s = 0;
flag = false;
}
void push(T x) {
if (flag)s += x - v[id];
v[id] = x;
id = (id + 1) % v.size();
if (!id)flag = true;
}
int getNum() {
if (flag)return v.size();
return id;
}
T getSum() {
return s;
}
private:
vector<T>v;
int id;
bool flag;
T s;
};
二,滑动窗口总和
滑动窗口总和问题的辅助空间分析:
(1)如果直接给出数组,可以用朴素遍历法,所需辅助空间为O(1)。
(2)如果以数据流的方式给出,要么用可增长的数组依次把所有输入数据存下来,所需辅助空间为O(n),要么使用循环数组,所需辅助空间为O(k),k是窗口大小。
力扣 346. 数据流中的移动平均值
给定一个整数数据流和一个窗口大小,根据该滑动窗口的大小,计算其所有整数的移动平均值。
实现 MovingAverage 类:
MovingAverage(int size) 用窗口大小 size 初始化对象。
double next(int val) 计算并返回数据流中最后 size 个值的移动平均值。
示例:
输入:
["MovingAverage", "next", "next", "next", "next"]
[[3], [1], [10], [3], [5]]
输出:
[null, 1.0, 5.5, 4.66667, 6.0]
解释:
MovingAverage movingAverage = new MovingAverage(3);
movingAverage.next(1); // 返回 1.0 = 1 / 1
movingAverage.next(10); // 返回 5.5 = (1 + 10) / 2
movingAverage.next(3); // 返回 4.66667 = (1 + 10 + 3) / 3
movingAverage.next(5); // 返回 6.0 = (10 + 3 + 5) / 3
提示:
1 <= size <= 1000
-105 <= val <= 105
最多调用 next 方法 104 次
class MovingAverage {
public:
MovingAverage(int size) {
while (size--)v.push_back(0);
loc = 0, flag = 0;;
}
double next(int val) {
v[loc++] = val;
if (loc == v.size())flag = 1, loc = 0;
double ans = 0;
for (auto &vi : v)ans += vi;
return ans / (flag == 1 ? v.size() : loc);
}
vector<int>v;
int loc;
int flag;
};
力扣 362. 敲击计数器
设计一个敲击计数器,使它可以统计在过去 5 分钟内被敲击次数。(即过去 300 秒)
您的系统应该接受一个时间戳参数 timestamp (单位为 秒 ),并且您可以假定对系统的调用是按时间顺序进行的(即 timestamp 是单调递增的)。几次撞击可能同时发生。
实现 HitCounter 类:
HitCounter() 初始化命中计数器系统。
void hit(int timestamp) 记录在 timestamp ( 单位为秒 )发生的一次命中。在同一个 timestamp 中可能会出现几个点击。
int getHits(int timestamp) 返回 timestamp 在过去 5 分钟内(即过去 300 秒)的命中次数。
示例 1:
输入:
["HitCounter", "hit", "hit", "hit", "getHits", "hit", "getHits", "getHits"]
[[], [1], [2], [3], [4], [300], [300], [301]]
输出:
[null, null, null, null, 3, null, 4, 3]
解释:
HitCounter counter = new HitCounter();
counter.hit(1);// 在时刻 1 敲击一次。
counter.hit(2);// 在时刻 2 敲击一次。
counter.hit(3);// 在时刻 3 敲击一次。
counter.getHits(4);// 在时刻 4 统计过去 5 分钟内的敲击次数, 函数返回 3 。
counter.hit(300);// 在时刻 300 敲击一次。
counter.getHits(300); // 在时刻 300 统计过去 5 分钟内的敲击次数,函数返回 4 。
counter.getHits(301); // 在时刻 301 统计过去 5 分钟内的敲击次数,函数返回 3 。
提示:
1 <= timestamp <= 2 * 109
所有对系统的调用都是按时间顺序进行的(即 timestamp 是单调递增的)
hit and getHits 最多被调用 300 次
进阶: 如果每秒的敲击次数是一个很大的数字,你的计数器可以应对吗?
class HitCounter {
public:
HitCounter() {
int size = 300;
while (size--)v.push_back(0);
loc = 0;
}
void hit(int timestamp) {
if (timestamp != loc) {
if (timestamp - loc >= 300)for (int i = 0; i < v.size(); i++)v[i] = 0;
else for (int i = loc + 1; i <= timestamp; i++)v[i % 300] = 0;
}
v[timestamp % 300]++;
loc = timestamp;
}
int getHits(int timestamp) {
int ans = 0;
for (int i = timestamp - 299; i <= timestamp && i<=loc; i++) {
ans += v[(i + 300) % 300];
}
return ans;
}
vector<int>v;
int loc;
};
三,滑动窗口最值
和滑动窗口总和问题差不多,如果用整个数组或动态增长的数组来建立线段树,则辅助空间是O(n),如果用循环数组来建立线段树,则所需辅助空间为O(k),k是窗口大小。
四,DP中的解空间平移
1,一维DP
假设递推式是a[i] = f(a[i-1],a[i-2],a[i-3],a[i-4]),其中f是一个确定的函数。
如果用动态增长的数组来存所有的a,则则辅助空间是O(n),如果用循环数组,则所需辅助空间为O(k),这里k=4
PS:高维DP中的某一个独立维度具有这样的性质,也是可以这样做的,那样可能就把O(nm)降到O(km)
2,二维DP
假设递推式是形如
或者
且我们只关系最终i取最大值时的a值,不关心i取中间数值的情况,
则可以通过循环数组进行映射,即解空间平移,映射后的递推式:
这样就可以利用空间压缩,省掉i的维度,从而对于k!=0的情况下就不需要复制数据。
实例:
五,else
力扣 1652. 拆炸弹
你有一个炸弹需要拆除,时间紧迫!你的情报员会给你一个长度为 n
的 循环 数组 code
以及一个密钥 k
。
为了获得正确的密码,你需要替换掉每一个数字。所有数字会 同时 被替换。
- 如果
k > 0
,将第i
个数字用 接下来k
个数字之和替换。 - 如果
k < 0
,将第i
个数字用 之前k
个数字之和替换。 - 如果
k == 0
,将第i
个数字用0
替换。
由于 code
是循环的, code[n-1]
下一个元素是 code[0]
,且 code[0]
前一个元素是 code[n-1]
。
给你 循环 数组 code
和整数密钥 k
,请你返回解密后的结果来拆除炸弹!
示例 1:
输入:code = [5,7,1,4], k = 3 输出:[12,10,16,13] 解释:每个数字都被接下来 3 个数字之和替换。解密后的密码为 [7+1+4, 1+4+5, 4+5+7, 5+7+1]。注意到数组是循环连接的。
示例 2:
输入:code = [1,2,3,4], k = 0 输出:[0,0,0,0] 解释:当 k 为 0 时,所有数字都被 0 替换。
示例 3:
输入:code = [2,4,9,3], k = -2 输出:[12,5,6,13] 解释:解密后的密码为 [3+9, 2+3, 4+2, 9+4] 。注意到数组是循环连接的。如果 k 是负数,那么和为 之前 的数字。
提示:
n == code.length
1 <= n <= 100
1 <= code[i] <= 100
-(n - 1) <= k <= n - 1
class Solution {
public:
vector<int> decrypt(vector<int>& code, int k) {
vector<int>v;
int s = 0;
for (auto x : code)s += x, v.push_back(s);
for (auto x : code)s += x, v.push_back(s);
for (int i = 0; i < code.size(); i++) {
if (k == 0)code[i] = 0;
else if (k > 0)code[i] = v[i + k] - v[i];
else code[i] = v[code.size() + i - 1] - v[code.size() + i - 1 + k];
}
return code;
}
};