【解法1】暴力解:枚举最小正整数

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

## 【解法1】暴力解:枚举最小正整数

(1)可以证明最小未出现的正整数t一定≤n+1(因为如果t>n+1,则1\~t-1都会出现一次,但只有n个整数,所以t-1≤n,即t≤n+1),枚举所有可能的未出现最小正整数,扫描一次数组判断是否符合。

(2) 伪代码如下:

int ans(int A[], int n)
{
    for (int i=1; i<=n+1; i++)
    {
        bool flag=false;
        for (int j=0; j<n; j++)
        if (i==A[j])
        flag=true;
        if (flag==false)
        return i;
    }
}
// 先假设i未出现
//此时i出现了,f1ag变成true
//若i未出现过则输出i
(3) // 时间复杂度:O(n²) 空间复杂度:O(1)
## 【解法2】暴力解:快速排序,然后扫描一遍数组 (1)先对数组A快速排序得到升序序列,然后遍历数组找到第一个未出现的最小正整数。 (2) 伪代码如下:
void Qsort(int A[], int L, int R)
{N
    if (L>=R)
    return;
    int pivot, i=L, j=R;
}
// 当前区间元素个数<=1则退出
// i和j是左右两个数组下标移动
<!-- page 66 -->
pivot=A[L]; // pivot作为基准值参与比较
while (i&lt;j){
    while (i&lt;j &amp;&amp; A[j]&gt;=pivot)
    j--;
    while (i&lt;j &amp;&amp; A[i]&lt;=pivot)
    i++;
    if (i&lt;j)
    swap(A[i], A[j]); // A[j]&lt;pivot, A[i]&gt;pivot
}
swap(A[L], A[i]); //将基准值放入他的最终位置
Qsort(A, L, i-1); //递归处理左区间
Qsort(A, i+1, R); // 递归处理右区间
int ans(int A[], int n)
{
    Qsort(A, 0, n-1);
    int t=1; //假设初始最小未出现正整数t=1
    for (int i=0; i&lt;n; i++)
    if (A[i]==t)
    t++; // t已出现,此时最小未出现正整数是t+1
    return t; //输出最小未出现正整数
}
(3) // 时间复杂度:O(nlogn) 空间复杂度:O(logn)
## 【解法3】优化解:散列表 (1)可以证明最小未出现的正整数t一定≤n+1(因为如果t>n+1,则1\~t-1都会出现一次,但只有n个整数,所以t-1≤n,即t≤n+1),设置一个数组count,count[i]表示i是否在原数组A中出现过,扫描一遍数组A,更新count数组,之后再扫描一遍count数组输出未出现的最小正整数。 (2) 伪代码如下:
int ans(int A[],int n)
{
    int count[n+2]={0}; // 0~n+1
    for (int i=0; i&lt;n; i++) // 扫描数组A
    if (A[i]&gt;0 &amp;&amp; A[i]&lt;=n+1) // 当0&lt;A[i]&lt;=n+1时才修改
    count[A[i]]++;
    for (int j=1; j&lt;=n+1; j++) //扫描count数组
    if (count[j]==0) // j未出现过,是最小未出现正整数
    return j; //输出j
}
(3) // 时间复杂度:O(n) 空间复杂度:O(n)
<!-- page 67 --> (13 分)定义三元组(a,b,c)(a,b,c 均为正数)的距离 D = |a - b| + |b - cl + |c - al。给定 3 个非空整数集合 S1、S2和S3,按升序分别存储在3 个数组中。请设计一个尽可能高效的算法,计算并输出所有可能的三元组(a,b,c)(a∈S1,b∈S2,c∈S3) 中的最小距离。例如S1 = {-1,0, 9},S2= {-25, -10, 10,11},S3 ={2,9, 17, 30,41},则最小距离为2,相应的三元组为(9,10,9)。要求: (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(nml)</td><td rowspan=1 colspan=1>0(1)</td><td rowspan=1 colspan=1>10</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>枚举+多指针法</td><td rowspan=1 colspan=1>O(mn)</td><td rowspan=1 colspan=1>O(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(n * log(m * l))</td><td rowspan=1 colspan=1>O(1)</td><td rowspan=1 colspan=1>12</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>多指针法</td><td rowspan=1 colspan=1>O(m + n + l)</td><td rowspan=1 colspan=1>O(1)</td><td rowspan=1 colspan=1>13</td></tr></table> 【解法1】暴力解:枚举,三重循环将abc都扫描一次 1)数组A、B、C的长度分别为n、m、I,三重循环枚举a、b、c,对于每一组a、b、c求出对应的距离D,并从中选出最小的距离。 2)伪代码如下:
int ans(int A[],int n,int B[],int m,int C[],int l)
{
    int D_min=INT_MAX, D; // D_min初值为最大整数
    for (int i=0; i&lt;n; i++)
    for (int j=0; j&lt;m; j++)
    for (int k=0; k&lt;1; k++){ // 三重循环枚举abc
        D=abs(A[i]-B[j])+abs(B[j]-C[k])+abs(C[k]-A[i]);
        D_min=min(D_min, D); // 更新最小D值
    }
    return D_min; //输出最小D值
}
(3) 时间复杂度: O(nml)或O(n³) 空间复杂度:O(1) 【解法2】优化解:枚举+多指针法。两重循环扫描ab,而c数组指针后移 1)数组A、B、C的长度分别为n、m、I,两重循环枚举a、b,对数组C设置变量k存储数组下标不断后移,初始k=0,只要移动后的D更小,就选择往后移动,然后对于每一组a、b、c求出对应的距离D,并从中选出最小的距离。
int DD(int a,int b,int c)
{ // DD(a,b,c)=|a-b|+|b-c|+|b-a|
    return abs(a-b)+abs(b-c)+abs(c-a);
}
int ans(int A[],int n,int B[],int m,int C[],int l)
{
    int D, D_min=INT_MAX; // D_min初值为最大整数
    int k=0;
    for (int i=0; i&lt;n; i++)
    for (int j=0; j&lt;n; j++)
    { //两重循环枚举a、b
        D = DD(A[i], B[j], C[k]); //枚举a、b得到的初始D
        while (k &lt; l - 1 &amp;&amp; DD(A[i], B[j], C[k+1])&lt;D)
        D = DD(A[i], B[j], C[++k]); // 更新下标k和距离D
        D_min = min(D_min, D); // 更新最小D值
    }
    return D_min; // 输出最小D值
}
<!-- page 68 -->
(3) // 时间复杂度: O(mn) 或O(n²) 空间复杂度:O(1)
【解法3】优化解:枚举+折半查找。一重循环扫描a,两轮折半查找b、c (1)本解法可以证明当a确定时,b只能是数组B中<a且最接近a的那个数或>=a且最接近a的那个数,c也只能是数组C中<a且最接近a的那个数或>=a且最接近a的那个数,所以只需要枚举a并折半查找确定b和c可能的值然后选择最小的D。数组A、B、C的长度分别为n、m、I,一重循环枚举a=A[i],通过两次折半查找可以查找到插入位点j和k保证B[j]<A[i]<B[j+1],C[k]<A[i]<C[k+1](如果插入位点是最左端和最右端另外判断),然后判断<A[i],B[i],C[k]>、<A[i],B[j+1].C[k]>、<A[i],B[i],C[k+1]>、<A[i],B[j+1],C[k+1]>哪种情况D最小即可,就能得到a确定的情况下最小可能的D,枚举a更新Dmin即可。最后输出最小值D min。 (2) 伪代码如下: <!-- verified-by-hand --> > [!success] 已结合原文第 68 页及 Markdown 上下文人工校对 > DD 函数缺少闭合花括号,导致 ans 被非法嵌套;数组 C 的长度 l 在 1-1、k+1<1 中被 OCR 成数字 1,并混入裸字‘道’。用契约明确的 lowerBound 返回首个 >=x 位置,再检查前驱和该位置,可完整覆盖四种候选。
int lowerBound(int A[], int n, int x)
{
    int L = 0, R = n;
    while (L &lt; R)
    {
        int mid = L + (R - L) / 2;
        if (A[mid] &lt; x) L = mid + 1;
        else R = mid;
    }
    return L;
}

