【解法2】优化解:散列表

强化讲义 · 原始转写 · 原书检索 · 文字用于定位,公式与图表请回原页核验 · 本次导入快照

## 【解法2】优化解:散列表

(1)使用 count 数组保存A数组中每个数出现的次数,count[i]表示i出现的次数,循环统计完count 后计算出数组S,Si 表示小于等于i的元素有多少个。然后循环扫描 B 数组看 <= 5n -Bi]的数有多少个,加到sum中,循环完后返回sum。(2)伪代码如下:

int ans(int A[], int B[], int n)
{
    int pair_count = 0;
    int count[5*n+1], S[5*n+1]; // S[i]表示≤i的数有多少个
    for (int i = 0; i &lt;= 5*n; i++)
    count[i] = 0; // 初始化count数组
    for (int i = 0; i &lt; n; i++)
    if (A[i] &lt;= 5*n)
    count[A[i]]++;
    S[0] = count[0];
    for (int i = 1; i &lt;= 5*n; i++)
    S[i] = S[i-1] + count[i]; // 统计S数组
    for (int j = 0; j &lt; n; j++)
    if (B[j] &lt;= 5*n)
    pair_count = pair_count + S[5*n-B[j]];
    return pair_count;
}
(3)时间复杂度:O(n) 空间复杂度:O(n) ## \*习题2-13 (13 分) 已知数组 A 中有 n 个非负元素,每个元素 A[i]< 2n。请设计一个时间上尽可能高效的算法,求出数组 A 中第 k小的元素。例:A = {1,2, 1,2, 3, 3},n = 6,k = 3,按照升序得到{1,1,2,2,3,3},所以第k小的元素是2(第3、4大元素都是2),输出2。(1)给出算法的基本设计思想。(2)根据设计思想,采用C、C++或Java 语言描述算法,关键之处给出注释。(3)说明你所设计算法的时间复杂度和空间复杂度。 <table><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>做题思路</td><td rowspan=1 colspan=1>时间复杂度</td><td rowspan=1 colspan=1>空间复杂度</td><td rowspan=1 colspan=1>得分</td></tr><tr><td rowspan=1 colspan=1>暴力解</td><td rowspan=1 colspan=1>快速排序</td><td rowspan=1 colspan=1>O(nlogn)</td><td rowspan=1 colspan=1>O(logn)</td><td rowspan=1 colspan=1>11</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>散列表 Ko</td><td rowspan=1 colspan=1>(n)</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>13</td></tr></table> <!-- page 74 --> ## 【解法1】暴力解:快速排序 (1) 对数组快速排序,然后输出 A[k-1]。(2) 伪代码如下:
//快速排序
void Qsort(int A[], int L, int R)
{
    if (L &gt;= R) return; // 区间≤1,返回
    int i = L, j = R;
    int pivot = A[L]; //选首元素为基准
    // 不断交换左边比 pivot 大的数和右边比 pivot 小的数
    while (i &lt; j)
    {
        while (i &lt; j &amp;&amp; A[j] &gt;= pivot) j--; // 从右找第—个 &lt;pivot 的数
        while (i &lt; j &amp;&amp; A[i] &lt;= pivot) i++; // 从左找第—个 &gt;pivot 的数
        if (i &lt; j) swap(A[i], A[j]);
    }
    // 将基准值 A[L] 与 A[i] 交换
    swap(A[L], A[i]);
    //递归处理左右两个子区间
    Qsort(A, L, i - 1);
    Qsort(A, i + 1, R);
}
int ans(int A[], int n, int k)
{
    Qsort(A, 0, n-1);
    return A[k-1];
}
(3) 快排选择枢值时会加上优化不会退化成最坏情况,代码中省略优化部分。时间复杂度:1 O(nlogn)空间复杂度:O(logn) ## 【解法2】暴力解:散列表 (1) 用 count数组保存每个元素出现的次数,count[i]表示i出现的次数。统计完后再从0 开始扫描一遍 count 数组,k 减去 count[i],如果 k<=0了则输出 i。(2) 伪代码如下: <!-- verified-by-hand --> > [!success] 已结合原文第 74 页及 Markdown 上下文人工校对 > k 的递减和命中判断缺少循环花括号,i 在循环外不可见,函数末尾多花括号。
int ans(int A[], int n, int k)
{
    int count[2 * n];
    for (int i = 0; i &lt; 2 * n; i++) count[i] = 0;
    for (int i = 0; i &lt; n; i++) count[A[i]]++;
    for (int i = 0; i &lt; 2 * n; i++)
    {
        k -= count[i];
        if (k &lt;= 0) return i;
    }
    return -1;
}
<!-- page 75 --> (3)时间复杂度:O(n)空间复杂度:O(n) ## 习题2-14 (13 分)已知数组 A 中有n 个元素(可能重复),每个元素 2n ≤ A[i]< 3n。请设计一个尽可能高效的算法,从小到大输出 A 数组中的元素(重复元素只输出一次)。例:A= {13,17,16,14,13,13},n=6,出现的元素是13、17、16、14,所以从小到大依次输出:13、14、16、17。(1)给出算法的基本设计思想。(2)根据设计思想,采用C、C++或Java 语言描述算法,关键之处给出注释。 <table><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>做题思路</td><td rowspan=1 colspan=1>时间复杂度</td><td rowspan=1 colspan=1>空间复杂度</td><td rowspan=1 colspan=1>得分</td></tr><tr><td rowspan=1 colspan=1>暴力解</td><td rowspan=1 colspan=1>快速排序</td><td rowspan=1 colspan=1>O(nlogn)</td><td rowspan=1 colspan=1>O(logn)</td><td rowspan=1 colspan=1>11</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>散列表</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>13</td></tr></table> ## 【解法1】暴力解:快速排序 (1)对数组快速排序,然后从前往后遍历,只输出和前一个元素不一样的元素,这样就能够保证每个元素只输出一次。(2)伪代码如下: <!-- suspect-code --> > [!warning] 这段代码需要核对(原文第 75 页) > OCR 体检未通过:花括号不平衡。扫描件的两栏排版(代码 + 右侧注释)有时会被交错读取,产生看起来合理但语义错误的代码。背之前请对照原文。
//快速排序 void Qsort(int A[], int L, int R)
{
    if (L &gt;= R) return; //区间≤1,返回
    int i = L, j = R;
    int pivot = A[L]; //选首元素为基准
    // 不断交换左边比 pivot大的数和右边比 pivot 小的数
    while (i &lt; j)
    {
        while (i &lt; j &amp;&amp; A[j] &gt;= pivot) j--; // 从右找第—个 &lt;pivot 的数
        while (i &lt; j &amp;&amp; A[i] &lt;= pivot) i++; // 从左找第—个 &gt;pivot 的数
        if (i &lt; j) swap(A[i], A[j]);
        //将基准值 A[L]与 A[i]交换
        swap(A[L], A[i]);
        //递归处理左右两个子区间
        Qsort(A, L, i - 1);
        Qsort(A, i + 1, R);
    }
    void ans(int A[], int n)
    {
        Qsort(A, 0, n-1);
        for (int i = 0; i &lt; n; i++)
        if (i == 0 || A[i] != A[i-1]) // A[i]这个元素第一次出现则输出
        printf(&quot;%d &quot;, A[i]);
    }
<!-- page 76 --> (3)时间复杂度:O(nlogn)空间复杂度:O(logn) ## 【解法2】优化解:散列表 (1)count数组表示某个元素是否出现,count[x]表示x+2n出现的次数(0≤x<2n),遍历A数组统计每个元素出现的次数,即count[A[i]+2n]++。遍历完后依次扫描count数组判断有哪些元素出现过,从小到大输出这些元素。(2)伪代码如下:
void ans(int A[], int n)
{
    int count[n]_={0};
    for (int i = 0; i &lt; n; i++)
    count[A[i]-2*n]++; //统计 A 中每个元素出现次数
    for (int i = 0; i &lt; n; i++)
    if (count[i] &gt; 0)
    printf(&quot;%d&quot;, i+2*n); // 若 count[i]&gt;0 说明 i+2n 出现过
}
(3)时间复杂度:O(n)空间复杂度:O(n) ## 习题2-15 (15 分) 已知—个整数序列 A = (a0, a₁, . . . , an−1),其中 0 ≤ ai < n(0 ≤ i < n)。从数组中选择一些数,要求选择的数中任意两数差的绝对值<=k。设计一个在时间上尽可能高效的算法输出最多可能选择的元素个数。要求:(1)给出算法的基本设计思想。(2)根据设计思想,采用C、C++或Java语言描述算法,关键之处给出注释。(3)说明你所设计算法的时间复杂度和空间复杂度。 <table><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>做题思路</td><td rowspan=1 colspan=1>时间复杂度</td><td rowspan=1 colspan=1>空间复杂度IT</td><td rowspan=1 colspan=1>得分</td></tr><tr><td rowspan=1 colspan=1>暴力解</td><td rowspan=1 colspan=1>枚举</td><td rowspan=1 colspan=1>O(n2)</td><td rowspan=1 colspan=1>0(1)</td><td rowspan=1 colspan=1>11</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>快速排序</td><td rowspan=1 colspan=1>O(nlogn)</td><td rowspan=1 colspan=1>O(logn)</td><td rowspan=1 colspan=1>13</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>散列表</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>15</td></tr></table> 【解法1】暴力解:枚举,枚举最小元素 <!-- page 77 --> (1) 假设我们选择的数中最小的那个为A[i],那么我们可以选择的数范围就是A[i]\~A[i]+k,只要确定了A[i]再访问一遍数组就能够算出有多少个可选元素m,所以使用两层循环,外层i遍历数组,内层j再遍历一次整个数组判断可选元素个数,选择最多可能的可选元素,输出m。 (2) 伪代码如下:
int ans(int A[], int n, int k)
{
    int m; //m是本次遍历选择元素数
    int m_max = 0; // m_max初值为0
    for (int i = 0; i &lt; n; i++){ //A[i]为本次枚举最小元素
        m=0; // 每次枚举时都需要清空m
        for (int j = 0; j &lt; n; j++)
        if (A[j]&gt;=A[i] &amp;&amp; A[j]&lt;=A[i]+k)
        m++; // A[j]处于选择范围则m++
        m_max = max(m_max, m); // 如果m大则更新m_max
    }
    return m_max; // 输出最大m值
}
(3)时间复杂度:O(n²)空间复杂度:O(1) ## 【解法2】优化解:快速排序,然后数组指针后移 (1)先快速排序将A数组变成升序,设置数组指针i和j(初始为0),枚举i从0...n-1,j从上一轮i的j开始往后移动,如果A[j+1]<=A[i]+k说明A[j+1]仍然处于A[i]..A[i]+k这个范围,此时j后移,直到j移动到最后一个元素说明已经不会有更大的m=j-i+1。(2)伪代码如下: <!-- suspect-code --> > [!warning] 这段代码需要核对(原文第 77 页) > OCR 体检未通过:疑似两条语句被读进同一行;中文出现在代码行但缺注释符。扫描件的两栏排版(代码 + 右侧注释)有时会被交错读取,产生看起来合理但语义错误的代码。背之前请对照原文。
//快速排序
void Qsort(int A[], int L, int R)
{
    int i = L, j = R; if (L &gt;= R) return; //区间≤1,返回
    int pivot = A[L]; //选首元素为基准
    // 不断交换左边比 pivot 大的数和右边比 pivot 小的数
    while (i &lt; j)
    {
        while (i &lt; j &amp;&amp; A[j] &gt;= pivot) j--; // 从右找第—个 &lt;pivot 的数
        while (i &lt; j &amp;&amp; A[i] &lt;= pivot) i++; // 从左找第—个 &gt;pivot 的数
        王 if (i &lt; j) swap(A[i], A[j]);
        //将基准值A[L] 与A[i] 交换
        swap(A[L], A[i]);
        //递归处理左右两个子区间
        Qsort(A, L, i - 1);
        Qsort(A, i + 1, R);
    }
    int ans(int A[], int n, int k)
    {
        int m; //m是本次遍历选择元素数
        int m_max = 0; // m_max初值为0
        int i = 0, j = 0; //A[i~j]是选择的元素
        Qsort(A, 0, n-1); //快速排序
        for (i = 0; i &lt; n; i++){
            while (j+1&lt;n &amp;&amp; A[j+1]&lt;=A[i]+k)
            j++; // j+1也在范围内,j后移
            m = j-i+1; //求出m
            m_max = max(m_max, m); // 如果m大则更新m_max
            if (j == n-1) break; // j 已到末尾
        }
        return m_max; //输出最大m值
    }
}
<!-- page 78 --> ## (3)时间复杂度:O(nlogn)空间复杂度:O(logn) 快排的时间复杂度为O(nlogn),指针后移的时间复杂度为O(n),所以总时间复杂度是O(nlogn)。 ## 【解法3】优化解:散列表 (1)数组中每个元素大小都在0...n-1,可以使用数组B[n] 表示每个值出现的次数,扫描一次整个数组统计每个数出现的次数,然后R 从0扫描到n-1,维持一个队列,队头L,队尾R,保证这个队列长度不超过k,m 表示队列中元素出现次数的总和,此时可取数的范围就是[L...R],一个新元素 R 入队后(m += B[R]),如果队列长度超过 k 了则将 L 出队(m -=B[L]),统计最大的m 即最大队列中元素出现的次数和。(2)伪代码如下:
int ans(int A[], int n, int k)
{
    int m = 0;
    int m_max = 0; // m_max 初值为 0
    int B[maxn];
    memset(B, 0, sizeof(B));
    for (int i = 0; i &lt; n; i++) B[A[i]]++; // A[i]出现次数+1
    int L = 0, R = 0;
    for (R = 0; R &lt; n; R++)
    {
        // m 加上 B[R]
        if (R-L &gt; k)
        { // 如果范围差大于 k 则 L 右移
            m -= B[L]; // [L+1,R],减 B[L]
            L++;
        }
        m_max = max(m_max, m); // 如果 m 大则更新 m_max
    }
    return m_max;
} 彩
## (3)时间复杂度:O(n) 空间复杂度:O(n) <!-- page 79 --> ## 习题2-16 (15分)在数组中,某个数字减去它右边的数字得到一个数对之差。求所有数对之差的最大值。例如,在数组 {2, 4, 1,16, 7, 5, 11, 9} 中,数对之差的最大值是 11,是 16 减去 5 的结果。设计一个在时间上尽可能高效的算法输出所有数对之差的最大值。(1)给出算法的基本设计思想。(2)根据设计思想,采用C 或C++语言描述算法,关键之处给出注释。(3)说明你所设计算法的时间和空间复杂度。 <table><tr><td rowspan=1 colspan=1></td><td rowspan=1 colspan=1>做题思路</td><td rowspan=1 colspan=1>时间复杂度</td><td rowspan=1 colspan=1>空间复杂度</td><td rowspan=1 colspan=1>得分</td></tr><tr><td rowspan=1 colspan=1>暴力解</td><td rowspan=1 colspan=1>枚举</td><td rowspan=1 colspan=1>O(n2)</td><td rowspan=1 colspan=1>0(1)</td><td rowspan=1 colspan=1>13</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>预处理研</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>O(1)</td><td rowspan=1 colspan=1>15</td></tr></table>