算法基础课——第一章 基础算法(一)

article/2025/11/10 18:39:27
image-20210807031457384

第一章 基础算法(一)


  • 上课的主要任务是把算法的主要思想给理解清楚(虽然讲课的内容和英语差不多,都是以背为主,但是如果不理解算法的话,还是很难应用它的).
    • 所以我们需要对算法思想,和它为什么是对的,有一个深刻的理解.
  • 课后可以做两方面的事情:
    1. 理解算法,并把模板背过(这里的背过不是一个字母一个字母地去背,而是在需要使用该算法时,能够快速地把算法的模板默写出来、调试通过就可以了);
      • 背也是有方法的,主要是完全熟悉算法思想,结合模板来理解.
      • 默写也是需要找相应的模板题来默写.
    2. 课后刷题.

Q:如何提高算法模板的熟练度?

A:一道模板题写完后,删掉原来的代码重新写一遍,大概重复 3 ∼ 5 3\sim 5 35 次,就可以对模板有一个很好的记忆了.


排序


  • 主要讲两个排序算法(因为其他排序算法在算法竞赛中的应用也不是很多.
  • 排 序 { 快 速 排 序 归 并 排 序 排序\begin{cases}快速排序\\归并排序\end{cases} {.

AcWing 785. 快速排序

原题链接

给定你一个长度为 n n n 的整数数列。

请你使用快速排序对这个数列按照从小到大进行排序。

并将排好序的数列按顺序输出。

输入格式

输入共两行,第一行包含整数 n n n

第二行包含 n n n 个整数(所有整数均在 1 ∼ 1 0 9 1∼10^9 1109 范围内),表示整个数列。

输出格式

输出共一行,包含 n n n 个整数,表示排好序的数列。

数据范围

1 ≤ n ≤ 100000 1≤n≤100000 1n100000

输入样例:

5
3 1 2 4 5

输出样例:

1 2 3 4 5

时/空限制: 2s / 64MB
来源: 模板题
算法标签:快速排序

yxc’s Solution

  • 快速排序的主要思想是分治.

  • 对于一段待排序的数组 q [ L ∼ R ] \rm{q[L\sim R]} q[LR] 来说,快速排序的算法流程如下:

    1. 先随便在数组当中找到一个值 x x x 作为分界点(确定分界点),常用的分界点有如下几种:

      • 取左边界,即 x = q [ L ] x=\rm{q[L]} x=q[L]

      • 取中间值,即 x = q [ m i d ] x=\rm{q[mid]} x=q[mid],其中 m i d = ⌊ L + R 2 ⌋ \rm{mid=\lfloor \frac{L+R}{2}}\rfloor mid=2L+R

      • 取右边界,即 x = q [ R ] x=\rm{q[R]} x=q[R]

      • 在区间 [ L , R ] \rm [L,R] [L,R] 中随机一个位置.

        这里推荐使用取中间值作为分界点.

    2. 根据 x x x 的值,将整个区间分为两半(调整区间):

      • ⩽ x \leqslant x x 的数放到区间的左半部分,将 ⩾ x \geqslant x x 的数放到区间的右半部分.
      • 注意左半部分和右半部分不一定含有相同数量的数,这是依据 x x x 的值而定的.
      • 而且 x x x 不一定是左半部分的最后一个数或右半部分的第一个数(因为可能有很多数和 x x x 相等),只要符合调整区间的结果就可以.
    3. 递归处理左半部分和右半部分.

      • 因为左半部分的最大值是小于右半部分的最小值的;
      • 如果左半部分和右半部分都分别排好序了,拼接到一起一定是整个数组排好序了的状态.
  • 如何优雅地调整区间是快速排序的重难点,有很多种实现方法,这里介绍一种思想比较简单的实现方法:

    1. 先开两个额外的数组 a [ ] , b [ ] \rm{a[],b[]} a[],b[].
    2. 扫描遍历 q [ L ∼ R ] \rm{q[L\sim R]} q[LR],对于数组 q [ ] \rm{q[]} q[] 的第 i i i 个数 q [ i ] \rm{q[i]} q[i],如果:
      • q [ i ] ⩽ x \rm{q[i]}\it \leqslant x q[i]x,将 q [ i ] \rm{q[i]} q[i] 插入到数组 a [ ] \rm a[] a[] 的末尾.
      • q [ i ] ⩾ x \rm q[i] \it \geqslant x q[i]x,将 q [ i ] \rm{q[i]} q[i] 插入到数组 b [ ] \rm b[] b[] 的末尾.
    3. 先将 a [ ] \rm a[] a[] 中的数覆盖到 q [ ] \rm q[] q[] 中,再将 b [ ] \rm b[] b[] 中的数覆盖到 q [ ] \rm q[] q[] 中.
  • 但是这样会开辟额外的空间,这里介绍一种优美的实现这种思想的方法,不需要开辟额外的空间:

    1. 使用两个指针 i , j i,j i,j,第一个指针 i i i 指向数组开头 q [ L ] \rm q[L] q[L],第二个指针 j j j 指向数组结尾 q [ R ] \rm q[R] q[R].
    2. 指针 i i i 先往右移动,直到出现 q [ i ] ⩾ x \rm q[\it i\rm ] \it \geqslant x q[i]x i i i 停止移动;指针 j j j 再往左移动,直到出现 q [ j ] ⩽ x \rm q[ \it j \rm ] \it \leqslant x q[j]x 时停止移动.
    3. 此时 q [ i ] \rm q[\it i\rm ] q[i] 需要放到右半部分,而 q [ j ] \rm q[\it j\rm ] q[j] 需要放到左半部分,因此只需要交换它们就好了.
      • 交换完后,有 q [ L ∼ i ] \rm q[L\sim \it i\rm] q[Li] 都是左半部分的数, q [ j ∼ R ] \rm q[\it j \rm \sim R] q[jR] 都是右半部分的数.
      • 即在任何时刻, i i i 左边的数都是 ⩽ x \leqslant x x 的, j j j 右边的数都是 ⩾ x \geqslant x x 的.
    4. 直到 i i i j j j 相遇或越过彼此为止.
  • 快速排序是不稳定的排序算法,期望时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn).

    快速排序的最坏时间复杂度为 O ( n 2 ) O(n^2) O(n2).

    但是它期望每次都能将数组对半分,那么递归过程就类似一棵二叉树,其递归层数为 log ⁡ n \log n logn,故期望的时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn).

    这里的稳定与不稳定不是指排序算法的时间效率,而是两个相同的数字,在排序前后的位置是不是相对不变的.

    如果位置一定相对不变,则称排序算法是稳定的;否则,排序算法就是不稳定的.

    当然,快速排序是可以变成稳定的排序算法,比如用某种机制让所有的数字不相同.

#include <cstdio>
#include <algorithm>using namespace std;const int N = 1e5 + 10;int n;
int q[N];void quick_sort(int q[], int L, int R)
{if (L >= R) return;int x = q[(L + R) / 2], i = L - 1, j = R + 1;/*由于下面的 while 循环的逻辑是先将指针往中间移动一次,再判断其是左半部分的数还是右半部分的数,所以这里指针的初始值是左右边界往外再扩一格.*/while (i < j){do i ++ ; while (q[i] < x);do j -- ; while (q[j] > x);if (i < j) swap(q[i], q[j]);}quick_sort(q, L, j);quick_sort(q, j + 1, R);/*这里最终可能会出现 i = j 或者 i = j + 1 两种情况,综合考虑,如上 while 循环能保证 L ~ i - 1 在左半部分, j + 1 ~ R 在右半部分,这里取 j + 1 ~ R 为右半部分,则 L ~ j 即为左半部分,但需要注意这种写法不能去分界点为 q[R],因为可能会出现 j = R 而出现死循环.*/
}int main()
{scanf("%d", &n);for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);quick_sort(q, 0, n - 1);for (int i = 0; i < n; i ++ ) printf("%d ", q[i]);return 0;
}
// 运行时间: 162 ms
// 运行空间: 1492 KB  

