【解法2】暴力解:快速排序,然后扫描一遍数组
强化讲义 · 原始转写 · 原书检索 · 文字用于定位,公式与图表请回原页核验 · 本次导入快照
## 【解法2】暴力解:快速排序,然后扫描一遍数组
(1)先对数组A快速排序得到升序序列,然后遍历数组找到第一个未出现的最小正整数。
(2) 伪代码如下:
<!-- suspect-code -->
> [!warning] 这段代码需要核对(原文第 94 页)
> OCR 体检未通过:花括号不平衡。扫描件的两栏排版(代码 + 右侧注释)有时会被交错读取,产生看起来合理但语义错误的代码。背之前请对照原文。
void Qsort(int A[], int L, int R)
{
if (L>=R)
return; // 区间≤1,退出
int pivot, i=L, j=R; // i,j 为左右指针
pivot=A[L]; // pivot 为基准
while (i<j){
while (i<j && A[j]>=pivot)
while (i<j && A[i]<=pivot)
i++;
if(i<j)
swap(A[i], A[j]); // A[j]<pivot, A[i]>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<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};
for (int i=0; i<n; i++) // 扫描数组A
if (A[i]>0 && A[i]<=n+1) // 当0<A[i]<=n+1时才修改
count[A[i]]++;
for (int j=1; j<=n+1; j++) // 扫描count数组
if (count[j]==0) //j未出现过,是最小未出现正整数
return j; //输出j
}
<!-- page 95 -->
(3)时间复杂度:O(n)空间复杂度:O(n)
## 2020年41题
(13 分) 定义三元组(a, b, c)(a,b,c 均为正数)的距离 D = |a − b| + |b − cl + |c- a|。给定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>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(n *log(m *l))*</td><td rowspan=1 colspan=1>0(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)伪代码如下: K
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<n; i++)
for (int j=0; j<m; j++)
for (int k=0; k<l; 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(n3)空间复杂度:O(1)
<!-- page 96 -->
【解法2】优化解:枚举+多指针法。两重循环扫描ab,而c数组指针后移1)数组A、B、C的长度分别为n、m、I,两重循环枚举a、b,对数组C设置变量k存储数组下标不断后移,初始k=0,只要移动后的D更小,就选择往后移动,然后对于每一组a、b、c求出对应的距离D,并从中选出最小的距离。2)伪代码如下: L
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)
{
// D_min初值为最大整数
int k=0;
for (int i=0; i<n; i++)
for (int j=0; j<n; j++)
{ //两重循环枚举a、b
D = DD(A[i], B[j], C[k]); //枚举a、b得到的初始D
D = DD(A[i],B[j],C[++k]); // 更新下标k和距离D
D_min = min(D_min, D); //更新最小D值
}
} return D_min; // 输出最小D值
## (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、1,一重循环枚举a=A[i],通过两次折半查找可以查找到插入位点j和k保证B[i]< A[i]< B[j+1],C[k] < A[i] < C[k+1](如果插入位点是最左端和最右端另外判断),然后判断<A[i], B[j], 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)伪代码如下: EL
<!-- suspect-code -->
> [!warning] 这段代码需要核对(原文第 96 页)
> OCR 体检未通过:中文出现在代码行但缺注释符。扫描件的两栏排版(代码 + 右侧注释)有时会被交错读取,产生看起来合理但语义错误的代码。背之前请对照原文。
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 D, D_min=INT_MAX; √ D_min初值为最大整数
for (int i=0; i<n; i++)
{
int j=B_Search(B, 0, m-1, A[il); // 在B中根据A[i]折半查找
int k=B_Search(C, 0, l-1, A[i]); // 在C中根据A[i]折半查找
if (j>=0 && k>=0)
D_min=min(D_min, DD(A[i], B[j], C[k]));
if (j+1<m && k>=0)
D_min=min(D_min, DD(A[i], B[j+1], C[k]));
if (j>=0 && k+1<l)
if (j+1<m && k+1<l)
D_min=min(D_min, DD(A[i], B[j+1],C[k+1]));
}
return D_min; // 输出最小D值
<!-- page 97 -->
(3)时间复杂度:O(n \*(logm + logl))或O(nlogn)空间复杂度:O(1)
## 【解法4】优化解:多指针法
(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<n && j<m && k<l)
{
D_min=min(D_min, DD(A[i],B[j], C[k])); // 更新D_min
if (A[i]<B[j] && A[i]<C[k])
i++;
else
if (B[j]<C[k])
j++;
else
k++;
}
return D_min; // 输出最小D值
}
(3) 时间复杂度:O(m + n + l)或O(n) 空间复杂度:O(1)
## 2025年41题
<!-- page 98 -->
(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(n2)</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>O(1)</td><td rowspan=1 colspan=1>13</td></tr></table>
【解法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 < n; i++)
{ // 遍历每个起始位置i
int maxMul = A[i] * A[i]; //初始化
for(int j = i; j < n; j++)
{ // 遍历所有j >= i
int currentMul = A[i] * A[j];
if (currentMul > maxMul) //当前乘积更大,更新
maxMul = currentMul;
}
res[i] = maxMul; // 将最大乘积存入res[i]
} 有机考研
输出数组res
}
3)时间复杂度:O(n²),因为使用了两个嵌套的for循环。空间复杂度:O(1),使用了常数级别的额外空间。 X
## 【解法2】预处理,使用辅助数组B和C(12分左右)1)算法的基本思想观察可知:
如果 A[i]是正数,想要乘积最大,自然希望乘以区间内最大的数;
如果A[i] 是负数,想要乘积最大,需要乘以区间内最小的数(即最小的负数,两个负数相乘为正,值越大)。因此,对于每个位置i,提前预处理出:
• 从位置 i 到末尾的最大值 B[i] ;
<!-- page 99 -->
从位置 i 到末尾的最小值 C[i]。最终对于每个 i,只需要在A[i] \* B[i] 和A[i]\*C[i] 之间取较大值作为res[i],即:res[i]=max(A[i]×B[i],A[i]×C[i])2) C代码实现
// 使用辅助数组B和C的实现
void CalMulMax(int A[], int res[], int n)
{
int B[n];// B[i]表示A[i..n-1]中的最大值
int C[n];// C[i]表示A[i..n-1]中的最小值
//从后向前遍历,填充B和C数组
B[n-1] = A[n-1];
C[n-1] = A[n-1];
for(int i = n-2; i >= 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 < n; i++)
res[i] = max(A[i]*B[i], A[i]*C[i]);
输出数组res
}
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 >= 0; i--)
{
B = max(B, A[i]);
C = min(C, A[i]);
res[i] = max(A[i]*B, A[i]*C);
}
输出数组res
}
3)时间复杂度:O(n)。空间复杂度:O(1)。
<!-- page 100 -->
## 3 链表
由 2009 年真题引入
【2009年真题,15分】已知一个带有表头结点的单链表,结点结构为
<table><tr><td>data</td><td>link</td></tr><tr><td></td><td></td></tr></table>
假设该链表只给出了头指针list。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)。若查找成功,算法输出该结点的 data域的值,并返回1;否则,只返回0。要求:1)描述算法的基本设计思想。2)描述算法的详细实现步骤。3)根据设计思想和实现步骤,采用程序设计语言描述算法(使用C、C++或Java 语言实现),关键之处请给出简要注释。 当计
## 3.1 链表概述
## 3.1.1 特点
我们要在学习了顺序表的基础上再来学习链表,链表和顺序表比较:
共同点:都是线性表。这意味着顺序表的很多方法在链表中依然可以使用,如:散列表、
不同点:
链表不可以随机访问。即很多需要用到随机访问的方法都不能使用,如快速排序(双链表可以使用,但不推荐)、折半查找。
单链表只能单向从头到尾依次访问。即访问顺序只能是从头到尾,那我们需要掌握很多对应的技巧,如:快慢指针、头插法等。
做题推荐流程:暴力解法->判断能否优化->优化解。对链表有一个技巧:如果算法的时间复杂度是O(nlogn)或以上,则可以优化,否则就不需要。 计
关于这种技巧,请大家学习完本章再试着回答为什么可以这样判断。