循环数组

目录

一,循环数组、实现模板

二,滑动窗口总和

力扣 346. 数据流中的移动平均值

力扣 362. 敲击计数器

三,滑动窗口最值

四,DP中的解空间平移

1,一维DP

2,二维DP

五,else

力扣 1652. 拆炸弹


一,循环数组、实现模板

循环数组是一种简单的数据结构,通过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是窗口大小。

参考剑指 Offer 59 - I. 滑动窗口的最大值

四,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

假设递推式是形如

a[i][j]=\left\{\begin{matrix} ...,k=0\\ a[i-1][j-1],k\neq 0 \end{matrix}\right.  或者 a[i][j]=\left\{\begin{matrix} ...,k=0\\ a[i-1][j+1],k\neq 0 \end{matrix}\right.

且我们只关系最终i取最大值时的a值,不关心i取中间数值的情况,

则可以通过循环数组进行映射,即解空间平移,映射后的递推式:

b[i][j]=\left\{\begin{matrix} ...,k=0\\ b[i-1][j],k\neq 0 \end{matrix}\right.

这样就可以利用空间压缩,省掉i的维度,从而对于k!=0的情况下就不需要复制数据。

实例:

力扣 1223. 掷骰子模拟

五,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;
    }
};

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值