AcWing 786. 第k个数

原题链接

给定一个长度为 n n n 的整数数列,以及一个整数 k k k,请用快速选择算法求出数列从小到大排序后的第 k k k 个数。

输入格式

第一行包含两个整数 n n n k k k

第二行包含 n n n 个整数(所有整数均在 1 ∼ 1 0 9 1∼10^9 1109 范围内),表示整数数列。

输出格式

输出一个整数,表示数列的第 k k k 小数。

数据范围

1 ≤ n ≤ 100000 1≤n≤100000 1n100000,
1 ≤ k ≤ n 1≤k≤n 1kn

输入样例:

5 3
2 4 1 5 3

输出样例:

3

时/空限制: 1s / 64MB
来源: 模板题
算法标签:快速排序 快速选择

yxc’s Solution

  • 如果直接用快速排序来做,那么时间复杂度就是 O ( n log ⁡ n ) O(n\log n) O(nlogn) 了.

  • 而快速选择算法的时间复杂度是 O ( n ) O(n) O(n) 的.

  • 类似于快速排序算法,对于数组 q [ L ∼ R ] \rm q[L \sim R] q[LR],快速选择算法的流程如下:

    1. 确定分界点 x x x,一般取分界点 x x x q [ L ] \rm q[L] q[L] q [ R ] \;q[R] q[R] q [ ⌊ L + R 2 ⌋ ] q[\lfloor\frac{L+R}{2}\rfloor] q[2L+R].
    2. 根据 x x x 的值,将整个区间分为两半(调整区间),将 ⩽ x \leqslant x x 的数放到区间的左半部分,将 ⩾ x \geqslant x x 的数放到区间的右半部分.
    3. 假设左半部分数字的个数为 S L S_L SL,右半部分数字的个数为 S R S_R SR,若:
      • k ⩽ S L k\leqslant S_L kSL,说明当前数组的第 k k k 小的数在左半部分,此时只需要递归处理左半部分即可.
      • k > S L k > S_L k>SL,说明当前数组的第 k k k 小的数在右半部分,此时只需要递归处理右半部分即可,不过需要注意的是,此时需要寻找的,是右半区间第 k − S L k-S_L kSL 小的数.
  • 每次期望寻找的区间长度都会减少一半,则总的期望遍历次数为 n + n 2 + n 4 + ⋯ ⩽ 2 n n+\frac{n}{2}+\frac{n}{4}+\cdots \leqslant 2n n+2n+4n+2n,故总的期望时间复杂度为 O ( n ) O(n) O(n).

    1 + 1 2 + 1 4 + ⋯ = lim ⁡ n → + ∞ 1 − 1 2 n 1 2 = 2 1+\frac{1}{2}+\frac{1}{4}+\cdots=\lim\limits_{n\to +\infty}\frac{1-\frac{1}{2^n}}{\frac{1}{2}}=2 1+21+41+=n+lim2112n1=2.