int DD(int a, int b, int c)
{
    return abs(a - b) + abs(b - c) + abs(c - a);
}

int ans(int A[], int n, int B[], int m, int C[], int l)
{
    int D_min = INT_MAX;
    for (int i = 0; i &lt; n; i++)
    {
        int jb = lowerBound(B, m, A[i]);
        int kc = lowerBound(C, l, A[i]);
        for (int db = -1; db &lt;= 0; db++)
            for (int dc = -1; dc &lt;= 0; dc++)
            {
                int j = jb + db, k = kc + dc;
                if (j &gt;= 0 &amp;&amp; j &lt; m &amp;&amp; k &gt;= 0 &amp;&amp; k &lt; l)
                    D_min = min(D_min, DD(A[i], B[j], C[k]));
            }
    }
    return D_min;
}
(3) // 时间复杂度: O(n *(logm + logl))或O(nlogn) 空间复杂度:O(1)
<!-- page 69 --> (1)数组A、B、C的长度分别为n、m、I,对数组A、B、C分别设置变量i、j、k存储数组下标不断后移,初始i=j=k=0,可以证明如果下标是往后移动,只有当移动A[i]、B[i]、C[k]最小的那个时,D=2L3才有可能减小,所以每次都只后移所指元素值最小的对应数组下标变量,一直重复进行,并更新D的最小值Dmin,直到所指元素值最小的对应数组无法在后移,最后输出最小值D\_min。 (2) 伪代码如下:
int DD(int a,int b,int c) // DD(a,b,c)=|a-b|+|b-c|+|b-a|
{
    return abs(a-b)+abs(b-c)+abs(c-a);
}
int ans(int A[],int n,int B[],int m,int c[],int l)
{
    int D_min=INT_MAX; // D_min初值为最大整数
    int i=0, j=0, k=0; //数组A、B、C的下标i、j、k
    while(i&lt;n &amp;&amp; j&lt;m &amp;&amp; k&lt;1)
    {
        D_min=min(D_min,DD(A[i],B[j],C[k])); // 更新D_min
        if (A[i]&lt;B[j] &amp;&amp; A[i]&lt;C[k])
        i++;
        else
        if (B[j]&lt;C[k])
        j++;
        else
        k++;
    }
    return D_min; // 输出最小D值
}
(3) // 时间复杂度: O(m + n + l)或O(n) 空间复杂度:O(1)
## 2025年 41题 (13 分)有两个长度均为 n的一维整型数组A[n]、res[n],计算 A[i] 与 A[i](0≤i≤j≤n-1)乘积的最大值,并将其保存到res[i] 中。若AΠ={1, 4, -9, 6},则得到 res[]={6,24,81,36}。现给定数组A,请设计时间空间上尽可能高效的算法CalMulMax,求 res 中各元素的值。函数原型为:void CalMulMax(int A[], int res[]. int n)。 (1)给出算法的基本思想。(4分) (2) 用C/C++ 描述算法关键之处给出注释。(7 分) (3)说明时间、空间复杂度。(2 分) <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(n²)</td><td rowspan=1 colspan=1>0(1)</td><td rowspan=1 colspan=1>10</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>12</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>0(1)</td><td rowspan=1 colspan=1>13</td></tr></table> <!-- page 70 --> 【解法1】暴力解(双重循环枚举,10分左右 1)算法的基本思想 使用两个嵌套的for循环遍历数组A的所有可能的(i, j)组合,其中0 ≤i ≤j ≤ n-1。对于每个i,遍历所有j≥i,计算A[i]\*A[j],并记录其中的最大值到res[i]中。 2)C代码实现
// 暴力破解法实现
void CalMulMax(int A[], int res[], int n)
{
    for(int i = 0; i &lt; n; i++)
    { //遍历每个起始位置
        o m
        int maxMu] = A[i] * A[i]; //初始化
        for(int j = i; j &lt; n; j++)
        { // 遍历所有j &gt;= i
            int currentMu] = A[i] * A[j];
            if (currentMul &gt; maxMul) //当前乘积更大,更新
            maxMul = currentMul;
            res[i] = maxMul; //将最大乘积存入res[i]
        }
        输出数组res
    }
}
3)时间复杂度:O(n²),因为使用了两个嵌套的for循环。 空间复杂度:O(1),使用了常数级别的额外空间。 【解法2】预处理,使用辅助数组B和C(12分左右) 1)算法的基本思想观察可知: 如果 A[i] 是正数,想要乘积最大,自然希望乘以区间内最大的数; • 如果 A[i] 是负数,想要乘积最大,需要乘以区间内最小的数(即最小的负数,两个负数相乘为正,值越大)。 因此,对于每个位置 i,提前预处理出: • 从位置 i 到末尾的最大值 B[i]; • 从位置 i 到末尾的最小值 Ci。
//使用辅助数组B和C的实现
void CalMulMax(int A[], int res[], int n)
{
    int B[n];
    int c[n];
    // 从后向前遍历,填充B和C数组
    B[n-1] = A[n-1];
    C[n-1] = A[n-1];
    for(int i = n-2; i &gt;= 0; i--)
    {
        B[i] = max(B[i+1], A[i]);
        c[i] = min(c[i+1], A[i]);
    }
    //计算res数组,最大值只可能是A[i]*B[i]或A[i]*C[i]
    for(int i = 0; i &lt; n; i++)
    res[i] = max(A[i]*B[i], A[i]*C[i]);
    输出数组res
}
<!-- page 71 --> 3)时间复杂度:O(n),因为预处理B和C数组各需一次线性遍历,计算res数组也只需一次线性遍历。 空间复杂度:O(n),需要额外的两个辅助数组B和C,每个大小为n。 【解法3】预处理,使用辅助变量B和C(13分左右) 1)算法的基本思想 只需要在解法二的基础上,用变量存最小值和最大值即可。 2)C代码实现
//使用辅助变量B和C的实现
void CalMulMax(int A[], int res[], int n)
{
    int B; // B表示A[i..n-1]中的最大值
    int C; // C表示A[i..n-1]中的最小值
    B = A[n-1];
    C = A[n-1];
    res[n-1] = A[n-1]*A[n-1];
    //计算res数组,最大值只可能是A[i]*B[i]或A[i]*C[i]
    for(int i = n-2; i &gt;= 0; i--)
    {
        B = max(B, A[i]);
        C = min(C, A[i]);
        res[i] = max(A[i]*B, A[i]*C);
    }
    输出数组res
}
<!-- code-audit: false_positive --> > [!info] 已人工审读:这是题解复杂度文字,不是程序语句;解法的 O(n) 时间、O(1) 额外空间结论与紧邻代码一致。 ```c 3)时间复杂度:O(n)。 空间复杂度:O(1)。 ```