【解法1】暴力解:枚举,枚举数对中的两个元素

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

## 【解法1】暴力解:枚举,枚举数对中的两个元素

(1)使用两层循环,外层i遍历数组,内层j再从i+1开始遍历一次整个数组(默认i <j),A[i]是数对中的前一个元素,A[j]即是数对中的后一个元素,求出m=A[i]和A[i]之差的绝对值,输出最大的m。(2)伪代码如下:

int ans(int A[], int n)
{
    int m; // m 是数对之差的绝对值
    int m_max = INT_MIN; //m_max初值为最小 int
    for (int i = 0; i &lt; n-1; i++) // 枚举前一个元素
    for (int j = i+1; j &lt; n; j++)
    {
        m = A[i] - A[j]; // A[i] 和 A[j] 之差
        m_max = max(m_max, m); // 如果 m 大则更新 m_max
    }
    return m_max; // 输出最大 m 值
}
(3)时间复杂度:O(n²)空间复杂度:O(1) ## 【解法2】优化解:预处理,保存i之前的最大元素 (1)j遍历一遍数组,t保存A[0\~j-1]的最大元素,相当于枚举j作为数对后一个元素,而t作为数对第一个元素,数对差为 m = t-A[j],保存最大数对差 m\_max。(2) 伪代码如下:
int ans(int A[], int n)
{
    int m_max = INT_MIN;
    int m;
    // 预处理,数组B保存最大值
    int B[n];
    B[0] = A[0];
    for (int i = 1; i &lt; n; i++) //预处理
    B[i] = max(A[i], B[i-1]);
    for (int i = 1; i &lt; n; i++)
    m_max = max(m_max, B[i-1]-A[i]); // 更新 m max
    return m_max; // 输出最大m值
}
<!-- page 80 --> 进一步优化可以发现只需要维护一个最大值变量,无需额外数组,空间复杂度可以降到O(1)
int ans(int A[], int n)
{ int m_max = INT_MIN; // m_max 初值为最小 int
    int m;
    int t = A[0]; // t 来保存A[i] 之前的最大值,初值为A[0]
    for(int j =1;j &lt; n; j++) // 后—个元素下标从 1 开始
    {
        m = t - A[j];
        m_max = max(m_max, m); // 如果 m 大则更新 m_max
        t=max(t, A[j]); // 如果 A[j] 大则更新 t
    }
    return m_max; // 输出最大 m 值
}
(3)时间复杂度:O(n)空间复杂度:O(1)(这里保存状态只用了一个变量,所以空间复杂度仍然是O(1)。) ## 习题2-17 (15 分)已知一个整数升序序列 A=(a0, a1,... , an-1)。设计一个尽可能高效的算法返回整数x 出现的次数。函数原型为:int countOccurrences(int A[], int n, int x)。(1)给出算法的基本设计思想。(2)根据设计思想,采用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(n)</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(logn)</td><td rowspan=1 colspan=1>0(1)</td><td rowspan=1 colspan=1>15</td></tr></table> 【解法1】暴力解:枚举,枚举所有元素,判断是否等于x <!-- page 81 --> (1)从头到尾完整地遍历一次数组A。在遍历的过程中,设置一个计数器,每当遇到一个值等于目标整数x的元素时,就将计数器加一。遍历结束后,计数器的值就是x在数组中出现的总次数。(2)C语言算法描述
int countOccurrences(int A[], int n, int x)
{
    int count = 0; // 初始化计数器
    //枚举,遍历整个数组A
    for (int i = 0; i &lt; n; i++)
    {
        // 如果当前元素等于x,计数器加一
        if (A[i] == x) count++;
    }
    return count;// 返回最终计数值
}
(3) 时间复杂度:O(n) 空间复杂度:O(1) ## 【解法2】优化解:折半查找, (1) 如果数组 A 是有序的,我们可以利用二分查找来快速定位元素。这个方法的思想是: 1. 定位左边界:首先,折半查找第一个大于或等于x的元素下标,记为left。这确定了 x首次出现的位置。 2. 有效性判断:检查 A[left]是否等于x。如果不等,说明数组中根本不存在x,直接返回0。 道 3. 定位右边界:接着,再次折半查找第一个大于或等于 x+1 的元素下标,记为 right。这个下标需要进行一次调整来精确定位最后一个x的位置: 如果 A[right] 的值大于 x (即查找成功),那么最后一个 x 实际在 right 的前一个位置,因此我们将 right 减一。 烤 如果 A[right] 的值等于 x (即你查找失败,这只在x 是数组末尾元素时发生),那么right 本身就已经是最后一个x的位置,无需变动。 4. 计算次数:此时,left 和 right 已分别指向第一个和最后一个 x 的下标。因此,x 出现的总次数就是 right - left + 1。 (2)
//折半查找模板二:查找第一个大于或等于 x的元素的下标
int B_Search(int A[], int L, int R, int x)
{
    int mid;
    while (L &lt; R)
    {
        mid = (L + R) / 2;
        if (A[mid] &gt;= x) R = mid;
        else L = mid + 1;
    }
    return L;
}
<!-- page 82 --> <!-- verified-by-hand --> > [!success] 已结合原文第 82 页及 Markdown 上下文人工校对 > 函数缺少闭合花括号,且直接访问 A[left]/A[right] 未检查折半查找返回的边界。改用两个边界二分避免越界。
int countOccurrences(int A[], int n, int x)
{
    int l = 0, r = n;
    while (l &lt; r) { int m = l + (r - l) / 2; if (A[m] &lt; x) l = m + 1; else r = m; }
    int left = l;
    l = 0; r = n;
    while (l &lt; r) { int m = l + (r - l) / 2; if (A[m] &lt;= x) l = m + 1; else r = m; }
    return l - left;
}
(3) 时间复杂度:O(logn)空间复杂度:O(1) <!-- page 83 --> ## 2.6.2 真题 ## 2010年42题 (13 分)设将n(n>1)个整数存放到一维数组R 中。试设计一个在时间和空间两方面都尽可能高效的算法。将 R 中保存的序列循环左移p(0 < p < n)个位置,即将 R 中的数据由(X0, X1, . . . , Xn−1)变换为(Xp, Xp+1,. . . , Xn−1, X0, X1, . . . , Xp−1)。要求: (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(n)</td><td rowspan=1 colspan=1>O(n)</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(1)</td><td rowspan=1 colspan=1>13</td></tr></table> ## 【解法1】暴力解:枚举,另设一个数组,枚举每个元素再移动 (1) 原序列保存在数组A中,设另一个数组B,将A[0], A[1]……,A[p-1]放到B[n-p], B[n-p+1]...,B[n-1]中,将A[p], A[p+1],…,A[n-1]放到B[0],B[1]...,B[n-p-1]中,数组B即是所求。(2)伪代码如下:
void ans(int A[], int n, int p)
{
    int B[n];
    for (int i=0; i&lt;p; i++)
    B[i+n-p]=A[i];
    B[i-p]=A[i];
    for (int i=0; i&lt;n; i++)
    printf(&quot;%d &quot;, B[i]); //输出元素重新放置后的数组
}
(3) 时间复杂度:O(n)空间复杂度:O(n) ## 【解法2】特殊解:技巧,数组翻转 (1) 原序列保存在数组A中,将数组 A 的前 p 个元素 A[0] ~ A[p-1] 翻转,再将后面的 A[p] \~A[n-1] 翻转,最后整体翻转 A[0]~A[n-1]。(2)伪代码如下:
void Reverse(int A[], int L, int R)
{
    int num=(R-L+1)/2;
    for (int i=0; i&lt;num; i++)
    swap(A[L+i], A[R-i]);
}
void ans(int A[], int n, int p)
{
    Reverse(A, 0, p-1); // 翻转A[0~p-1]
    Reverse(A, p, n-1); // 翻转A[p~n-1]
    Reverse(A, 0, n-1); // 翻转A[0~n-11
    for (int i=0; i&lt;n; i++)
    printf(&quot;%d &quot;, A[i]); // 输出翻转后的数组
}
<!-- page 84 --> (3) 时间复杂度:O(n) 空间复杂度:O(1) 三次翻转顺序很关键:先左段、再右段、最后整体。 ## 2011年42题 (15 分)一个长度为L(L≥1)的升序序列S,处在第「L/21个位置的数称为S的中位数。例如,若序列S1 = (11,13,15,17,19),则S1 的中位数是 15,两个序列的中位数是含它们所有元素的升序序列的中位数。例如,若 S2 = (2, 4,6,8,20),则 S1 和 S2 的中位数是 11。现在有两个等长升序序列A和B,试设计一个在时间和空间两方面都尽可能高效的算法,找出两个序列A和B的中位数。要求:(1)给出算法的基本设计思想。(2)根据设计思想,采用C、C++或Java语言描述算法,关键之处给出注释。(3)说明你所设计算法的时间复杂度和空间复杂度。 道V <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(n)</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></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>14</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>折半查找</td><td rowspan=1 colspan=1>O(logn)</td><td rowspan=1 colspan=1>O(1)</td><td rowspan=1 colspan=1>15</td></tr></table> ## 【解法1】暴力解:都放入新数组中快速排序
void Qsort(int A[], int L, int R)
{
    if (L&gt;=R)
    return; //区间≤1,退出
    int pivot, i=L, j=R; // i, j 为左右指针
    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 B[], int n)
{
    int C[2*n];
    for (int i=0;i&lt;n; i++)
    {
        C[i]=A[i];
        C[n+i]=B[i];
        Qsort(C, 0, 2*n-1); // 快速排序处理C[0]~C[2n-1]
        return C[n-1]; // 中位数输出C[n-1]
    }
}
<!-- page 85 --> ## (3)时间复杂度:O(nlogn)空间复杂度:O(n) 这种解法完全不利用A、B原本已升序的特性,因此效率较低。 ## 【解法2】优化解:多指针法,归并两个数组 (1)采用归并的方式合并数组A和B到新的数组C中,给A、B数组设置变量i和j保存数组下标,初始时都为0,每次比较A[i]和Bi],数组C的下一个空位保存小的那个数,并且对应指针(2) 伪代码如下:
int ans(int A[], int B[], int n)
{
    int C[2*n];
    int i=0,j=0,k=0;
    while (i&lt;n &amp;&amp; j&lt;n)
    if (A[i]&lt;B[j])
    C[k++]=A[i++];
    else
    C[k++]=B[j++];
    while (i&lt;n) C[k++]=A[i++];
    while (j&lt;n) C[k++]=B[j++];
    return C[n-1]; // 输出C[n-1]
}
<!-- page 86 --> (3)时间复杂度:O(n) 空间复杂度:O(n) 【解法3】优化解:数组指针后移1)给A、B数组设置变量i和j保存数组下标,计数k表示此时是第几小的元素,初始时都为0,每次比较A和Bi],取较小的那个数对应指针后移且k++,直到k等于n,输出这个数。2)伪代码如下:
int ans(int A[], int B[], int n)
{
    int i=0, j=0, k;
    for (k=1; k&lt;n; k++) // 只需要移动n-1次,最后一次取当前较小值即为中位数。
    if (A[i]&lt;B[j]) i++;
    else j++;
    return min(A[i],B[j]);// 输出A[i]和B[j]中的小值
}
(3) 时间复杂度:O(n) 空间复杂度:O(1) 归并两个有序数组时,中位数一定是整个合并序列第n小的元素,因此只需要知道该位置的元素,无需完整合并后的数组。 人 ## 【解法4】优化解:多有序数组折半查找 (1)对A、B数组进行折半查找,设L1、R1为数组A的左右查找边界,L2、R2为B的左右查找边界。数组A[L1至R1]和B[L2\~R2]的中位数,设为a和b,求序列A、B的中位数过程如下:①若a < b则舍弃序列A中较小的一半A[L1...mid-1],同时舍弃序列B中较大的一半B[mid+1...R1]。②若a>=b则舍弃序列A中较大的一半A[mid+1...R1],同时舍弃序列B中较小的一半B[L1...mid-1]。重复过程①、②,且需要保证数组A、B中的元素个数相同,如果不同则从长的数组中舍弃一个(因为我们取mid时都是向下取整,所以这里舍弃最小的那个值),直到两个序列中均只含一个元素时为止,较小者即为所求的中位数。本质上不断缩小搜索区间,每次抛弃较小半区,直至剩下的最后一个候选区间。(2)伪代码如下:
void ans(int A[], int B[], int n)
{
    int mid1, mid2, L1=0, L2=0, R1=n-1, R2=n-1;
    while (L1&lt;R1) //如果L1&gt;=R1则退出循环
    {
        mid1=(L1+R1)/2;
        mid2=(L2+R2)/2; //取中间数,向下取整
        if (A[mid1]&lt;B[mid2]){
            L1=mid1;
            R2=mid2; // 更新查找范围
            if (R1-L1!=R2-L2)
            L1++;
        }
        else{
            R1=mid1;
            L2=mid2; // 更新查找范围
            if (R1-L1!=R2-L2)
            L2++; //保证R1-L1=R2-L2
        }
    }
    printf(&quot;%d&quot;, min(A[L1], B[L2])); // 输出A[i]和B[j]中的小值
}
<!-- page 87 --> (3)时间复杂度:O(logn)空间复杂度:O(1) 注意:本题的O(logn)解法思想深刻,但代码实现细节非常多,在考场上一次写对的难度很高。对于大部分同学,熟练掌握O(n)的解法(如解法3:多指针法)是更稳妥的策略可以确保拿到大部分分数。此O(logn)解法建议学有余力的同学深入理解。 炫价