#include <cstdio>
#include <algorithm>using namespace std;const int N = 1e5 + 10;int n, k;
int q[N];int quick_select(int q[], int L, int R, int k)
{if(L == R) return q[L];/*这里写 L == R 是因为我们时刻保证第 k 小的数在区间 [L,R] 的范围之内,所以区间 [L,R] 至少包含一个数,不会出现像快速排序那样需要写 L >=R (即区间可能没有数)的情况.*/int x = q[(L + R) / 2], i = L - 1, j = R + 1;while (i < j){while (q[ ++ i] < x);while (q[ -- j] > x);if (i < j) swap(q[i], q[j]);}int SL = j - L + 1;if (k <= SL) return quick_select(q, L, j, k);else return quick_select(q, j + 1, R, k - SL);
}int main()
{scanf("%d %d", &n, &k);for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);printf("%d", quick_select(q, 0, n - 1, k));return 0;
}
// 运行时间: 34 ms
// 运行空间: 596 KB 

AcWing 787. 归并排序

原题链接

给定你一个长度为 n n n 的整数数列。

请你使用归并排序对这个数列按照从小到大进行排序。

并将排好序的数列按顺序输出。

输入格式

输入共两行,第一行包含整数 n n n

第二行包含 n n n 个整数(所有整数均在 1 ∼ 1 0 9 1∼10^9 1109 范围内),表示整个数列。

输出格式

输出共一行,包含 n n n 个整数,表示排好序的数列。

数据范围

1 ≤ n ≤ 100000 1≤n≤100000 1n100000

输入样例:

5
3 1 2 4 5

输出样例:

1 2 3 4 5

时/空限制: 1s / 64MB
来源: 模板题
算法标签:归并排序

