📜  门| GATE-IT-2004 |问题16(1)

📅  最后修改于: 2023-12-03 15:28:46.105000             🧑  作者: Mango

门 GATE-IT-2004 - 问题16

这是 GATE-IT-2004 考试中的一道计算题,涉及到程序设计中的概念和流程控制。

题目描述

下面是问题的原文:

给定由 $n$ 个元素组成的数组 $A$,其中 $A_i \in {0,1}$。正整数 $k(n \geq k> 0)$ 去一个扫描窗口在 $A$ 上,窗口从左到右依次扫描,窗口宽度为 $k$,右端点为 $r$ ,左端点为 $l = r-k+1$。窗口的权重定义如下:$W(l) = \sum_{i=l}^{r}A_i*2^{i-l}$。你需要编写一个程序,对于给定的数组和正整数 $k$,输出所有满足 $W(l)$ 是 4 的倍数的窗口的左端点 $l$。

例如,对于输入数组 $A=[1,0,1,1,0,1,0,1,1]$ 和 $k=3$,我们可以得到如下的扫描窗口:

[1,0,1] 1 0 1 0 1 1
  1 [0,1,1] 0 1 0 1
    1 0 [1,1,0] 1 0 
      0 1 1 [0,1,1]
        1 0 1 1 [0,1]
          0 1 0 1 [1,1]

其中括号中的数字表示这个扫描窗口中的子数组,而其它数字则表示这个子数组中的每一个数,这些数在扫描窗口中依次排列。例如,在第一个扫描窗口中,$l=1$,$r=3$,而对应的子数组是 [1,0,1]。

对于每一个扫描窗口,我们都可以计算它的权重 $W(l)$,例如,对于第一个扫描窗口,$W(l)=12^0 + 02^1 + 1*2^2 = 5$。

问题要求我们找到所有满足 $W(l)$ 是 4 的倍数的窗口的左端点 $l$。在本例中,满足条件的左端点是 $l=4$ 和 $l=7$,因为对应的滑动窗口的权重分别为 $W(4)=42^0+12^1+12^2=12$ 和 $W(7)=12^0 + 12^1 + 12^2 + 1*2^3 = 14$,它们都是 4 的倍数。注意,这里仅仅要求输出所有满足条件的左端点,而不要求输出对应的右端点 $r$。

算法

首先,我们需要明确一下题目的要求:对于一个固定的 $k$ 值,找到所有滑动窗口 $A[l,r], r=l+k-1$,使得 $W(l)=\sum_{i=l}^{r}A_i*2^{i-l}$ 是 4 的倍数,即 $W(l) \bmod 4 = 0$。为此,我们需要有一个算法,在 $O(n)$ 的时间内遍历所有可能的滑动窗口,并且对每一个窗口,都快速计算出它的权重。那么,怎样快速计算滑动窗口的权重呢?

首先,让我们来看一个稍微简单一些的问题:假设我们有一个 $n$ 位的二进制数 $x$,我们如何计算它的十进制值呢?如果我们从低到高考虑,那么 $x$ 的第 $i$ 位表示的数值为 $x_i 2^i$,所以 $x$ 的十进制值就是:

$$ V(x) = \sum_{i=0}^{n-1} x_i 2^i $$

可以看到,这样的计算需要执行 $O(n)$ 个乘法操作和 $O(n)$ 个加法操作,时间复杂度为 $O(n)$。这个计算过程实际上就是把 $x$ 看作是一个二进制多项式,然后把它转换成一个十进制整数。所以,我们的问题可以转化为:对于一个固定的 $k$ 值,怎样快速计算出由 $k$ 个二进制数构成的串的权重?

我们也可以采用类似的算法思路,考虑构造一个由 $k$ 个二进制数构成的多项式 $P(x)$,然后把它转换成一个十进制整数,这个整数就是这个由二进制数构成的串的权重。具体来说,我们设多项式为:

$$ P(x) = \sum_{i=0}^{k-1} A_{l+i} x^i $$

那么,根据权重的定义,我们可以得到:

$$ W(l) = V(P(2)) $$

其中 $V(x)$ 表示将多项式 $x$ 转换成十进制整数。因此,我们可以先计算出多项式 $P(x)$,然后再把它转换成十进制数。而对于计算多项式的方法,我们可以直接遍历所有二进制数 $A_{l+i}$,并且在每一步中计算出这个二进制数对应的 $x^i$ 的系数。具体来说,设第 $i$ 位二进制数为 $A_{l+i}$,那么 $P(x)$ 的系数 $a_i$ 就是:

$$ a_i = A_{l+i} 2^i $$

只需要累加所有这样的系数,就得到了 $P(x)$ 的系数。

这里有一个小问题:由于我们是按照从低位到高位的顺序遍历二进制数的,所以计算出的 $P(x)$ 实际上是 $A_{l+k-1} x^{k-1} + \cdots + A_{l+1} x + A_l$,而不是 $A_l x^{k-1} + \cdots + A_{l+k-2} x + A_{l+k-1}$。显然,它们是等价的,因为二进制数所代表的数值是不依赖于它的位数的排列顺序的。不过,由于题目要求的是 $W(l)$,因此我们必须让 $P(x)$ 的系数按照 $A_l, A_{l+1}, \cdots, A_{l+k-1}$ 的顺序排列。为了做到这点,我们可以对 $a_i$ 进行如下变换:

$$ \begin{aligned} a_i & = & A_{l+i} 2^i \ & = & A_{l+i} 2^{k-1} \cdot \dfrac{x}{2^k} \cdot 2^{i-k+1} \ & = & A_{l+i} \cdot x^i \cdot \dfrac{2^{k-i}}{2^k} \ & = & A_{l+i} \cdot x^i \cdot 2^{i-k} \end{aligned} $$

通过这个变换,我们可以用 $O(k)$ 的时间复杂度计算出多项式 $P(x)$ 了。而把多项式 $P(x)$ 转换成十进制数,只需要按照定义把系数相加就可以了,这个过程的时间复杂度也是 $O(k)$。

最后,我们可以编写一个伪代码,表示算法的整个过程。

首先,我们需要定义一个函数 calc_weight,它的参数是数组 $A$、滑动窗口的左端点 $l$、窗口宽度 $k$,返回值是窗口的权重。

def calc_weight(A, l, k):
    P = 0
    for i in range(k):
        P += (A[l+i] << i)
    return P % 4 == 0

然后,我们需要遍历所有可能的滑动窗口,并且对每一个窗口,都调用一次 calc_weight 函数。如果返回值为 True,那么说明这个窗口的权重是 4 的倍数,因此输出它的左端点。

def find_windows(A, k):
    n = len(A)
    for l in range(n-k+1):
        if calc_weight(A, l, k):
            print(l)

这就是整个算法的实现过程。

复杂度

对于给定的 $k$ 值,算法的时间复杂度是 $O(nk)$,其中 $n$ 是数组 $A$ 的长度。因为 $k$ 是一个定值,所以算法可以认为是线性时间复杂度的。当然,由于计算多项式系数的过程中使用了位运算,因此实际上常数比较大,常常需要优化才能通过大数据测试。

总结

这是一道比较简单的计算题,主要考察了对于位运算和多项式的理解和运用。具体来说,需要掌握如何计算二进制数的十进制值,以及如何用多项式表示由二进制数构成的串,并且计算它的十进制权重。此外,在算法设计方面,需要掌握如何利用滑动窗口遍历所有子串并进行判断。