【解法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&lt;n &amp;&amp; j&lt;n)
    {
        D_min = min(D_min, abs(a[i]- b[j]));
        if (a[i]&lt;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&lt;n; i++)
    for (int j=0; j&lt;n; j++)
    if (i!=j &amp;&amp; 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 &lt;= R)
    {
        int mid = (L + R) / 2;
        if (A[mid] == x) return mid;
        if (x &lt; A[mid]) R = mid - 1;
        else L = mid + 1;
    }
    return -1;
}

int ans(int A[], int n, int k)
{
    for (int i = 0; i &lt; n; i++)
    {
        int j = B_Search(A, 0, n - 1, k - A[i]);
        if (j != -1 &amp;&amp; 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 &lt; n; i++)
    {
        if (A[i] &gt; k) continue;
        if (count[k-A[i]] &gt; 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 &lt; n; i++)
    {
        while (i&lt;j &amp;&amp; A[i]+A[j]&gt;k) j--;
        if (i &gt;= 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 &lt; n; i++)
    for (int j = 0; j &lt; 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&lt;n &amp;&amp; j&lt;n) //直到有一个数组遍历完
    {
        if (A[i] == B[j]) //数组A和B中都存在的元素存入C中
        {
            C[k++] = A[i++];
            j++;
        }
        if (A[i] &lt; B[j]) i++; // A[i]更小,i指针后移
        if (A[i] &gt; 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 &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]);
        //递归处理左右两个子区间
        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 &lt; 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 &lt; R)
    {
        int mid = (L + R) / 2;
        if (A[mid] &lt;= 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 &lt; n; i++) s[i + 1] = s[i] + A[i];
    for (int i = 0; i &lt; n; i++)
    for (int j = i; j &lt; n; j++)
    if (s[j + 1] - s[i] &lt;= k) count++;
    return count;
}
```c (3)时间复杂度:O(n²) // 空间复杂度:O(n) ```