yxc’s Solution

  • 归并排序的主要思想是分治.

  • 对于一段待排序的数组 q [ L ∼ R ] \rm{q[L\sim R]} q[LR] 来说,归并排序的算法流程如下:

    1. 确定分界点 m i d = ⌊ L + R 2 ⌋ \rm mid=\lfloor \frac{L+R}{2}\rfloor mid=2L+R.

    2. 递归,将数组的左半部分 q [ L ∼ m i d ] \rm q[L\sim mid] q[Lmid] 和右半部分 q [ m i d + 1 ∼ R ] \rm q[mid+1 \sim R] q[mid+1R] 分别先排好序.

    3. 归并,将左右两部分有序的数组合并为一个有序的数组.

  • 如何将两个有序的数组合二为一是归并排序的重难点,实现方法如下:

    1. 用指针 i i i 指向左半部分数组的开头 q [ L ] \rm q[L] q[L],用指针 j j j 指向右半部分数组的开头 q [ m i d + 1 ] \rm q[mid+1] q[mid+1].

    2. 开辟一个新的数组 r e s \rm res res 来合并后的数组.

    3. 比较 q [ i ] \rm q[\it i \rm ] q[i] q [ j ] \rm q[ \it j \rm ] q[j] 哪个更小,更小者放入数组 r e s \rm res res 中,并让对应指针向后移动.

      • q [ i ] = q [ j ] \rm q[\it i \rm ] =\rm q[ \it j \rm ] q[i]=q[j] 时,哪个放入数组 r e s \rm res res 都一样,这里为了稳定,优先考虑将 q [ i ] \rm q[\it i \rm ] q[i] 放入数组 r e s \rm res res 中.

      • 因为这样可以保证 q [ i ] \rm q[\it i \rm ] q[i] q [ j ] \rm q[ \it j \rm ] q[j] 分别为两个数组中剩余所有数的最小值,则其中较小者就是剩余所有数中的最小值.

    4. 直到某一部分的指针指到了该部分对应的结尾,则剩下的另一部分的数组的剩余数字全部放入数组 r e s \rm res res,即可完成合并.

  • 归并排序是稳定的排序算法,时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn).

    对于归并排序来说,每次都会从中间分开进行递归,整个递归的过程可以看作一棵二叉树.

    对于有 n n n 个数的数组的归并排序,二叉树的叶子结点总共有 n n n 个,则二叉树总共有 log ⁡ n \log n logn 层.

    每层都需要把数组合并在一起,每层的时间复杂度都为 O ( n ) O(n) O(n).

    综合到一起,总的时间复杂度即 O ( n log ⁡ n ) O(n\log n) O(nlogn).

#include <cstdio>
#include <iostream>using namespace std;const int N = 1e5 + 10;int n;
int q[N], res[N];void merge_sort(int q[], int L, int R)
{if (L >= R) return;int mid = (L + R) / 2;merge_sort(q, L ,mid); merge_sort(q, mid + 1, R);int k = L, i = L, j = mid + 1;while (i <= mid && j <= R)if (q[i] <= q[j]) res[k ++ ] = q[i ++ ];else res[k ++ ] = q[j ++ ];while (i <= mid) res[k ++ ] = q[i ++ ];while (j <= R) res[k ++ ] = q[j ++ ];for(i = L; i <= R; i ++ ) q[i] = res[i];
}int main()
{scanf("%d", &n);for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);merge_sort(q, 0, n - 1);for (int i = 0; i < n; i ++ ) printf("%d ", q[i]);return 0;
}
// 运行时间: 95 ms
// 运行空间: 5460 KB 

AcWing 788. 逆序对的数量

原题链接

给定一个长度为 n n n 的整数数列,请你计算数列中的逆序对的数量。

逆序对的定义如下:对于数列的第 i i i 个和第 j j j 个元素,如果满足 i < j i<j i<j a [ i ] > a [ j ] a[i]>a[j] a[i]>a[j],则其为一个逆序对;否则不是。

输入格式

第一行包含整数 n n n,表示数列的长度。

第二行包含 n n n 个整数,表示整个数列。

输出格式

输出一个整数,表示逆序对的个数。

数据范围

1 ≤ n ≤ 100000 1≤n≤100000 1n100000
数列中的元素的取值范围 [ 1 , 1 0 9 ] [1,10^9] [1,109]

输入样例:

6
2 3 4 5 6 1

输出样例:

5

时/空限制: 1s / 64MB
来源: 模板题
算法标签:归并排序

