海贼19-8班现场课
20566人加入学习
(0人评价)
编码能力训练课

0基础入门,突破欧拉50题

价格 ¥ 299.00
该课程属于 海贼19-8班(C++高薪班) 请加入后再学习

总结:

1、对于素数筛、线性筛,我们需要非常熟悉这些代码,然后知道该如何对其进行更改来解决相对应的问题。

2、这道题,似乎也可以改线性筛的代码来满足要求,但似乎写起来不如素数筛简单;

[展开全文]

总结

1、线性筛开两个数组,是很合适的方式,一个记录第几个素数,另一个可以判断是否为素数;

[展开全文]

总结:

1、二分查找也有递归写法,如果只需要返回True或者False的话。

2、我认为有一个更占用空间的写法,就是先减去二倍的平方数,再判断得到的数字是否是素数,这样会更简单也更节省时间,因为每个单词可以只需要判断1次if即可;目前,每个单词都需要被二分查找确认一次;不过确实解题思路有很多。

[展开全文]

总结:

1、素勾股数的算法·要记住!

    for (int n = 1; n <= 32; n++) {

        for (int m = n + 1; m <= 32; m++) {

            if (gcd(m, n) - 1) continue;                //gcd(m, n) - 1   对偶逻辑   gcd(m, n) != 1

            int a = m * m - n * n;

            int b = 2 * m * n;

            int c = m * m + n * n;

        }

    }

[展开全文]

总结:

1、memset,memcopy,memcmp都是按字节来批量修改,移动,比较的函数。

2、本来是一个开辟二维数组,然后挨个往里面存储;现在是开辟一个一维的指针数组,而储存每个计算后的数字的数组,在计算的时候再开辟出来,计算完以后返回数组地址即可。

3、这是暴力方法,还有整数素因子表示法,筛法思想等写法,可以提升效率;只需要比较素因子分解后,每个素数位上的幂次,是否相等即可,不需要计算出来,这应该要快得多,可能就是整数素因子表示法;

4、result[result_len++] = temp; //这样比用memcpy,memset更快;为什么呀?因为不用复制过去,已经用指针计算过一次了!

[展开全文]

总结:

1、memset是对较大的结构体或数组进行清零操作的一种最快方法!

void *memset(void *s, int ch, size_t n);

函数解释:将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。

memset:作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法 [1]  。

2、解题要抓住,最主要的判断函数,这里就是计算循环节的位数,并了解其特点,1/n的循环节不超过其所有余数可能性,因此不超过n-1;

[展开全文]

总结:

1、要把每个变量表示的语义信息,清晰地清楚地确认明白!有利于有许多变量进行复杂变化的过程。

例如get_num(int k, int n, int &x)函数中,涉及到

 

k                             表示还有多少个可能状态数需要跳跃?第一百万,还需要按顺序跳跃k = 999999个可能状态数;

n                            表示,我当前这个位置数字每向上变化一次,跳跃了多少个可能状态数:是一个阶乘数,fac[i - 1];

step = k / n            我利用当前这个位置数字,最多可以向上变化几次,来最大程度减少需要跳跃的可能状态数。(保证按顺序跳跃,维持字典序)

x                             表示当且这个位置上的为数字几,以及最后向上跳跃变化为了数字几?

i和num[i]                这个数字,有没有用过?(num[i] = 1则可用,= 0则不可用;只能用处于后面的数字,前面的数字都根据需要安排好了)

t                             用于遍历数字i,看看向上变化,还有哪些数字可以用,没用过,以及变化step次会停在哪个没有用过的数字i上;

2、为什么不是这里写地址&x,上面写指针*x呀?而是这里写变量x,上面写该变量的地址&x?对于更改x而言,是等价的写法吗?

int get_num(int k, int n, int &x)         //传一个“引用”进去?

......

        k = get_num(k, fac[i - 1], x)

 

 

int get_num(int k, int n, int *x) 

......

        k = get_num(k, fac[i - 1], &x)

 

#include<stdio.h>

void myswap1(int x, int y)

{

int t;

t=x;

x=y;

y=t;

}

 

void myswap2(int *p1, int *p2)

{

int t;

t=*p1;

*p1=*p2;

*p2=t;

}

 

void myswap3(int &x, int &y)

{

int t;

t=x;

x=y;

y=t;

}

 

int main()

{

int a,b;

a = 2;

b = 3;

myswap1(a,b); //作为对比,直接交换两个整数,显然不行

printf("调用交换函数后的结果是:%d 和 %d\n", a, b);

a = 2;

b = 3;

myswap2(&a,&b); //交换两个整数的地址

printf("调用交换函数后的结果是:%d 和 %d\n", a, b);

a = 2;

b = 3;

myswap3(a,b); //直接以变量a和b作为实参交换

printf("调用交换函数后的结果是:%d 和 %d\n", a, b);

 

return 0;

}

