一、前言
sheepice的刷力扣篇章,虽然现在比较忙,但是如果有空,且写到一些比较有启发的力扣题的时候希望能够发一点点题解,这些题解的灵感来源于我自己看了很多神犇的题解,真的会很有收获。
sheepice的博客地址:大家感兴趣也可以去里面,说不定能学到一点东西哦!
二、力扣的某“简单题”
这个题目其实说真的,刚开始觉得真的挺难的,然后看到是个简单题我就知道数据的范围肯定给的非常的小,果不其然,数据范围只给了50,哈哈哈,所以第一次做这个题目的时候,这不就是简单的暴力枚举的题目吧,因为这题是三叶姐给到的“-+”题,然后看了看,好家伙,居然有可能上升为一个困难题(三叶姐关于本题的解答),awsl,于是乎我觉得今天去学习一下线段树的基本东西!!
三、一题多解
①暴力哈希
这里其实可以很容易想到,把所有题目中给到的range范围中的数,直接存储下来,然后呢直接进行一次 [ l e f t , r i g h t ] [left, right] [left,right]区间的一个遍历,看看区间内的元素是否满足在区间之内就好了!
class Solution {
public:
map<int, int> nums;
bool isCovered(vector<vector<int>>& ranges, int left, int right) {
for(auto range : ranges) {
for(auto i = range[0]; i <= range[1]; i++) {
nums[i]++;
}
}
for(int i = left; i <= right; i++) {
if(!nums.count(i)) return false;
}
return true;
}
};
暴力解法的时间复杂度为 O ( N ) O(N) O(N),由于这里开了一个哈希表,所以空间复杂度为 O ( N ) O(N) O(N)。当然这里的数据因为比较小嘛,直接开一个常数的52一个数组去记录数字是否存在,空间复杂度就变成 O ( C ) O(C) O(C)了哈哈。
②差分数组加前缀和
关于差分的思想和前缀和的计算,相信很多同学已经会了,在此不多加赘述,那么这一题的差分思想在哪呢?其实就是我们去计算一个一个区间所存在数字的时候.先设置一个差分数组 d i f f [ 52 ] diff[52] diff[52],假设我们要计算区间 [ 1 , 10 ] [1,10] [1,10]之内,保证这之间的数都出现过也就不为0,那么我们只需要让 d i f f [ 1 ] + 1 diff[1]+1 diff[1]+1然后让 d i f f [ 11 ] − 1 diff[11]-1 diff[11]−1,之后对区间做一次前缀和我们可以发现区间 [ 1 , 10 ] [1,10] [1,10]内的 d i f f diff diff数组就会全部变成1,代表数字出现在区间内,这样虽然并没有大大的优化时间复杂度。但是我们不难发现,如果当题目所给的区间有重复的时候,我们是可以通过这样的方法去计算出每一个数字被重复的次数,也就是在整个区间里面重复出现了多少次,这个可能也对日后碰到这样的题目提供了一个非常不错的思路!
class Solution {
public:
int diff[52];
bool isCovered(vector<vector<int>>& ranges, int left, int right) {
for(auto range : ranges) {
diff[range[0]]++;
diff[range[1] + 1]--;
}
for(int i = 1; i < 52; i++) {
diff[i] += diff[i - 1];
}
for(int i = left; i <= right; i++) {
if(diff[i] <= 0) return false;
}
return true;
}
};
时间复杂度为 O ( N ) O(N) O(N),空间复杂度就变成 O ( C ) O(C) O(C)。
③树状数组
本题采用线状数组其实无非就是会和第二种解法一样,只不过在树状数组的add操作里面,每次加入的是代表此元素出现的次数,最后利用差分的思想,可以直接得到某元素出现的次数,其实和方法二大同小异,但是希望自己再练一遍树状数组,所以呢,就还是写了一遍代码。如果对树状数组不了解的同学,sheepice也写了一篇博客,仅供uu们进行参考.树状数组初探!!
class Solution {
public:
int n = 55;
int sum[55];
int lowbit(int x) {
return x & (-x);
}
void add(int index, int value) {
for(int i = index; i <= n - 1; i += lowbit(i)) {
sum[i] += value;
}
}
int query(int index) {
int ans = 0;
for(int i = index; i > 0; i -= lowbit(i)) {
ans += sum[i];
}
return ans;
}
bool isCovered(vector<vector<int>>& ranges, int left, int right) {
for(auto range : ranges) {
for(auto i = range[0]; i <= range[1]; i++) {
add(i, 1);
}
}
for(int i = left; i <= right; i++) {
if(query(i) - query(i - 1) <= 0) return false;
}
return true;
}
};
时间复杂度为 O ( N ) O(N) O(N),空间复杂度变成 O ( C ) O(C) O(C)。
④线段树
线段树应该是解决所有树状数组能够解决的一些相关问题,同时也是解决绝大部分的区间求和和查询的一个最有利的手段,有关线段树的学习,大家可以参考此篇:线段树和树状数组。本篇下面的代码仅供参考,因为sheepicce今天才稍微懂一点线段树,预计这周会总结一下线段树的一些东西哦!
const int N = 55;
class Solution {
public:
struct Node {
int l, r, cnt;
};
Node tr [N * 4];
void pushup(int u) {
tr[u].cnt = tr[u << 1].cnt + tr[u << 1 | 1].cnt;
}
void build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r, tr[u].cnt = 0;
if(l != r)
{
int mid = (l + r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
// 从 tr 数组的下标 u 开始,在数值 x 的位置进行标记
void update(int u, int x) {
if (tr[u].l == x && tr[u].r == x) {
tr[u].cnt = 1;
} else {
int mid = (tr[u].l + tr[u].r) >> 1;
if (x <= mid) update(u << 1, x);
else update(u << 1 | 1, x);
pushup(u);
}
}
// 从 tr 数组的下标 u 开始,查询 [l,r] 范围内有多少个数值被标记
int query(int u, int l, int r) {
if (l <= tr[u].l && tr[u].r <= r) return tr[u].cnt;
int mid = (tr[u].l + tr[u].r) >> 1;
int ans = 0;
if (l <= mid) ans += query(u << 1, l, r);
if (r > mid) ans += query(u << 1 | 1, l, r);
return ans;
}
bool isCovered(vector <vector > & rs, int l, int r) {
build(1, 1, N);
for (auto & cur : rs) {
int a = cur[0], b = cur[1];
for (int i = a; i <= b; i++) {
update(1, i);
}
}
int tot = r - l + 1 , cnt = query(1, l, r);
return tot == cnt;
}
};
四、总结
总之,虽然是一个简单的题目,但是还是有很多可以拓展的地方,这也是我需要慢慢去学习的!加油啊!冲鸭!