yxc’s Solution

  • 这里介绍利用归并排序求解逆序对.

  • 对于一段待排序的数组 q [ L ∼ R ] \rm{q[L\sim R]} q[LR] 来说,归并排序的算法流程如下:

    1. 确定分界点 m i d = ⌊ L + R 2 ⌋ \rm mid=\lfloor \frac{L+R}{2}\rfloor mid=2L+R.

    2. 递归,将数组的左半部分 q [ L ∼ m i d ] \rm q[L\sim mid] q[Lmid] 和右半部分 q [ m i d + 1 ∼ R ] \rm q[mid+1 \sim R] q[mid+1R] 分别先排好序.

    3. 归并,将左右两部分有序的数组合并为一个有序的数组,对于一个逆序对 ( x , y ) (x,y) (x,y),此时有三种情况:

      1. x , y x,y x,y 都在数组的左半部分;

      2. x , y x,y x,y 都在数组的右半部分;

      3. x x x 在数组的左半部分, y y y 在数组的右半部分.

  • 假设归并排序的递归过程,可以帮我们计算数组左半部分 q [ L ∼ m i d ] \rm q[L\sim mid] q[Lmid] 和右半部分 q [ m i d + 1 ∼ R ] \rm q[mid+1 \sim R] q[mid+1R] 的逆序对数量,那么此时就只需要计算第 3 种逆序对的数量就可以了,考虑如何计算:

    • 对于数组右半部分 q [ m i d + 1 ∼ R ] \rm q[mid+1 \sim R] q[mid+1R] 内的数 q [ j ] \rm q[\it j\rm ] q[j],如果可以在数组左半部分 q [ L ∼ m i d ] \rm q[L\sim mid] q[Lmid] 中找到最小的数 q [ i ] \rm q[\it i\rm ] q[i] 使得 q [ i ] > q [ j ] \rm q[\it i\rm ]>\rm q[\it j\rm ] q[i]>q[j].
    • 则说明 q [ i ∼ m i d ] \rm q[\it i\rm \sim mid] q[imid] 区间内的数(共有 m i d − i + 1 \rm mid-\it i\rm +1 midi+1 个)都 > q [ j ] >\rm q[\it j\rm ] >q[j],因为 q [ L ∼ m i d ] \rm q[L\sim mid] q[Lmid] 已经先排好序了.
  • 故只需在合并时,检查是否有 q [ i ] > q [ j ] \rm q[\it i\rm ]>\rm q[\it j\rm ] q[i]>q[j],并计算答案即可(注意逆序对的数量可能超出 int 的表示范围).

  • 时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn).

#include <cstdio>
#include <iostream>using namespace std;typedef long long LL;
const int N = 1e5 + 10;int n;
LL ans;
int q[N], res[N];void merge_sort(int q[], int L, int R)
{if (L >= R) return;int mid = (L + R) / 2;merge_sort(q, L ,mid); merge_sort(q, mid + 1, R);int k = L, i = L, j = mid + 1;while (i <= mid && j <= R)if (q[i] <= q[j]) res[k ++ ] = q[i ++ ];else {ans += mid - i + 1;res[k ++ ] = q[j ++ ];}while (i <= mid) res[k ++ ] = q[i ++ ];while (j <= R) res[k ++ ] = q[j ++ ];for(i = L; i <= R; i ++ ) q[i] = res[i];
}int main()
{scanf("%d", &n);for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);merge_sort(q, 0, n - 1);printf("%lld", ans);return 0;
}
// 运行时间: 79 ms
// 运行空间: 4696 KB 