在第一个程序中,传值不成功的原因是指在形参上改变了数值,没有在实参上改变数值。

在第二个程序中,传地址成功的原因利用指针改变了原来的地址,所以实参就交换了。

在第三个程序中,引用是直接改变两个实参变量a,b的值,所以就交换了。

注:引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。

  引用的声明方法:类型标识符 &引用名=目标变量名;

  【例】:int a; int &ra=a; //定义引用ra,它是变量a的引用,即别名

  说明:

  (1)&在此不是求地址运算,而是起标识作用。

  (2)类型标识符是指目标变量的类型。

  (3)声明引用时,必须同时对其进行初始化。

  (4)引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。

   ra=1; 等价于 a=1;

  (5)声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。&ra与&a相等。

  (6)不能建立数组的引用。因为数组是一个由若干个元素所组成的集合,所以无法建立一个数组的别名。

[展开全文]

总结:

1、为什么第四步编写程序,也算一步?

分两种:

一种是正向递推,最后需要回溯回去;

第二种是逆向递推,也叫伪递推,这里体现的是就伪递推,从最后一层往第一层递推;

[展开全文]

总结:

1、记忆化搜索,需要开一个新的数组,然后在新的递归之前,直接返回累加值;

#define max_n 20

 

int val[max_n][max_n] = {0};

int keep[max_n][max_n] = {0};

 

int dfs(int i, int j, int n) {

    if (i + 1 ==n) return val[i][j];

    if (keep[i][j]) return keep[i][j];        //在新的递归开始前,直接返回累加值,正是这个返回记录值,减少了递归的次数。

    int val1 = dfs(i + 1, j, n);

    int val2 = dfs(i + 1, j + 1, n);

    return keep[i][j] = (val1 > val2 ? val1 : val2) + val[i][j];        //记忆化。

}

2、深度优先搜索的写法要记住,这是一种很简洁的写法。

#define max_n 20

 

int val[max_n][max_n] = {0};

 

int dfs(int i, int j, int n) {

    if (i + 1 == n) return val[i][j];

    int val1 = dfs(i + 1, j, n);

    int val2 = dfs(i + 1, j + 1, n);

    return (val1 > val2 ? val1 : val2) + val[i][j];

}

3、DFS(i, j, n)从位置(i,j)向下走n步的最大值;

[展开全文]

总结:

1、注意遍历上限是否可以降低,也可以提升效率

[展开全文]

总结:

1、大整数处理用数组,第一位的作用是记录该数字的总位数,可以用len = strlen(str)来提取;逐位相加,超过10,则进位;到最高位仍然超过10则位数+1;

2、注意三点:

①数组每一位就0-9,因此用开字符型数组,char str[]来接收大整数字符串!然后用整型数组,int ans[]来把大整数字符串变为大整数的整型串,从而得以计算加法;

②要注意一开始用读到的是最高位,因此要存放在str[len]上!然后持续往下存放!

②要注意把读到的数字字符转成数字:ans[len - i] = str[i] - '0';

[展开全文]

总结:

1、用到素数的时候,先把线性筛函数写出来,然后再根据题目需求进行相应的更改。

线性筛得记下来:

 

int prime[max_n + 5] = {0};

 

void init() {

    for (int i = 2; i <= max_n; i++) {

        if (!prime[i]) prime[++prime[0]] = i;

        for (int j = 1; j <= prime[0]; j++) {

            if (prime[j] * i > max_n) break;

            prime[prime[j] * i] = 1;

            if (i % prime[j] == 0) break;

        }

    }

    return ;

}

 

对于任意正整数N,设它的质因数分解为

(p1^n1)*(p2^n2)*......*(pk^nk),其中n1, n2, ..., nk > 0, p1, p2, ..., pk 为素数

它的所有约数个数为(n1 + 1) *(n2 + 1) *...... *(nk + 1),记作F(n)

若C=A*B,且A,B互素,那么C的约数个数F(C)为多少?

F(C) = F(A) × F(B) ——因为互素,所以没有相同的素因子,因此做笛卡尔积;

枚举n,则第n个三角形数的约数个数F(n)为

F(n) = F(n(n+1)/2) 

       = F(n/2) × F(n+1) 当n为偶数

       = F(n) × F((n+1))/2) 当n为奇数

 

用线性筛的代码框架,实现了求每个正整数的约数个数;

但是由于其中添加了一个while循环,破坏了整个代码的“优美性”;

