已知一个由像素点组成的单色屏幕,每行均有 w 个像素点,所有像素点初始为 0,左上角位置为 (0,0)。
现将每行的像素点按照「每 32 个像素点」为一组存放在一个 int 中,再依次存入长度为 length 的一维数组中。
我们将在屏幕上绘制一条从点 (x1,y) 到点 (x2,y) 的直线(即像素点修改为 1),请返回绘制过后的数组。
注意:
示例1:
示例2:
提示:
我是第一次做这种题型的,我看到后第一反应就是头皮发麻,这里面的变量有点小多,但再仔细看看好像自己有点思路。然后一系列硬着头皮去做,终于写出来了。下面我就慢慢介绍,我的思路是怎样的。还有就是,这道题需要用到位运算符与bitset数据结构的知识,不懂的同学可以去看看我写的这两篇文章:
我自己写的这种方法有点笨,里面充满了各种条件判断。但胜在逻辑好懂,小白都能看懂。下面我来说一下我的解题思路。
首先由题意可知,每一行有 w 个字符,w 可以被32整除,也就是意味着 w 最小为32,不要被这个下面的提示吓到了。测试案例里没有w小于32的情况。
那么就可知一行会有 w / 32
个int ,这个就是我们等会要去遍历一行中int的个数。
那么由它给的length,我们就知道,这个像素矩阵一共有 length /(w /32)
行,这个就是我们要遍历的行的个数。
它们分别组成了两层的for循环,为的就是我们要找到对应行 y
,然后去将x1
与x2
之间的像素点全部置为1
将到这里,就该讲讲如何去将x1 与 x2 之间的像素点置为1了。
我们要分情况去讨论,一共有这么几种情况:
我们要去处理这6种情况下,我们该如何往vector中去添加int。所以可想而知,我的代码有多复杂,其中要判断的边界条件有多少。废话就不多说了,我代码中的注释很详细的,剩下的就看代码吧。
代码如下:
class Solution {
public:
vector<int> drawLine(int length, int w, int x1, int x2, int y) {
int size = w / 32 ; //一行像素点中,有多少个int。
vector<int> result ;
int size_y = length / size; // 这个像素矩阵一共有多少行
for(int i = 0; i < size_y; ++i){ //遍历每一行
if(i == y){ //找到y所在的那一行
bool flag1 = false; //设置标志位,如果在某一行找到过x1了,将x1置为true;
bool flag2 = false;//设置标志位,如果在某一行找到过x2了,将x2置为true;
for(int j = 0; j < size; ++j){ //遍历每一行中的int
bitset<32> num(0);
if(32*(j + 1) - x1 <= 0 && !flag1){ //在这个int中找不到x1,那肯定找不到x2,添加一个int,跳出本次循环
result.push_back(0);
continue;
}
if(32*(j + 1) - x1 > 0 && !flag1){ //第一次找到了x1,之后要去看看能不能找到x2
flag1 = true;//找到了x1,先将对应flag置为true
num.set(32*(j + 1) - x1 - 1); //先将x1这个点置为1。
if(32*(j+1) - x2 <= 0){ //找不到x2,从x1这个点往右侧,所有位置为1。添加int然后跳过本次循环。
for(int k = 32*(j+1) - x1 - 1; k >=0; --k){
num.set(k);
}
result.push_back((int)num.to_ulong());
continue;
}
if(32*(j+1) - x2 > 0){ //找到了x2,那就将x1到x2之间的点都置为1。添加int,然后跳过本次循环
flag2 = true;
for(int k = 32*(j+1) - x2 -1; k <= 32*(j + 1) - x1 - 1; ++k ){
num.set(k);
}
result.push_back((int)num.to_ulong());
continue;
}
}
if(32*(j + 1) - x1 > 0 && flag1 && !flag2){//我已经找到过x1,但还没有找到x2
if(32*(j + 1) - x2 <= 0){ //说明x2不在这个int中
result.push_back(-1);
continue;
}
}
if(32*(j + 1) - x2 > 0 && !flag2 && 32*(j + 1) - x2 < 32){ //我第一次找到了x2,我要把x2这个位向左所有位都置于1
num.set(32*(j + 1) - x2 - 1);
for(int k = 32*(j + 1) - x2 - 1 ; k < 32; ++k){
num.set(k);
}
result.push_back((int)num.to_ulong());
continue;
}
result.push_back(0);//我找到过x1,x2,但还要填0;
continue;
}
}
if(i != y){
for(int i = 0; i < size; ++i){
result.push_back(0);
}
}
}
return result;
}
};
首先,我们注意到,在计算变量 size_y 时,使用了除法运算。因此,时间复杂度为 O ( 1 ) O(1) O(1)。
然后,我们考虑主循环,即遍历每一行像素点的循环。在最坏情况下,我们需要遍历所有行,因此时间复杂度为 O ( l e n g t h ) O(length) O(length)。在每行像素点的循环中,我们需要遍历每个 32 位的整数,因此时间复杂度为 O ( w / 32 ) O(w/32) O(w/32)。在这个循环中,我们使用了 bitset,对于每个 32 位的整数,需要使用常数时间。因此,对于每个像素点,时间复杂度为 O ( 1 ) O(1) O(1)。
综上所述,总时间复杂度为 O ( l e n g t h ⋅ w / 32 ) O(length \cdot w/32) O(length⋅w/32)。空间复杂度方面,我们使用了一个大小为 w / 32 w/32 w/32 的 vector,因此空间复杂度为 O ( w / 32 ) O(w/32) O(w/32)。
需要注意的是,这个分析是在最坏情况下的情况下,实际的时间复杂度可能会更低,取决于输入参数的具体值。
我看了某大神的解法,就感觉到大神思考问题思考的太透彻了,我还有很多需要学习的地方,向大神学习。好了废话不多说,来讲讲大神的思路是什么样子的:
count
去记录一行中有多少个int。x1
为起始位置去遍历,直到x1<=x2
为止res[y * count + i /32]
去找到x1
所在的那个int,然后用去找到对应的二进制位 31 - i % 32
,最后用位运算符将它置为1就好1 << 31 - i % 32
class Solution {
public:
vector<int> drawLine(int length, int w, int x1, int x2, int y) {
int count = w / 32; //一行中有多少int
vector<int> res(length, 0);//先将所有的int都提前存进vector中,反正如果不是y那一行添加的int也都是0
for(int i = x1; i <= x2 && i < length * 32; i++)//处理y那一行的每一个int
//res[y * x + i /32] 这是为了找到x1和x2的那个int值在哪,之后方便用位运算符去修改其中的数据。
// 1 << (31 - (i % 32)) 找到了对应的二进制位,将他改为1就好。
res[y * conut + i / 32] |= (1 << 31 - i % 32);
return res;
}
};
使用了一个一维 vector 来存储所有的像素点,而不是使用一个二维数组或 bitset。这可以将空间复杂度降低为 O ( w / 32 ) O(w/32) O(w/32)。
其次,只遍历了直线所在的那一行,而不是遍历所有像素点。这可以将时间复杂度降低为 O ( x ) O(x) O(x),其中 x x x 表示直线所在的一行中像素点的数量。
综上所述,代码的时间复杂度为 O ( x ) O(x) O(x),空间复杂度为 O ( w / 32 ) O(w/32) O(w/32)
这道题是一道很考验细节处理的题。并且也考验了对题目要点总结的能力。做完了这道题我感觉到收益良多。