【解法1】暴力解:枚举
原始讲义 · 非增强校订版 · 原书检索 · 文字用于定位,公式与图表请回原页核验 · 本次导入快照
## 【解法1】暴力解:枚举
(1)使用双指针i,j分别指向a,b,同步遍历。每次移动指向较小元素的指针,才有可能获得更小的距离。
(2) 伪代码如下:
int ans(int a[], int b[], int n)
{
int D_min = INT_MAX;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
D_min = min(D_min, abs(a[i] - b[j]));
return D_min;
}
(3)时间复杂度:O(n²) // 空间复杂度:O(1)
<!-- page 38 -->
## 【解法2】优化解:多指针法
(1)使用双指针i,j分别指向a,b,同步遍历。每次移动指向较小元素的指针,才有可能获得更小的距离。
(2) 伪代码如下:
int ans(int a[], int b[], int n)
{
int D_min = INT_MAX;
int i = 0, j = 0;
while (i<n && j<n)
{
D_min = min(D_min, abs(a[i]- b[j]));
if (a[i]<b[j]) i++;
else j++;
}
return D_min;
}
(3) 时间复杂度:O(n) 空间复杂度:O(1)
## 习题2-8
(15分)已知升序数组A含有n个非负整数,没有重复元素,设计一个时间上尽可能高效的算法求是否存在两个数和为k(k>0),如果存在则返回1,不存在则返回0。
例:A={1,2,3,7,8,11,15},k=18,则7+11=18,返回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(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>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(k)</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(1)</td><td rowspan=1 colspan=1>15</td></tr></table>
注意 A[i] + A[j] == k 时还需要保证i和 j 不相等,这个条件一定要注意。
## 【解法1】暴力解:枚举
(1)二重循环i和j扫描数组,当i和j不相等且A[i]+A[j]==k时返回1,如果扫描完都没找到则返回0。
(2) 伪代码如下:
<!-- page 39 -->
int ans(int A[], int n, int k)
{
for (int i=0; i<n; i++)
for (int j=0; j<n; j++)
if (i!=j && A[i]+A[j]==k) return 1;
return 0;
}
(3) // 时间复杂度:O(n²) 空间复杂度:O(1)
## 【解法2】优化解:折半查找
(1)循环i遍历数组,如果A[i]<=k则在整个数组中折半查找k-A[i],若折半查找能够找到k-A[il则返回1。如果i遍历完整个链表都无法找到则返回0。 ka
(2) 伪代码如下:
<!-- verified-by-hand -->
> [!success] 已结合原文第 39 页及 Markdown 上下文人工校对
> while 和 for 缺块,return 越出函数;更重要的是查找失败 j=-1 时原条件 i!=j 恒真,会误报成功。
int B_Search(int A[], int L, int R, int x)
{
while (L <= R)
{
int mid = (L + R) / 2;
if (A[mid] == x) return mid;
if (x < A[mid]) R = mid - 1;
else L = mid + 1;
}
return -1;
}
int ans(int A[], int n, int k)
{
for (int i = 0; i < n; i++)
{
int j = B_Search(A, 0, n - 1, k - A[i]);
if (j != -1 && i != j) return 1;
}
return 0;
}
(3)时间复杂度:O(nlogn) // 空间复杂度:O(1)
## 【解法3】优化解:散列表
(1)设置count数组表示某个数出现的次数(数组下标0\~k),循环遍历数组A并更新count数组,遍历i访问A[i],如果A[i]<=k则判断count[k-A[i]]已经>0说明之前k-A[i]已经出现过,说明A[i]和k-A[i]都已经找到,输出1。如果最终都没有找到,则输出0。
(2) 伪代码如下:
<!-- page 40 -->
int ans(int A[], int n, int k)
{
int count[k+1] = {0}; //散列表记录某个数出现的次数
for (int i = 0; i < n; i++)
{
if (A[i] > k) continue;
if (count[k-A[i]] > 0) return 1; //找到A[i]和k-A[i]
count[A[i]]++;
}
return 0;
}
(3) // 时间复杂度: O(n) 空间复杂度:O(k)
## 【解法4】优化解:多指针法
(1)设置变量i、j存储数组下标,初始i = 0,j = n-1,此前往后循环遍历i,对于每个i从上次的j出发往左找直到 A[i]+ A[j] <= k,对于这次i的取值只可能是当前的j能满足 A[i] + A[j] 等于 k,若能够找到 A[i] + A[j] 等于 k 则返回 1,不断重复这个过程直到i和j相遇,如果一直找不到就返回0。
(2) 伪代码如下:
int ans(int A[], int n, int k)
{
int j = n-1;
for (int i = 0; i < n; i++)
{
while (i<j && A[i]+A[j]>k) j--;
if (i >= j) return 0;
if (A[i] + A[j] == k) return 1;
}
}
(3) // 时间复杂度: O(n) 空间复杂度:O(1)
## 习题2-9
(13分)已知升序数组(无重复元素)A、B都含有n个整数,设计一个时间上尽可能高效的算法求新的数组C使得C中只存在A、B中都包含的元素,输出数组C。
(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(n2)</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>13</td></tr></table>
<!-- page 41 -->
## 【解法1】暴力解:枚举法
1)将数组A中的每个数和数组B中的每个数比较,如果相同就存入数组C,最后输出数组C。
2)伪代码如下:
int ans(int A[], int B[], int n)
{
int C[n];
int k = 0;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (A[i] == B[j]) C[k++] = A[i];
输出数组 C
}
(3) // 时间复杂度:O(n²) 空间复杂度:O(n)
## 【解法2】优化解:多指针法
(1)使用双指针i,j同步遍历A,B。若 A[i] == B[j] 则为交集元素;否则移动指向较小元素的指针。
(2) 伪代码如下:
int ans(int A[], int B[], int n)
{
int C[n];
int i = 0, j = 0, k = 0; // i、j作为遍历A、B的下标
while (i<n && j<n) //直到有一个数组遍历完
{
if (A[i] == B[j]) //数组A和B中都存在的元素存入C中
{
C[k++] = A[i++];
j++;
}
if (A[i] < B[j]) i++; // A[i]更小,i指针后移
if (A[i] > B[j]) j++; //B[j]更小,j指针后移
}
输出数组C
}
(3) 时间复杂度:O(n) 空间复杂度:O(n)
## 习题2-10
(13分)将一个数组最开始的若于元素搬到数组的末尾,称之为数组的旋转。已知数组A为某个有序数组旋转后的结果(无重复值),设计一个时间上尽可能高效的算法求该旋转数组的最小元素。
例:数组 A = {3,4,5,6, 1, 2} 为有序数组 {1, 2, 3,4, 5, 6} 的—个旋转数组,该数组的最小值为 1。
(1)给出算法的基本设计思想。
(2)根据设计思想,采用C、C++或Java语言描述算法,关键之处给出注释。
(3)说明你所设计算法的时间复杂度和空间复杂度。
<!-- page 42 -->
<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>9</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>11</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)对数组A快速排序然后输出最小值A[0]。
2)伪代码如下:
//快速排序
void Qsort(int A[], int L, int R)
{
if (L >= R) return; //区间内元素个数为0或 1,自然有序,递归
终止
int i = L, j = R;
int pivot = A[L]; // 选取区间第一个元素作为基准
// 不断交换左边比 pivot 大的数和右边比 pivot 小的数
while (i < j)
{
while (i < j && A[j] >= pivot) j--; // 从右向左找第一个小于 pivot 的数
while (i < j && A[i] <= pivot) i++; // 从左向右找第一个大于 pivot 的数
if (i < 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[0];
}
}
(3) // 时间复杂度:O(nlogn) 空间复杂度:O(logn)
## 【解法2】暴力解:枚举
(1)遍历一趟数组,依次判断每个元素是否比当前已存在的最小值A\_min更小,如果是则更新当前最小值。遍历完数组之后输出A\_min。
(2) 伪代码如下:
<!-- page 43 -->
int ans(int A[], int n)
{
int A_min = A[0];
for (int i = 1; i < n; i++)
A_min = min(A[i], A_min);
return A_min;
}
(3) // 时间复杂度:O(n) 空间复杂度:O(1)
## 【解法3】优化解:折半查找
(1)对数组进行折半查找,如果L==R或者A[mid]比A[mid-1]小那就说明已经找到最小值所在,返回A[mid]。每次二分,判断mid所在区间是否包含最小值。由于数组整体是旋转有序数组:
• 若 A[mid]<= A[R],说明右半部分有序,最小值一定在左半部分(含 mid)。
• 若 A[mid] > A[R],说明最小值一定在右半部分(不含 mid)。
(2) 伪代码如下:
<!-- verified-by-hand -->
> [!success] 已结合原文第 43 页及 Markdown 上下文人工校对
> while 缺少花括号,return L 被挤到函数外,复杂度说明也被错放进代码块。
int B_Search(int A[], int L, int R)
{
while (L < R)
{
int mid = (L + R) / 2;
if (A[mid] <= A[R]) R = mid;
else L = mid + 1;
}
return L;
}
int ans(int A[], int n)
{
return A[B_Search(A, 0, n - 1)];
}
## 习题2-11
(15 分)已知一个整数升序序列 A = {a0, a₁,…, an-1},其中 0 ≤ a < n(0 ≤ i < n)。连续子序列是从原序列中选取的一段连续的元素构成的序列(至少包含一个元素),如{a0,a₁}、{ao}都是连续子序列。设计一个在时间上尽可能高效的算法,输出元素之和<=k的连续子序列个数。
例:A = {1,2,3,4,5},n = 5,k = 6,则符合要求的连续子序列为 {1}、{2}、{3}、{4}、{5}、{1,2}、{2,3}、{1,2,3},共有8个,返回8。
(1)给出算法的基本设计思想。
(2)根据设计思想,采用C、C++或Java语言描述算法,关键之处给出注释。
<!-- page 44 -->
(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>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>15</td></tr></table>
## 【解法1】暴力解:枚举
(1)两层循环枚举,外层i枚举连续子序列的左边界,内层i枚举右边界,判断连续子序列Ai\~i]是否满足元素之和<=k(我们可以预先设置一个数组 S,S[i+1] = A[0] + A[1] +.. + A[i],只需要判断 S[j+1] - S[i]是否<=k即可),如果是
(2) 伪代码如下:
int ans(int A[], int n, int k)
{
int count = 0;
int s[n + 1] = {0};
for (int i = 0; i < n; i++) s[i + 1] = s[i] + A[i];
for (int i = 0; i < n; i++)
for (int j = i; j < n; j++)
if (s[j + 1] - s[i] <= k) count++;
return count;
}
```c
(3)时间复杂度:O(n²) // 空间复杂度:O(n)
```