【解法2】优化解:散列表
← 内容总览 07 / 24 下一节 →
强化讲义 · 原始转写 · 原书检索 · 文字用于定位,公式与图表请回原页核验 · 本次导入快照
## 【解法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 -- ; // 从右找第—个 <pivot 的数
while ( i & lt ; j & amp ; & amp ; A [ i ] & lt ; = pivot ) i ++ ; // 从左找第—个 >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 -- ; // 从右找第—个 <pivot 的数
while ( i & lt ; j & amp ; & amp ; A [ i ] & lt ; = pivot ) i ++ ; // 从左找第—个 >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]>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 -- ; // 从右找第—个 <pivot 的数
while ( i & lt ; j & amp ; & amp ; A [ i ] & lt ; = pivot ) i ++ ; // 从左找第—个 >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>
← 内容总览 07 / 24 下一节 →