二分


  • 二分重点讲一下整数二分,因为整数二分在实现时很容易写错、发生死循环、在边界的地方发生问题;然后再讲一下浮点数二分(即小数二分),它会比较简单一些.

  • 二 分 { 整 数 二 分 浮 点 数 二 分 二分\begin{cases}整数二分\\浮点数二分\end{cases} {.

  • 二分查找算法模板

  • 整数二分的本质:

    • 二分的本质其实并不是单调性,也就是说,如果有单调性就可以二分,但可以二分题目不一定具有单调性;
    • 对于某一区间 [ L , R ] \rm[L,R] [L,R],若我们在区间上定义了某种性质,使得性质在区间 [ x , R ] \rm [x,R] [x,R] 内是满足的,而在区间 [ L , x − 1 ] \rm [L,x-1] [L,x1] 内是不满足的;
    • 那么整个区间就会根据这个性质一分为二,而二分就可以查找这个性质的边界,即 x \rm x x x − 1 \rm x-1 x1.
  • 如果想要二分找到 x \rm x x,则实现方法:

    1. 取得中间值 m i d = ⌊ L + R 2 ⌋ \rm mid=\lfloor \frac{L+R}{2} \rfloor mid=2L+R.
    2. 如果 m i d \rm mid mid
      • 满足性质,则说明至少 [ m i d , R ] \rm [mid,R] [mid,R] 是满足性质的,且边界 x \rm x x 就有可能是 m i d \rm mid mid,此时查找的区间就可以缩小到 [ L , m i d − 1 ] \rm [L,mid-1] [L,mid1].
      • 不满足性质,则说明至少 [ L , m i d ] \rm [L,mid] [L,mid] 是不满足性质的,且边界 x \rm x x 就不可能是 m i d \rm mid mid,此时查找的区间就可以缩小到 [ m i d + 1 , R ] \rm [mid+1,R] [mid+1,R].
    3. 直到 L > R \rm L>R L>R,则最后一次可能作为边界 x \rm x x m i d \rm mid mid 就一定是 x \rm x x.
    while (L <= R)
    {int mid = (L + R) / 2;if (check(mid)){ans = mid;R = mid - 1;}else L = mid + 1;
    }
    
  • 无解的情况是与具体题目有关的,与二分的模板是无关的,可以发现二分的模板默认是有解的(当无解时,是不存在最后一次可能作为答案的 m i d \rm mid mid 的).

  • 二分查找的时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn),其中 n n n 为二分查找的区间长度.

AcWing 789. 数的范围

原题链接

给定一个按照升序排列的长度为 n n n 的整数数组,以及 q q q 个查询。

对于每个查询,返回一个元素 k k k 的起始位置和终止位置(位置从 0 0 0 开始计数)。

如果数组中不存在该元素,则返回 -1 -1

输入格式

第一行包含整数 n n n q q q,表示数组长度和询问个数。

第二行包含 n n n 个整数(均在 1 ∼ 10000 1∼10000 110000 范围内),表示完整数组。

接下来 q q q 行,每行包含一个整数 k k k ,表示一个询问元素。

输出格式

q q q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。

如果数组中不存在该元素,则返回 -1 -1

数据范围

1 ≤ n ≤ 100000 1≤n≤100000 1n100000
1 ≤ q ≤ 10000 1≤q≤10000 1q10000
1 ≤ k ≤ 10000 1≤k≤10000 1k10000

输入样例:

6 3
1 2 2 3 3 4
3
4
5

输出样例:

3 4
5 5
-1 -1

时/空限制: 1s / 64MB
来源: 模板题,AcWing
算法标签:二分

yxc’s Solution

  • 直接应用整数二分即可.
  • 时间复杂度为 O ( q log ⁡ n ) O(q \log n) O(qlogn).
#include <cstdio>
#include <iostream>using namespace std;const int N = 1e5 + 10;int n, q, k;
int a[N];int main()
{scanf("%d %d", &n, &q);for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);while (q -- ){scanf("%d", &k);int L = 0, R = n - 1, ansL = 0;while (L <= R){int mid = (L + R) / 2;if (a[mid] >= k){ansL = mid;R = mid - 1;} else L = mid + 1;}if (a[ansL] != k) {printf("-1 -1\n");continue;}L = 0, R= n - 1; int ansR = n; /*这里是寻找数组内第一个 > k 的数字的位置 ansR,此时有数组内最后一个 <=k 的数字的位置为 ansR-1,但数组内的数字可能都 <= k,故无解时 ansR 应为 n,此时对应数组内最后一个 <=k 的数字的位置为 n-1,即最后一个数字.*/while (L <= R){int mid = (L + R) / 2;if (a[mid] > k){ansR = mid;R = mid - 1;}else L = mid + 1;}printf("%d %d\n", ansL, ansR-1);}return 0;
}
// 运行时间: 71 ms
// 运行空间: 728 KB 

AcWing 790. 数的三次方根

原题链接

给定一个浮点数 n n n,求它的三次方根。

输入格式

共一行,包含一个浮点数 n n n

输出格式

共一行,包含一个浮点数,表示问题的解。

注意,结果保留 6 6 6 位小数。

数据范围

− 10000 ≤ n ≤ 10000 −10000≤n≤10000 10000n10000

输入样例:

1000.00

输出样例:

10.000000

