【解法1】暴力解:枚举
强化讲义 · 原始转写 · 原书检索 · 文字用于定位,公式与图表请回原页核验 · 本次导入快照
## 【解法1】暴力解:枚举
(1) 二重循环i和j扫描数组,当i和j不相等且A[i]+A[j]== k时返回1,如果扫描完都没找到则返回0。(2)伪代码如下:
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[i]则返回1。如果i遍历完整个链表都无法找到则返回0。(2)伪代码如下:
<!-- suspect-code -->
> [!warning] 这段代码需要核对(原文第 66 页)
> OCR 体检未通过:疑似花括号被 OCR 读成单个字符。扫描件的两栏排版(代码 + 右侧注释)有时会被交错读取,产生看起来合理但语义错误的代码。背之前请对照原文。
//折半查找
int B_Search(int A[], int L, int R, int x)
{
int mid;
while (L <= R)
{
mid = (R + L) / 2;
if (A[mid] == x)
return mid;
else
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 (i != j) return 1; // 需要保证 i 和 j 不相等
}
return 0;
1
(3)时间复杂度:O(nlogn)空间复杂度:O(1)
## 【解法3】优化解:散列表
<!-- page 67 -->
(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)伪代码如下:
int ans(int A[], int n, int k)
{
int count[k+1] = {0};
for (int i = 0; i < n; i++)
{
if (A[i] > k) break;
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]+ Aj] 等于 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)说明你所设计算法的时间复杂度和空间复杂度。
<!-- page 68 -->
<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>11</td></tr><tr><td rowspan=1 colspan=1>优化解</td><td rowspan=1 colspan=1>多指针法</td><td rowspan=1 colspan=1>tO(n)</td><td rowspan=1 colspan=1>O(n)</td><td rowspan=1 colspan=1>13</td></tr></table>
【解法1】暴力解:枚举法1)将数组A中的每个数和数组B中的每个数比较,如果相同就存入数组C,最后输出数组C。2)伪代码如下:
<!-- verified-by-hand -->
> [!success] 已结合原文第 68 页及 Markdown 上下文人工校对
> 两个指针移动分支被移出 while,导致越界;相等时 j++ 也脱离条件。
int ans(int A[], int B[], int n)
{
int C[n], i = 0, j = 0, k = 0;
while (i < n && j < n)
{
if (A[i] == B[j]) { C[k++] = A[i]; i++; j++; }
else if (A[i] < B[j]) i++;
else j++;
}
output(C, k);
return k;
}
(3)时间复杂度:O(n²)空间复杂度:O(n)
## 【解法2】优化解:多指针法
指针。(2)伪代码如下:
<!-- suspect-code -->
> [!warning] 这段代码需要核对(原文第 68 页)
> OCR 体检未通过:花括号不平衡。扫描件的两栏排版(代码 + 右侧注释)有时会被交错读取,产生看起来合理但语义错误的代码。背之前请对照原文。
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)
<!-- page 69 -->
## 习题2-10
(13分)将一个数组最开始的若于元素搬到数组的末尾,称之为数组的旋转。已知数组A为某个有序数组旋转后的结果(无重复值),设计一个时间上尽可能高效的算法求该旋转数组的最小元素。例:数组A = {3,4, 5,6,1,2} 为有序数组{1,2, 3,4, 5,6}的—个旋转数组,该数组的最小值为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(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>枚举 2</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></td></tr></table>
【解法1】暴力解:快速排序1)对数组A快速排序然后输出最小值A[0]。2)伪代码如下:
//快速排序
void Qsort(int A[], int L, int R)
{
if (L >= R) return; //区间≤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)
<!-- page 70 -->
(1)遍历一趟数组,依次判断每个元素是否比当前已存在的最小值A\_min更小,如果是则更新当前最小值。遍历完数组之后输出A\_min。(2)伪代码如下:
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 所在区间是否包含最小值。由于数组整体是旋转有序数组: X
若 A[mid] <= A[R],说明右半部分有序,最小值一定在左半部分(含 mid)。
若 A[mid]> A[R],说明最小值一定在右半部分(不含 mid)。(2)伪代码如下:
<!-- suspect-code -->
> [!warning] 这段代码需要核对(原文第 70 页)
> OCR 体检未通过:花括号不平衡;中文出现在代码行但缺注释符。扫描件的两栏排版(代码 + 右侧注释)有时会被交错读取,产生看起来合理但语义错误的代码。背之前请对照原文。
int B Search(int A[], int L, int R)
{ int mid;
while (L < R)
{ 如果L>R则退出循环
mid = (L+R)/2; // mid取中间数,向下取整
if (A[mid] <= A[R]) R = mid; //取左一半
else L = mid + 1; //取右一半
}
return L;
int ans(int A[], int n)
{
int i = B_Search(A, 0, n-1);
return A[i];
}
(3)时间复杂度:O(logn)空间复杂度:O(1)
因为本题比较元素不是要查找到x的位置,而是根据某一种条件来缩小区间,所以用到的是折半查找的写法2。
## 习题2-11
<!-- page 71 -->
(15 分) 已知—个整数升序序列 A = {a0, a1, . . . , an−1},其中 0 ≤ ai < n (0 ≤ i < n)。连续子序列是从原序列中选取的一段连续的元素构成的序列(至少包含一个元素),如{a0,a1}、{a0}都是连续子序列。设计一个在时间上尽可能高效的算法,输出元素之和<=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 语言描述算法,关键之处给出注释。(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>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>15</td></tr></table>
## 【解法1】暴力解:枚举
(1)两层循环枚举,外层i枚举连续子序列的左边界,内层j枚举右边界,判断连续子序列A[i\~j] 是否满足元素之和 <= k(我们可以预先设置一个数组S,S[i+1] = A[0] + A[1] + ... +A[i],只需要判断 S[j+1] - S[i]是否 <=k 即可),如果是则计数器 count 加 1,最后输出count。(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;
}
(3)时间复杂度:O(n²)空间复杂度:O(n)
## 【解法2】优化解:多指针法
(1) 用指针 i, j 维护—个窗口,保证其和 current sum <= k。j 作为右边界向右移动,同时累加窗口内的元素和。当窗口和超过k时,移动左边界来缩小窗口,直到和再次满足条件。每当j移动到新位置,都将以j为右边界、且和小于等于k的子序列个数加到累加和count里,这些子序列的起点就是从i到j,个数为j-i+1。最终得到的累加和 count 就是所有符合条件的
<!-- page 72 -->
int ans(int A[], int n, int k)
{
int count = 0;
int sum = 0;
int i = 0; //窗口左边界
for (int j = 0; j < n; j++) // j 是窗□右边界
{
sum += A[j];
{ while (sum > k)
sum -= A[i];
i++;
}
count += (j - i + 1);
}
return count;
}
(3) 时间复杂度:O(n) 空间复杂度:O(1)
## 习题2-12
(13 分) 已知两个非负整数序列 A = {a0, a1, . .., an−1}、B = {b0, b1, .. . , bn−1},求出有多少个数对{ai, bi}使得ai+bj <=5n,设计一个在时间上尽可能高效的算法,有多少个这样的数对。例:A= <2,10,8>,B = <8,12,1>,n = 3,<2,8>、<2,12>、<2,1>、<10,1>、<8,1> 共满足要求的数对,返回5。(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>秀</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) 两层循环i 和 j 遍历数组(保证 i < j),如果 A[i] + B[j] <= 5n 则计数器 t+1,遍历完后返回t。(2)伪代码如下:
int ans(int A[], int B[], int n)
{
int t = 0;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (A[i] + B[j] <= 5 * n) t++;
return t;
}
<!-- page 73 -->
(3)时间复杂度:O(n²)空间复杂度:O(1)