【解法3】优化解:散列表
← 内容总览 05 / 24 下一节 →
原始讲义 · 非增强校订版 · 原书检索 · 文字用于定位,公式与图表请回原页核验 · 本次导入快照
## 【解法3】优化解:散列表
(1)设置count数组表示某个数出现的次数,循环遍历数组A并更新count数组,访问A[i]时如果发现count[A[i]]已经>0说明之前A[i]已经出现过,输出A[i]。如果最终都没有发现有重复元素,则输出-1。
(2) 伪代码如下:
int ans ( int A [], int n )
{
int count [ n ] = { 0 };
for ( int i = 0 ; i & lt ; n ; i ++ )
{
if ( count [ A [ i ]] & gt ; 0 ) return A [ i ];
count [ A [ i ]] ++ ;
} return -1 ; . c
}
(3) 时间复杂度:O(n) 空间复杂度:O(n)
## 习题2-4
(15 分)已知整数数组A含有n 个不相等的整数(n为奇数,且数组A 中每个元素的绝对值≤n)。设计一个时间上尽可能高效的算法,求数组A的中位数。例:n=5,A={-3,0,4,-2,3},则中位数为0。
(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>0(1)考研m</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><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(logn)</td><td rowspan=1 colspan=1>15</td></tr></table>
## 【解法1】暴力解:枚举法
(1)对每个元素 A[i],计算比它小的元素个数count,如果恰好为 n/2,则为中位数。
(2) 伪代码如下:
<!-- page 32 -->
int ans ( int A [], int n )
{
for ( int i = 0 ; i & lt ; n ; i ++ )
{
int count = 0 ;
for ( int j = 0 ; j & lt ; n ; j ++ )
if ( A [ j ] & lt ; A [ i ]) count ++ ;
if ( count == n / 2 ) return A [ i ];
return -1 ;
}
}
( 3 ) // 时间复杂度:O(n²) 空间复杂度:O(1)
## 【解法2】暴力解:快速排序
(2) 伪代码如下:
(1)将数组A快速排序,输出排序后数组A最中间的数A[n/2]即是中位数。
//快速排序函数
void Qsort ( int A [], int L , int R )
{
if ( L & gt ; = R ) return ; //区间内元素个数为0或 1,自然有序,递归
终止
int i = L , j = R ;
int pivot = A [ L ]; // 选取区间第一个元素作为基准
// 不断交换左边比 pivot 大的数和右边比 pivot 小的数
while ( i & lt ; j )
o m
{
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 )
{
Qsort ( A , 0 , n -1 );
return A [ n / 2 ];
}
}
( 3 ) // 时间复杂度:O(nlogn) 空间复杂度:O(logn)
<!-- page 33 -->
## 【解法3】优化解:散列表
(1) 设置散列表 count[2n+1],散列函数 f(x) = x - n,其中 count[i] 表示 i-n 这个数出现的次数,循环遍历数组 A 并更新 count 数组。之后i 从 0 遍历到 2n,每次将 count[i]累加,当累加变量>= n / 2 时输出 i - n 并退出程序。
(2) 伪代码如下:
int ans ( int A [], int n )
{
int count [ 2 * n + 1 ] = { 0 }; //散列表记录x-n出现的次数
for ( int i = 0 ; i & lt ; n ; i ++ )
count [ A [ i ] + n ] ++ ;
int sum = 0 ; //累计到目前为止已经是第几小的数了
for ( int i = 0 ; i & lt ; 2 * n + 1 ; i ++ )
{
sum = sum + count [ i ];
if ( sum & gt ; n / 2 )
return i - n ;
}
}
( 3 ) // 时间复杂度:O(n) 空间复杂度:O(n)
## 【解法4】优化解:多指针法
(1)对无序数组使用多指针法,类似快速排序,但是在递归调用区间[L,i-1]和[i+1,R]时,因为中位数只可能出现在下标包含n/2的区间中,所以增加一个判断,只调用其中一个区间。
(2) 伪代码如下:
void Qsort_Select ( int A [], int L , int R )
{
if ( L & gt ; = R ) return ; //区间内元素个数为 0 或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 ]);
if ( L & lt ; = n / 2 & amp ; & amp ; i -1 & gt ; = n / 2 )
Quick_Select ( A , L , i -1 , n ); // 左区间包括中位数
else
Quick_Select ( A , i + 1 , R , n ); // 右区间包括中位数
}
<!-- page 34 -->
int ans ( int A [], int n )
{
Quick_Select ( A , 0 , n -1 , n );
return A [ n / 2 ];
}
( 3 ) // 时间复杂度:O(n) 空间复杂度:O(logn)
## 习题2-5
(15分)已知数组A含有n个整数,现在要将小于k的元素放在数组右侧,大于等于k的元素放在左侧,设计一个时间上尽可能高效的算法实现这个功能。
例:n = 7,A = {4, 5, 9, 3, 7, 10,1},k = 6,则处理之后的数组可以为 {10, 7,9,3,5, 4, 1},其中 10、7、9 比 6 大,在数组左侧;而3、5、4、1小于等于6,在数组右侧。
(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>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>
【解法1】暴力解:快速排序
(1)将数组A快速排序得到升序数组,然后将数组A翻转并输出即可得到降序数组,满足小于k的元素在右侧,大于k的元素在左侧。 K
(2) 伪代码如下:
//快速排序函数
void Qsort ( int A [], int L , int R )
{
if ( L & gt ; = R ) return ;
终止
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 );
for ( int i & lt ; 0 ; i & lt ; n / 2 ; i ++ )
swap ( A [ i ], A [ n - i -1 ]);
输出数组 A
}
}
<!-- page 35 -->
( 3 ) // 时间复杂度: O(nlogn) 空间复杂度:O(logn)
## 【解法2】优化解:多指针法
(1)该方法类似快速排序,只需要把左侧小于k的元素和右侧大于k的元素不断交换即可。左右分别设置指针i、j,j从右往左移动直到A[j]>k,i从左往右移动直到 A[i]<k,交换 A[i]和 A[j]。不断重复该过程直到i和j相遇。
(2) 伪代码如下:
void Multiointers ( int A [], int L , int R , int k )
{
if ( L & gt ; = R ) return ; // 当前区间元素个数<=1则退出
int pivot , i = L , j = R ; // i和j是左右两个数组下标移动
pivot = k ;
while ( i & lt ; j )
{
while ( i & lt ; j & amp ; & amp ; A [ j ] & lt ; = pivot ) j -- ; //右边<=则不处理
while ( i & lt ; j & amp ; & amp ; A [ i ] & gt ; = pivot ) i ++ ; //左边>=则不处理
if ( i & lt ; j ) swap ( A [ i ], A [ j ]); // A[j]>k, A[i]<k
}
}
int ans ( int A [], int n , int k )
{
Multiointers ( A , 0 , n -1 , k );
按顺序输出A数组
}
( 3 ) // 时间复杂度:O(n) 空间复杂度:O(1)
## 习题2-6
(13分)有一个数组A由n个互不相同的正整数组成(n为奇数),该序列中的数先减后增,中位数是最小值,输入一个整数key。请设计一个时间和空间上尽可能高效的算法,判断key是否在该数组中,如果是则返回数组下标(下标从0开始),否则返回-1。例如对应 A = {10, 7, 5, 2, 6,13, 15},key = 5 时返回下标 2,若 key = 8 则返回 -1。
(1)给出算法的基本设计思想。
(2) 根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
(3)说明你所设计算法的时间复杂度和空间复杂度。
<!-- page 36 -->
<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(logn)</td><td rowspan=1 colspan=1>0(1)</td><td rowspan=1 colspan=1>13</td></tr></table>
## 【解法1】暴力解:枚举,扫描一遍数组
(1)循环i扫描一次数组,如果A[i]==key则返回i,如果没找到则返回-1。
(2) 伪代码如下:
int ans ( int A [], int n , int key )
{
for ( int i = 0 ; i & lt ; n ; i ++ )
if ( A [ i ] == key ) return i ;
return -1 ;
}
( 3 ) // 时间复杂度:O(n) 空间复杂度:O(1)
## 【解法2】优化解:折半查找两次
(1)A[0...n/2]递减,前半段折半查找一次;A[n/2...n-1]递增,后半段再折半查找一次。
(注意:因为一个是降序一个是升序,这两次写法会有不同,也可以合成一种写法)
(2) 伪代码如下:
//在升序数组中查找
int B_Search_up ( int A [], int L , int R , int x )
{
int mid ;
while ( L & lt ; = R ) //只要区间不为空就继续
{
mid = ( R + L ) / 2 ; //防溢出 首计算
if ( A [ mid ] == x )
return mid ; // 找到了,立即返回
else
if ( x & gt ; A [ mid ]) R = mid - 1 ;
else L = mid + 1 ; // x 在右半边,mid 被排除
}
return -1 ; //循环结束,说明没找到
//在降序数组中查找 m
int B_Search_down ( int A [], int L , int R , int x )
{
int mid ;
while ( L & lt ; = R ) // 只要区间不为空就继续
{
mid = ( R + L ) / 2 ; //防溢出
if ( A [ mid ] == x )
return mid ; //找到了,立即返回
}
}
}
<!-- page 37 -->
else
if ( x & lt ; A [ mid ]) R = mid - 1 ;
else L = mid + 1 ; // x 在右半边,mid 被排除
}
return -1 ; //循环结束,说明没找到
int ans ( int A [], int n , int key )
{
int i ;
i = B_Search_down ( A , 0 , n / 2-1 , key ); // 折半查找降序序列
if ( i != -1 )
return i ; //折半查找降序成功
i = B_Search_up ( A , n / 2 , n -1 , key ); //折半查找升序序列
return i ;
( 3 ) // 时间复杂度:O(logn) 空间复杂度:O(1)
## 习题2-7
(13 分)定义距离 d = x - yl,给定 2 个长度为 n 的升序数组 a、b,x 是数组 a 中的某个元素,y 是数组 b 中的某个元素。请设计一个尽可能高效的算法,计算并输出所有可能的的最小距离 d。例如数组 a = {-16,-8,5,8,13},b ={-2,0,2, 6, 10},则最小距离为 1,相应的 x=5, y=6, d= |5 − 6|=1。
(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(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>0(1)</td><td rowspan=1 colspan=1>13</td></tr></table>
← 内容总览 05 / 24 下一节 →