时/空限制: 1s / 64MB
来源: 模板题,AcWing
算法标签:二分

yxc’s Solution

  • 浮点数二分和整数二分类似,它要求所寻找的答案一定在当前查找的区间之内.
  • 当我们查找的区间长度很小时,我们就可以认为找到了答案.
  • 时间复杂度为 O ( log ⁡ N ) O(\log N) O(logN),其中 N N N 为二分查找的区间长度.
#include <cstdio>
#include <iostream>using namespace std;const double eps = 1e-7; 
/*这里的 eps 即为精度,因为题目要求保留 6 位小数,故我们要精确求解出 7 位小数才可以.
*/double n;int main()
{scanf("%lf", &n);double L = -1e5, R = 1e5;while(abs(R-L)>eps){double mid = (L + R) / 2;if (mid * mid * mid >= n) R = mid;else L = mid;}printf("%.6lf", L);return 0;
}
// 运行时间: 12 ms
// 运行空间: 220 KB 

本文档基于 AcWing算法基础课 制作

视频链接:第一章 基础算法(一)- AcWing

文档版本:

var1.0 完成于2022.04.26.


http://chatgpt.dhexx.cn/article/IJTNskAH.shtml

相关文章

算法基础课-数学知识

数学知识 第四章 数学知识数论质数约数欧拉函数欧拉定理与费马小定理 拓展欧几里得定理裴蜀定理 中国剩余定理快速幂 高斯消元求组合数卡特兰数 容斥原理博弈论Nim游戏SG函数 第四章 数学知识 数论 质数 质数判定&#xff1a;试除法&#xff0c;枚举时只枚举 i ≤ n i i \leq…

第六章.数据结构与算法基础

目录 第六章.数据结构与算法基础&#xff08;重点&#xff09;第一节.数组与矩阵数组稀疏矩阵 第二节.数据结构的定义第三节.线性表链表详解顺序存储与链式存储对比队列与栈 第四节.广义表第五节.树与二叉树树的概念二叉树的分类二叉树的重要特性二叉树的遍历反向构造二叉树树转…

【基础算法】

1.排序 -快速排序(先排序后递归) 一.找某一个数为基点&#xff08;假设为x&#xff09; 二.将这个数分为|--------<x---------|-------->x-------| x 三.然后递归,x左&#xff0c;右两侧分别排序 四.后输出 核心代码&#xff1a; void quick_sort(int q[], int l,…

算法 基础

什么是算法&#xff1f; 算法&#xff08;Algorithm&#xff09; 是指解题方案的准确而完整的描述&#xff0c; 是一系列解决问题的清晰指令&#xff0c; 算法代表着用系统的方法描述解决问题的策略机制。 一个算法的优劣可以用空间复杂度与时间复杂度来衡量。 大 O 表示法 定…

《算法基础》简单算法

《acwing算法基础》简单算法 文章目录 《acwing算法基础》简单算法快速排序:思路:时间复杂度:代码:快排边界问题:练习题: 归并排序:思路:时间复杂度:代码:练习题: 二分思想:思路:时间复杂度:代码:练习题: 一维前缀合与二维前缀和:差分:双指针算法:模板:练习题: 二进制算法:离散…

算法(基础)

算法基础 学习网址排序线性表顺序表链表栈与队列栈队列贪心法分治法搜索法滤波哈夫曼编码与译码B站视频csdn博客算法是编程的灵魂 学习网址 https://www.runoob.com/排序 冒泡排序分析

算法基础课-基础算法

基础算法 第一章 基础算法1快速排序2归并排序3二分算法整数二分浮点二分 4高精度高精度加法高精度减法高精度乘法&#xff08;一个高精度乘正常整数&#xff09;高精度除法&#xff08;一个高精度除以正常整数&#xff09; 5前缀和一维前缀和二维前缀和 6差分一维差分二维差分 …

【算法基础】基础算法

快速排序 模板题&#xff1a;785. 快速排序 - AcWing题库 思路&#xff1a; 定义一个x&#xff08;一般喜欢用中间的&#xff09;&#xff0c;我们快速排序&#xff0c;让x左边的都比它小&#xff0c;同时让右边的都比它大。然后像二分一样不断细分&#xff0c;缩小范围进行同…

数据结构与算法基础