有没有快速的方法,知道现在正整数最小素因子的幂次是多少?

有用记录式,而非计算式,这样就是稳稳地O(n)时间复杂度了。

开一个新的数组,记录最小的素因子的次幂,即可省略while来计算cnt了。

[展开全文]

总结:

1、算法很重要。素勾股数很重要;

 

找素勾股数,然后作为“勾股数的基底/秩”,来向上寻找a,b,c;

3 4 5,6 8 10, 9 12 15,......

可以枚举m和n,可以构造一组素勾股数

如果a b c三者互质(它们的最大公因数是1),他们就成为素勾股数:a^2 + b^2 = c^2

素勾股数具有如下性质:

性质1、他们的整数倍也是勾股数;

性质2、a b c之间两两互质

性质3、a、b必为一奇一偶

性质4、任何素勾股数均可表示为如下形式,其中n < m,且gcd(n, m) = 1

a = 2 * n * m

b = m^2 - n^2

c = m^2 + n^2

[展开全文]

总结:

1、二分查找的写法:函数指针,查找上界,查找值;

int binary_search(int (*func)(int), int n, int x)

 

int1 binary_search(int1 (*func)(int1), int1 n, int1 x) {

    int1 head =  1, tail = n, mid;

    while (head <= tail) {

        mid = (head + tail) >> 1;

        if (func(mid) == x) return mid;

        if (func(mid) < x) head = mid + 1;

        else tail = mid - 1;

    }

    return 0;

}

就是,第一个,差值为五边形数的一对五边形数

也可以解方程Pk - Pj = Pn

 

枚举到多少才能得到答案呢?可以找到这样一个枚举上界;

 

如何判断当前得到的和值或者差值,是五边形数?

方法一、如果我生成一个五边形数字的表, 把他放进去即可;那这个表需要设计成多大呢?