来源&#xff1a; bilibili数据结构与算法基础&#xff08;青岛大学-王卓&#xff09; 1.2 基本概念和术语 数据结构 数据元素之间的关系称为结构&#xff0c;数据结构是相互之间存在一种或多种特定关系的数据元素的集合。 数据结构的两个层次&#xff1a;逻辑结构和物理结构…

数据结构与算法基础(一)

大家好 我是makasa 这个栏目呢&#xff0c;我会按照我之前通过视频学习的一个笔记来记录. 同时,通过写这几篇blog来巩固一下我的基础 数据结构与算法&#xff0c;顾名思义&#xff0c;分为两个部分&#xff1a;数据结构、算法。那它们各自具体概述是什么呢。让我们看以下两个图…

【算法】算法基础

文章目录 1. 字符串1.1LeetCode151 Reverse Words in a String1.2LeetCode557 Reverse Words in a String III1.3统计字符串字母&#xff0c;数字&#xff0c;空格和其他字符个数1.4把字符串转换成整数1.5回文字符串 2.整数2.1LeetCode7 Reverse Integer2.2判断一个数是不是质数…

算法基础---基础算法

文章目录 快速排序归并排序二分 整数二分浮点数二分高精度 高精度加法高精度减法高精度乘法高精度除法前缀和 一维前缀和二维前缀和差分 一维差分二维差分双指针位运算离散化区间合并 一、快速排序 思想&#xff1a;1.首先确定一个分界点&#xff08;随机取任意一点为…

算法基础知识总结(基础算法)

算法基础知识总结 Efficient procedures for solving problems on large inputs. 一、基础算法 1、快速排序 1、类别&#xff1a;快速排序是一种 交换排序&#xff0c;冒泡排序也是一种交换排序。 2、原理&#xff1a;将未排序的元素根据一个作为基准的主元&#xff08;Pi…

算法基础知识

一、算法的定义 算法&#xff1a;对特定问题求解步骤的一种描述&#xff0c;是为解决一个或一类问题给出的一个确定的、有限长的操作序列。 二、算法与程序的区别与联系 区别&#xff1a; 程序&#xff1a;与某种编程语言有关&#xff0c;能直接在机器上运行。 算法&#xff1a…

算法基础简介

一、什么是算法 在数学领域&#xff0c;算法是为了解决某一类问题的公式和思想。 在计算机领域&#xff0c;本质是一些计算机指令&#xff0c;解决特定运算和逻辑问题。 算法的掌握程度&#xff0c;一方面可以检验程序员对计算机底层的了解&#xff0c;一方面也…

Unity 移动的几种方法(从某一点移动到另外一点)

对于unity的几种移动方法,在以下给出整理 1 方向*速度 2 vector.lerp 与目标点永远不会重合 3 vector.MoveTowards 会与目标点重合 4 translate方法 纯移动 5 WASD键盘方法 刚 体方法 7.鼠标方法 捕鱼达人 8.射线方法(指哪到哪) Controll…

Unity 控制物体移动

目录 1、通过改变物体的位置使物体移动 2、通过给物体施加力使物体移动 3、移动characterController以及碰撞检测 一、相关代码展示 1、通过改变物体的位置使物体移动 using System.Collections; using System.Collections.Generic; using UnityEngine;public class move …

unity3D人物移动的实现(一)

人物的移动&#xff0c;首先要考虑人物与地面的碰撞&#xff0c;碰撞发生的条件是&#xff0c;两者必须都为碰撞体&#xff0c;且至少有一方为刚体&#xff0c;为了方便&#xff0c;我们就给人物加上刚体属性和碰撞体。 1首先是碰撞体属性&#xff0c;人形使用胶囊碰撞体&#…

Unity让物体跟随鼠标移动

前言 最近在学习Unity&#xff0c;记录下学习的成果吧。本文最终结果是要实现一个小飞机跟随鼠标移动的效果。看下图片。 向量 在Unity中&#xff0c;每个对象都有自己的位置属性&#xff0c;组件叫做Transform,通过Transform可以获取对象的位置属性。在上面的实例中&#…

01_Unity常用的移动方法

文章目录 基础框架匀速移动变速移动自定义变速运动总结&#xff1a; 在制作一款游戏的时候&#xff0c;经常需要对物体的位置进行移动&#xff0c;我们希望这个移动是具有多样性的&#xff0c;并且可操作的。 C#中提供了非常丰富的移动代码工具&#xff0c;通过这些工具我们可以…