方法二、二分查找(算法时间复杂度O(logN);不仅可以用来在数组中查找元素,还可以用来求解单调函数的解。

[展开全文]

总结:

1、i×1,链接进ans;i×2,链接进ans;直到超过9位,则不再×n,不再链接进ans;

    while (digits(ans) < 9) {                            

        ans *= pow(10, digits(x * n));

        ans += n * x;

        n += 1;

    }

[展开全文]

总结

1、判断数字是否有重复,我们可以开一个数组,看数字对应下标位置是否为1,来判断;类似于标记合数;

这里有两处:

①判断a, b, a * b是否有重复数字:

    while (n) {

        if (num[n % 10]) return 0;                             

        num[n % 10] += 1;

        n /= 10;

    }

//长度为11的数组,每个数字挨个求10的模的时候,如果该对应下标位置上不为0,说明此前已经有余数为其赋值1了,因此这个数字重复了;

②判断a * b是否有重复数字:

int keep[max_n + 5] = {0}

 

            if (keep[a * b]) continue;

            sum += a * b;

            keep[a * b] = 1;

//长度为(a * b的上限+5)的数组,如果a * b位置上不为0,说明此前已经有a * b为其标记过了,因此这个a * b重复了;

2、求位数的函数,记得添加头文件,这个要记下来

#include <math.h>

 

int digits(int n) {

    if (!n) return 1;

    return floor(log10(n)) + 1;

}

3、求最大公约数的函数,也要记下来

#include <tinttypes.h>

 

int gcd(int32_t a, int32_t b) {

    if (!b) return a;

    return gcd(b, a % b);

}

4、和素数有关的一个整体解题结构大概是:

#include <stdio.h>

#include <int32_t.h>

#include <inttypes.h>

 

#define max_range ....

 

int32_t isPrime[max_range + 5] = {0};        //判断是否是素数

int32_t prime[max_range + 5] = {0};          //记录素数数组,用于遍历素数

 

void initprime(int n) {

    线性筛

    ......

}

 

void is_val(int n) {

    循环素数?;

    可截素数?;

    ......

}

 

void solve() {

    遍历素数数组:prime[i]

    判断是否is_val

    return 累加或者累乘的结果

}

 

int main() {

    init

    int many = solve()

    printf("%d\n", many);

    return 0;

}

遇到复杂的判定条件,或者遇到根据不同情形来计算的值,就声明一个函数来处理;

[展开全文]

总结:

1、降成一维的思路就是:

m元钱的组合种类为:

用0种 +(只用前1种且至少用了第1种货币)+(只用前2种且至少用了第2种货币)+(只用前3种且至少用了第3种货币)+ ... +(只用前8种且至少用了第8种货币)的总和;

他们之间是互斥的,因此把他们一层一层地计算,然后一层一层地累加起来即可,这样只需要一维数组。

f(n, m)        = f(n-1, m) + f(n, m - w[n])

f(n+1, m)    = f(n, m) + f(n+1, m - w[n+1])

f(n+1, m)    = f(n-1, m) + f(n, m - w[n]) + f(n+1, m - w[n+1])

f(n+1, m)    = f(n-2, m) + f(n-1, m - w[n-1]) + f(n, m - w[n]) + f(n+1, m - w[n+1])

f(n+1, m)    = ......

f(n+1, m)    = f(0, m) + f(0, m - w[0]) + f(1, m - w[1]) + ... + f(n, m - w[n]) + f(n+1, m - w[n+1])

2、当写递推算法出错的时候,要注意查看数组下标的合法性,看下标是否超过了数组的大小,判断下标的边界条件;

 

其他:

这是一个递推算法;

f(n, m)表示前n种钱币拼凑m元钱的所有不同的情况总数;

f(n, m) = f(n-1, m) + f(n, m - w[n]),其中w[n]位第n元钱的面值大小

容斥原理(并集算法)

A+B-A∩C

 

f(n, 0)为多少?f(n, 0) = 1

我们考虑以下数值及其公式:

f(1, 1) = f(0, 1) + f(1, 0) = 0 + 1

f(2, 2) = f(1, 2) + f(2, 0) = 1 + 1

f(3, 5) = f(2, 5) + f(3, 0) = 3 + 1

f(4, 10) = f(3, 10) + f(4, 0) = (1+3+4) + 1

以此类推,因此f(n, 0) = 1

此时,开[8][200]的数组,即可。

 

 

真的需要开那么多项吗?

像斐波那契用滚动数组,只需要开3项,甚至2项也足以;

这里也可以相应地进行修改。

因为,只用了这一层上一个数和上一层同样位置的数:

i = 1,从1到200标记完

i = 2,从1到200标记完

i = 3,从1到200标记完

i = 4,从1到200标记完

i = 2,从1到200标记完

...

i = 8,从1到200标记完

这样就可以把8项(8种币值的货币)缩小成2项,即可滚动;

[展开全文]

总结:

1、这里用一个二维数组,把位数,分配到二维数组的第二维度上,按位写了一个数学加法运算式;

2、用[0]位,表示有多少位;用[1], [2], [3]来表示,第1位的数字,第2位的数字,第3位的数字;

3、整个过程:999 + 999= 9|9|9 + 9|9|9= 18|18|18 = 18|19|8 = 19|9|8 = 1|9|9|8

4、用指针变量取滚动数组的地址;当n增长的时候,不断地按照012012012的顺序,不断替换*a, *b, *c所对应的地址,来更新下一个位置上的值。

5、不要忽略中间a[0] = b[0];的更新位数的操作;

[展开全文]

总结:

1、写代码,除了掌握基本的算法、代码工地,还要对操作系统,计算机有深刻了解,要知道计算机是如何执行的。

2、搜索算法中经常用到记忆化搜索;

int keep[max_n + 5] = {0};

......

    if (n <= size && keep[n]) return keep [n];        //这里n <= max_n && keep[n]不能写反了,写成keep[n] && n <= max_n

3、size实验中了解到,记忆化要考虑缓存到底能存多大的内容;

 

size                        real

100                        0m1.153s

1000                      0m0.652s  

1000000                0m0.047s

10000000              0m0.094s

这和计算机底层有关

缓存有三个等级:

L1,速度最快,容量最小

L2,速度居中,容量居中

L3,速度最慢,容量最大,最多也就8MB

先从内存读到缓存当中,当前最大的缓存是L3级的,但是也很小,大概也是8MB左右,相当于八百万个字节;

 

要涉及到页面置换的问题,有可能加载进来的数据,没有当前要找的值,因此要换一页,要分页进行加载,还有命中率的问题。

因此,记忆化,要考虑缓存到底能存多大的内容。

 

写好代码,要做哪些

1、算法,代码功底

2、要对操作系统,计算机有深刻了解,要知道计算机是如何执行的;

要衡量一天能准确地有价值地完成的代码量的多少;

[展开全文]

总结:

1、爆栈的时候,当前的数字n如果超过了int的数值表示范围,会发生什么?首先,数值会一直增加,然后增加到一个负值,最后爆栈了;可以用

if (n < 0) printf("erro\n");

来检查;最后将整型修改为long long

[展开全文]

授课教师

高级算法研发工程师
C++算法工程师

课程特色

视频(41)
文档(51)
图文(71)

学员动态

70648ae6d9fbd0f7 加入学习
coffee0906 加入学习
宋浩 开始学习 03-PPT资料
宋浩 开始学习 02-PPT资料