算法 64式 4、回溯算法整理__第1部分_1到13题
1算法思想
回溯
1.1含义
以深度优先方式搜索问题解的算法称为回溯法。
1.2思想
按照深度优先搜索策略,从根节点出发搜索解空间树,如果某结点不包含问题解,则逐层向祖先结点回溯;否则进入子树。
1.3特点
深度优先搜索+递归前设置变量,递归后清除变量设置
1.4适用
适合求取问题所有解类型的题目。例如求出问题的多少种组合。
1.5通用解法
回溯算法:
如果当前是问题的解,打印解;
设置变量;
递归;
清除变量设置。
1.6经典例题讲解
n皇后问题
在n×n格的国际象棋上摆放n个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
代码如下:
void place(int pos , vector<int>& result , vector< vector<int> >& results , int n) { if(pos == n) //如果当前是问题的解,打印解 { results.push_back(result); return; } //从中选出任意一个i值作为result[pos]摆放,作为第pos行的列 for(int i = 0 ; i < n ; i++) { bool isOk = true; for(int j = 0 ; j < pos ; j++) { //如果在同一列,说明不符合, if(result.at(j) == i) { isOk = false; break; } //如果在同一对角线 if( abs( result.at(j) - i ) == abs(j - pos) ) { isOk = false; break; } } if(isOk) { result.push_back(i); //设置变量 place(pos + 1 , result , results , n); //递归 result.pop_back(); //清除变量设置 } } } |
1.7回溯与递归的区别
回溯通常也用到递归,递归往往求取的是一个解,回溯通常求取的是多个解。回溯的过程中需要对标记量进行设置,递归完成后,需要对标记量清除设置。
2回溯系列
类别-编号 |
题目 |
遁去的1 |
1 |
Prime ring problem 在给定的1到n的数字中,将数字填入环中,使得环中任意2个相邻的数字和为素数。按字典序输出所有符合条件的解 |
计算机考研—机试指南 https://blog.csdn.net/qingyuanluofeng/article/details/47186279 关键: 1 对于其余情况,首先要判断当前的数字是否在环中,若不在环中,先标记为已用,并尝试设置第num+1个数字在ans数组中,然后继续尝试放入下一个数字,最后再将该数字 重新标记为未使用 2 注意剪枝的基本过程:初始化剪枝标记,初始化节点,放入循环中 //用于判定两个数的和是否为素数。 int prime[13] = {2,3,5,7,11,13,17,19,23,29,31,37,41}; //用于存放所有数的和 int ans[N]; bool mark[N];//剪枝标记 //判断一个数是否为素数 bool judge(int x) { //判断一个数是否为素数 for(int i = 0 ; i < 13 ; i++) { if(x==prime[i]) { return true; } } return false; } //判断最后一个数和第一个数是之和是否为质数,若是,输出答案 bool check(int n) { if(judge(ans[n] + ans[1])==false) { return false; } else { for(int i = 1 ; i <= n ; i++) { if(i!=1) { printf(" %d",ans[i]); } else { printf("%d",ans[i]); } } printf("\n"); return true; } } //num:ans数组中已经存入的数字,n表示总共输入的数字个数 void DFS(int num,int n) { //如果已经存在大于一个数字,就开始检查 if(num > 1) { if(judge(ans[num] + ans[num-1])==false) { return; } } //如果所有数字全部确定完毕,需要检查第一个数和第n个数的和是否为质数 if(num==n) { check(n); //检查结束之后就返回 return; } //对第2个数到第n个数进行判定,递归判定 for(int i = 2 ; i <= n ; i++) { //如果当前数未被尝试 if(false==mark[i]) { mark[i] = true;//标记该数已经被尝试 ans[num+1] = i;//将新的数字加入到数组中 DFS(num+1,n);//尝试新的数字 mark[i] = false;//如果尝试失败,将当前数字重新设置为未被访问 } } } |
2 |
货郎担问题 四个顶点的货郎担问题。求从顶点1出发,最后回到顶点1的最短路线。 输入: 4 0 0 1 7 8 0 5 1 7 2 0 1 2 5 3 0 输出: 1 3 2 4 1 6 |
算法设计与分析 https://blog.csdn.net/qingyuanluofeng/article/details/47189381 v1 v2 v3 v4 v1 无穷 无穷 1 7 v2 8 无穷 5 1 v3 7 2 无穷 1 v4 2 5 3 无穷 算法分析: 因为是采用回溯法来做,肯定是递归,然后还需要现场清理。 要设置一个二维数组来标识矩阵内容,然后回溯还需要设计 一个二维标记数组来剪枝,设定一个目标变量,初始为无穷大, 后续如果有比目标变量值小的就更新。剪枝的条件就是如果走到当前节点的耗费值>=目标变量,就直接不再往下面走, 向上走。 深度优先 = 递归 递归基:如果到达叶子节点的上一个节点,那么就进行是否更新的判断 递归步:如果没有到达叶子节点,就进行剪枝操作,判断能否进入下一个节点,如果能,更新最优值 关键: 1 //递归基:如果已经遍历到叶子节点的上一层节点,i标识递归深度 if(i == g_n) { //判断累加和是否超过最大值,如果有0,应该排除;满足这个条件,才打印 if((g_iArr[pArr[i-1]][pArr[i]] != 0) && (g_iArr[pArr[g_n]][1] != 0) && (g_iCurResult + g_iArr[pArr[i-1]][pArr[i]] + g_iArr[pArr[g_n]][1] < g_iResult )) { g_iResult = g_iCurResult + g_iArr[pArr[i-1]][pArr[i]] + g_iArr[pArr[g_n]][1]; //用当前最优路径去更新最优路径,防止下一次没有 for(int k = 1 ; k <= g_n ; k++) { g_iBestPath[k] = pArr[k]; 2 //递归步:判断能否进入子树,需要尝试每一个节点 else { //尝试不同的组合 for(int j = i ; j <= g_n ; j++) { //判断能否进入子树:如果当前值+下一个连线值的和 < 最优值,就进入,0要pass if( (g_iArr[pArr[i-1]][pArr[j]] != 0) && (g_iCurResult + g_iArr[ pArr[i-1] ][ pArr[j] ] < g_iResult) ) 3 //交换i与j,则i为当前可以尝试的范围 //为完成后面k个元素的排列,逐一对数组第n-k~n个元素互换。数组第一个元素为1,生成后面n-1个元素的排列 //数组第一个元素与第二个元素互换,第一个元素为2,第2个元素为1,生成后面的n-1个元素的排列... swap(&pArr[i],&pArr[j]); //更新当前累加值,是i-1与i的 g_iCurResult += g_iArr[ pArr[i-1] ][ pArr[i] ]; //递归 backTrace(i+1,pArr); //回溯,清空累加值;能够走到这里,说明上述结果不是最优解,需要向求解树上一层回退 g_iCurResult -= g_iArr[pArr[i-1]][ pArr[i] ]; swap(&pArr[i],&pArr[j]); 代码: const int MAXSIZE = 100; const int MAX = 1000000000; int g_iArr[MAXSIZE][MAXSIZE];//邻接矩阵 int g_iResult;//存放最优解 int g_iPath[MAXSIZE];//存放最优路径上 int g_n;//元素个数 int g_iCurResult;//当前累加路径和 int g_iBestPath[MAXSIZE];//还需要设置一个数组,用来保存最优解 //可以做成字符串全排列的性质track(int i,int* pArr,int* pResult),其中pArr是用于存放最优解的路径 void backTrace(int i,int* pArr) { //递归基:如果已经遍历到叶子节点的上一层节点 if(i == g_n) { //判断累加和是否超过最大值,如果有0,应该排除;满足这个条件,才打印 if((g_iArr[pArr[i-1]][pArr[i]] != 0) && (g_iArr[pArr[g_n]][1] != 0) && (g_iCurResult + g_iArr[pArr[i-1]][pArr[i]] + g_iArr[pArr[g_n]][1] < g_iResult )) { g_iResult = g_iCurResult + g_iArr[pArr[i-1]][pArr[i]] + g_iArr[pArr[g_n]][1]; //用当前最优路径去更新最优路径,防止下一次没有 for(int k = 1 ; k <= g_n ; k++) { g_iBestPath[k] = pArr[k]; } } } //递归步:判断能否进入子树,需要尝试每一个节点 else { //尝试不同的组合 for(int j = i ; j <= g_n ; j++) { //判断能否进入子树:如果当前值+下一个连线值的和 < 最优值,就进入,0要pass if( (g_iArr[pArr[i-1]][pArr[j]] != 0) && (g_iCurResult + g_iArr[ pArr[i-1] ][ pArr[j] ] < g_iResult) ) { //交换i与j,则i为当前可以尝试的范围 //为完成后面k个元素的排列,逐一对数组第n-k~n个元素互换。数组第一个元素为1,生成后面n-1个元素的排列 //数组第一个元素与第二个元素互换,第一个元素为2,第2个元素为1,生成后面的n-1个元素的排列... swap(&pArr[i],&pArr[j]); //更新当前累加值,是i-1与i的 g_iCurResult += g_iArr[ pArr[i-1] ][ pArr[i] ]; //递归 backTrace(i+1,pArr); //回溯,清空累加值;能够走到这里,说明上述结果不是最优解,需要向求解树上一层回退 g_iCurResult -= g_iArr[pArr[i-1]][ pArr[i] ]; swap(&pArr[i],&pArr[j]); } } } } |
3 |
n皇后问题 在n×n格的国际象棋上摆放n个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 |
算法设计与分析 https://blog.csdn.net/qingyuanluofeng/article/details/47189389 //判断当前列能否摆放成功。判断存不存在相同列或者在同一个斜线上 bool place(int x[] , int k) { for(int i = 1 ; i < k ; i++) { if(x[i] == x[k] || abs(x[i] - x[k]) == abs(i - k)) { return false; } } return true; } int backTrace(int x[], int n) { int k = 1 ; x[1] = 0; while(k > 0) { x[k] = x[k] + 1; //如果不能摆放,尝试下一个列 while(x[k] <= n && !place(x , k)) { x[k] = x[k] + 1; } //如果存在满足条件的列 if(x[k] <= n) { //如果所有皇后摆放完毕,输出结果 if(k == n) { break; } //继续处理下一个皇后,当前列摆放正确。处理下一个皇后,把其放在0列作为初始化 else { k++; x[k] = 0; } } //如果不满足,回溯。先置当前列的值为初始值,回溯到前一行 else { x[k] = 0; k--; } } } |
4 |
图的着色问题 给定无向图G(V,E),用m种颜色为图中每个顶点着色,要求每个顶点着一种颜色,并且使得相邻两个顶点之间的颜色不同 |
算法设计与分析 https://blog.csdn.net/qingyuanluofeng/article/details/47189399 分析: 用一个n元组描述图的一种颜色(x1,x2,...,xn),xi属于[1,m],1<=i<=n 为了用m种颜色对n个顶点着色,就有m^n中可能颜色组合,状态空间树是高度为n 的完全m叉树 约束方程: x[i] != x[j],若顶点i与顶点j相邻 <=>a[i][j] = 1 算法分析: 使用一个n元的一维数组 1判断当前元素是否与前面的元素存在重复 2如果颜色个数超出限制,那么使颜色的使用颜色为0,然后同时 关键: 1 //为颜色赋初始值,后面会累加到1,表示采用1号颜色 for(int i = 0 ; i < n ; i++) { pResult[i] = 0; 2 //检测当前节点是否与之前出现的节点颜色相同;若相同,则累加颜色数,并且是作为循环判断的,因为不行的话,肯定要与下面的其他颜色进行比较, //直至与其他颜色没有冲突为止 while(pResult[k] <= m && isSameColor(k,iArr,pResult)) { pResult[k]++; 3 //判断是否到循环出口,如果颜色数没有超过,说明是因为两个颜色不同而退出 if(pResult[k] <= m) { //如果k=n-1表示全部遍历结束,退出 if(k == n-1) { break; } //否则遍历下一个节点 else { k++; 4 //如果是由于颜色数超过最大值 else { //因为当前节点所有颜色均试过不通过后,重新置为0,清理现场,并回溯到其上一个节点,让其颜色进行累加,经典 pResult[k] = 0; //回到其父节点,通过改变父节点的颜色,来使子节点的颜色可以调整 k--; 代码: bool isSameColor(int k,int iArr[MAXSIZE][MAXSIZE],int* pResult) { for(int i = 0 ; i < k ; i++) { //检测当前节点是否相邻且颜色相同 if(iArr[i][k] && pResult[i] == pResult[k]) { return true; } } return false; } void dfs(int n,int m,int iArr[MAXSIZE][MAXSIZE],int* pResult) { //为颜色付初始值,后面会累加到1,表示采用1号颜色 for(int i = 0 ; i < n ; i++) { pResult[i] = 0; } //如果颜色pResult[i]:表示第i号节点使用何种颜色 int k = 0; while(k >= 0) { //使颜色标识累加 pResult[k]++; //检测当前节点是否与之前出现的节点颜色相同;若相同,则累加颜色数,并且是作为循环判断的,因为不行的话,肯定要与下面的其他颜色进行比较, //直至与其他颜色没有冲突为止 while(pResult[k] <= m && isSameColor(k,iArr,pResult)) { pResult[k]++; } //判断是否到循环出口,如果颜色数没有超过,说明是因为两个颜色不同而退出 if(pResult[k] <= m) { //如果k=n-1表示全部遍历结束,退出 if(k == n-1) { break; } //否则遍历下一个节点 else { k++; } } //如果是由于颜色数超过最大值 else { //因为当前节点所有颜色均试过不通过后,重新置为0,清理现场,并回溯到其上一个节点,让其颜色进行累加,经典 pResult[k] = 0; //回到其父节点,通过改变父节点的颜色,来使子节点的颜色可以调整 k--; } } } |
5 |
马的遍历问题 在n*m的棋盘中,马只能走“日” 字。马从位置(x,y)处出发,把棋盘的每一格都走一次,且只走一次。找出所有路径。 |
算法设计与分析 https://blog.csdn.net/qingyuanluofeng/article/details/47189427 问题分析: 1问题解的搜索空间? 棋盘的规模是n*m,是指行有n条边,列有m条边 马在棋盘的点上走,所以搜索控件是整个棋盘上的n*m个点 用n*m的二维数组记录马行走的过程,初值为0标识未经过。 2在寻找路径过程中,活结点的扩展规则? 对于棋盘上任意一点A(x,y),有8个扩展方向: A(x+1,y+2),A(x+1,y-2) A(x+2,y+1),A(x+2,y-1) A(x-1,y-2),A(x-1,y+2) A(x-2,y-1),A(x-2,y+1) 用数组fx[8] = {1,1,2,2,-1,-1,-2,-2}, fy[8]={2,-2,1,-1,-2,2,-1,1}来模拟马走日时下表的变化过程 问题3:扩展的约束条件? 1)不出边界 2)每个点只经过一次 棋盘点对应的数组元素初值为0,对走过的棋盘点的值置为所有步数 起点存储1,重点存储n*m 函数check,检查当前状态是否合理 问题4:搜索解空间? 搜索过程是从任一点(x,y)出发,按照深度优先原则,从8个方向 尝试一个可以走的棋盘点,直到走过棋盘上所有n*m个点。递归算法 注意要求找出全部可能的解,注意回溯过程的清理现场工作,也就是 置当前位置为未经过 数据结构设计: 1)用一个变量dep记录递归深度=走过的点数,当dep=n*m时,找到一族解 2)用n*m的二维数组记录马行走的过程,初始值为0标识未经过。搜索完毕后, 起点存储的是1,终点存储的是n*m const int MAXSIZE = 100; int g_count; //方向数组 //int g_x[8] = {1,1,2,2,-1,-1,-2,-2}; //int g_y[8] = {2,-2,1,-1,-2,2,-1,1}; int g_x[8]={1,2,2,1,-1,-2,-2,-1}; int g_y[8]= {2,1,-1,-2,-2,-1,1,2}; //记录所走的顺序 int g_path[MAXSIZE][MAXSIZE]; //已经拥有深度,同时兼顾了计数的功能,因此,无需再进行计数 void dfs(int x,int y,int iDepth,int n,int m) { int xx,yy; //迷宫算法典型步骤,一上来就是方向的扩展 for(int i = 0 ; i < 8 ; i++) { //获取新方向 xx = x + g_x[i]; yy = y + g_y[i]; //剪枝:是否越界 if(xx < 0 || xx >= n || yy < 0 || yy >= m) { continue; } //剪枝:是否访问过 if(g_path[xx][yy] != 0) { continue; } //设定新方向的存储值 g_path[xx][yy] = iDepth; //判断是否达到递归出口 if(iDepth == n*m) { //return ; //注意,这里要走出所有路径,因此不能返回,而是输出 print(n,m); } //如果没有到达递归出口 else { //递归 dfs(xx,yy,iDepth+1,n,m); //置已访问标记为未访问 //g_path[xx][yy] = 0; } } //置已访问标记为未访问 g_path[xx][yy] = 0; //iDepth--; } |
6 |
找n个数中r个数的组合 例如:当 n=5, r=3 时 , 所有组合为: 1 2 3 1 2 4 1 2 5 1 3 4 1 3 5 1 4 5 2 3 4 2 3 5 2 4 5 3 4 5 total=10 { 组合数 } 输入: 5 3 输出: 10 5 4 3 5 4 2 5 4 1 5 3 2 5 3 1 5 2 1 4 3 2 4 3 1 4 2 1 3 2 1 |
算法设计与分析 https://blog.csdn.net/qingyuanluofeng/article/details/47189433 分析1: 每组3个数的特点: 1)互不相同; 2)前面的数小于后面的数; 将上述两条作为约束条件。 3) 当 r =3时,可用三重循环对 每组中的3个数进行 枚举 。 用递归法设计该问题: 每个组合中的数据必须从大到小排列,因为递归算法设计是要找出大规模问题与 小规模问题之间的关系 分析2: 分析n=5,r=3时10个组合数 1)首先固定第一个数5,然后就是n=4,r=2的组合数,共6个组合 2)其次固定第一个数4,其后就是n=3,r=2的组合数,共3个 3)最够固定第一个数3,后面就是n=2,r=2的组合数,共1个 至此,找到了“5个数中3个数的组合”与"4个数中2个数的组合" ,3个数中2个数的组合,2个数中2个数的组合的递归关系 递归算法的三个步骤: 1)n个数中r个数组合递推到n-1个数r-1个数有组合,n-2个数中r-1个数有组合 ,...,r-1个数中有r-1个数有组合,共n-(r-1)次递归 2)递归地边界条件是r=1 3)函数主要操作是输出,每当递归到r=1时,就有一个新的组合产生,输出他们和 一个换行符, 先固定5,然后进行多次递归,数字5要多次输出,所以要用数组存储以备每次 递归到r=l时输出。 同样每次向下递归都要用到数组,所以将数组设置为全局变量 const int MAXSIZE = 100; int g_iArr[MAXSIZE]; bool isOk() { int iTemp; int iNext; int iCur = 100000; for(int j = g_iArr[0]; j >= 1; j--) { iNext = g_iArr[j];//第一个数 //判断前面的数是否比自己大,正常的顺序应该是前面大,后面小,如果不符合就是前面<=后面。现在拿到的第一个是最前面的 if( iCur <= iNext) { return false; } iCur = iNext; } return true; } void dfs(int n,int r) { for(int i = n ; i >= r ; i--) { //设定当前选中的元素为第一个,因为元素的选取是从大到小,因此为i g_iArr[r] = i; //递归基,当r减为1,说明成功了 if(r == 1) { if(isOk()) { for(int j = g_iArr[0]; j >= 1; j--) { cout << g_iArr[j]; } cout << endl; } } //递归步,从剩余n-1个数中挑选r-1个数 else { //添加限制条件,后面的大于前面,不能重复 dfs(n-1,r-1); } } } |
7 |
构造高精度数据: 构造一个尽可能大的数,使其从高到低前一位能被一整除,前2位能被2整除,...,前n位能被n整除 输出: 447204888644483284 |
算法设计与分析 https://blog.csdn.net/qingyuanluofeng/article/details/47189449 分析: 解空间:记高精度数据为a1a2...an, 则(a1*10^(n-1) + a2*10^(n-2) + ... + an)整除n 如何搜索? 从高位到低维逐位尝试,失败回溯的算法。算法的首位从1开始枚举,以后各位从0开始枚举。生成的高精度数据用数组从高位到低位 存储,一号元素存储最高位。 如何确定和保留最优解? 用数组A作为当前求解的高精度数据的暂存处。数组B为当前最大的满足条件的数。求解出的满足条件的数之间只需要比较位数就能确定大小。 n为当前满足条件的最大数据的位数,当i > n时,位数多数据一定大;i = n时,由于搜索是由小到大进行的,位数相等时后来满足条件的 数据一定比前面的大。 关键: 1 设置数组首位为1 A[1] = 1; 2 循环退出条件为A[1] > 9 ,while(A[1] <= 9)//因为最后会回溯到A[1],而A[1]的值最多为9 3 当 i >= n时,更新数据 if(i >= n)//寻找到更大的数据,将临时数组中的数据保存到最终数据中 { n = i ;//更新n 4 检查第i位是否符合条件 r = r % i;//获取第i位的余数 if(r != 0)//第i位失败 5 构造第i位可能的解 //构造第i位的可能解,也就是加上i-r,构成i缺少的部分 A[i] = A[i] + i - r; 6 确定第i位是否需要回溯,是当其值大于9,并且位数大于1 while(A[i] > 9 && i > 1)//搜索到第i位的解,回溯到前一位 7 回溯的做法是:另当前位为0,A[i] = 0 , 再减小位数;i--,另上一位可能解为:A[i] = A[i] + i; A[i] = 0;//置当前位为0 i = i - 1; A[i] = A[i] + i;//?尝试前一位,加i的原因是,在前一位的基础上加i,上一位必定能被i整除。 代码: void num() { //设置数组,用于暂存 int A[101] , B[101]; //初始化数组 memset(A, 0 , sizeof(A)); memset(B, -1 , sizeof(B)); //设置暂存数组的首位为1 A[1] = 1; int i = 1; int n = 1; while(A[1] <= 9)//因为最后会回溯到A[1],而A[1]的值最多为9 { if(i >= n)//寻找到更大的数据,将临时数组中的数据保存到最终数据中 { n = i ;//更新n for(int k = 1 ; k <= n ; k++) { B[k] = A[k]; } } i = i + 1; int r = 0; for(int j = 1 ; j <= i ; j++)//检查第i位是否符合条件 { r = r * 10 + A[j]; } r = r % i;//获取第i位的余数 if(r != 0)//第i位失败 { //构造第i位的可能解,也就是加上i-r,构成i缺少的部分 A[i] = A[i] + i - r; while(A[i] > 9 && i > 1)//搜索到第i位的解,回溯到前一位 { A[i] = 0;//置当前位为0 i = i - 1; A[i] = A[i] + i;//?尝试前一位,加i的原因是,在前一位的基础上加i,上一位必定能被i整除。 } } } for(int i = 1 ; i < 101 ; i++) { if(B[i] == -1) { break; } cout << B[i] ; } cout << endl; } |
8 |
流水作业车间调度: n个作业要在两台机器M1和M2组成的流水线上完成加工。每个作业加工的顺序都是现在M1上加工,然后在 M2上加工。M1和M2加工作业i所需的时间分别为ai和bi。流水作业调度问题要求确定这n个作业的最优 加工顺序,使得从第一个作业在机器M1上开始加工,到最后一个作业在机器M2上加工完成所需的时间 最少。作业在机器M1和M2的加工顺序相同。 输入: 3(作业数) 2 1 3 1 2 3 输出: 8 |
算法设计与分析 https://blog.csdn.net/qingyuanluofeng/article/details/47189475 分析: 类似字符串全排列的回溯算法结构。固定第一位,生成后续排列, 然后交换第一位与后面某一位,固定第一位,生成后续排列。 解空间:是排列树,用数组x[](初值为1,2,3,...,n)模拟不同排列 如何计算完成时间? 对机器M1进行加工,加工时间f1是固定的。f1[i] = f1[i-1] + M1[x[i]] 对机器M2加工分为两种情况 1)有空闲 ##a1........##a2...........## ##b1.... ##b2.........## 空闲时,f2[i] = f1[i] + M2[x[i]] 2)积压时 ##a1........##a2...........## ##b1..................##b2.........## 空闲时,f2[i] = f2[i-1] + M2[x[i]] 如何获取最优解? 最优调度应使及其M1没有空闲时间,机器M2空闲时间最少。 在搜索排列树的同时,不断更新最优解,最后找到问题的最优解。 搜索过程中,当某一排列的前几步的加工时间已经大于当前的最小值,就无需搜索计算。 数据结构设计: 二维数组job[100][2]存储作业在M1,M2的加工时间。 由于f1在计算中,只需当前值,用变量存储即可;f2在计算时,还依赖前一个作业的数据, 所以用数组存储。 变量f存储当前加工需要的全部时间。 算法步骤: 1 如果搜索到叶节点(必然是最优的),得到调度方案,更新 2 对当前作业直到最后作业做迭代 3 计算机器1工作时间 , 判断机器2处于积压或者空闲 并更新f2 4 更新直到当前结点的累加值 5 若累加值小于最优解,则进入该结点的叶子结点,交换两个作业,回溯下一个结点,再交换回来 6 将f1设置为回溯前的值,将累加值还原为回溯前的值 7 这是排列树问题,初始化应该给与一个排列 //这是回溯中的排列树问题:需要赋予一个初始排列。排列树: //选取的n个元素满足某种排列性质,时间复杂度为O(n!) for(int k = 1 ; k <= n ; k++) { //设置一个初始调度为调度作业1,2,...,n的顺序 x[k] = k; } 8 初始最优值应该置为无穷大 bestf = 1000000000;//易错,最优值应该初始化为无穷大 代码: int n;//作业数 int bestf;//存放最优调度时间 int f1;//机器1的调用时间 int f2[100];//机器2的调用时间 int f;//直至当前结点的机器调用累加时间 int job[100][3];//作业的运行时间 int x[100];//易错,当前调度 int bestx[100];//易错,存放当前作业最佳调度 void backTrace(int i) { if(i == n + 1)//如果到达叶子结点,则说明产生了调度方案,更新 { for(int j = 1 ; j <= n ; j++) { bestx[j] = x[j]; } bestf = f; } else { //回溯的标准格式 for(int j = i ; j <= n ; j++) { f1 = f1 + job[ x[j] ][1]; //如果机器2存在积压 if(f2[i-1] > f1) { f2[i] = f2[i-1] + job[ x[j] ][2]; } else { f2[i] = f1 + job[ x[j] ][2]; } f = f + f2[i]; //判断累加和是否小于最优调度值,是就进行继续尝试下一个结点 if(f < bestf) { swap(&x[i] , &x[j]); backTrace(i+1); swap(&x[i] , &x[j]); } //清理现场 f1 = f1 - job[ x[j] ][1]; f = f - f2[i]; } } } void process() { while(cin >> n) { //输入 memset(job , 0 , sizeof(job)); for(int i = 1 ; i <= n ; i++) { for(int j = 1 ; j <= 2 ; j++) { cin >> job[i][j]; } } //计算 f = 0; f1 = 0; bestf = 1000000000;//易错,最优值应该初始化为无穷大 memset(f2 , 0 , sizeof(f2)); //易错,这是回溯中的排列树问题:需要赋予一个初始排列。排列树:选取的n个元素满足某种排列性质,时间复杂度为O(n!) for(int k = 1 ; k <= n ; k++) { //设置一个初始调度为调度作业1,2,...,n的顺序 x[k] = k; } backTrace(1); cout << f2[n] << endl; } } |
9 |
连续邮资问题: 假设国家发行了n种不同面值的邮票,并且规定每张信封上最多只允许贴m张邮票。连续邮资问题要求对于给定的n和m的值,给出邮票面值的最佳设计,在一张信封上可以贴出从邮资1开始,增量为1的最大连续邮资区间。 输入: 2(邮票的种类数) 3(允许张贴的邮票数) 2 3 5 4 输出: 7 1 3 70 1 3 11 15 32 |
算法设计与分析 https://blog.csdn.net/qingyuanluofeng/article/details/47189483 举例分析: 当n=2,m=3时,如果面值分别为1和4,则可以获得的邮资范围为1~6 加上 8 , 9 , 12 如果过面值为1,3,则可以获得1~7之间的每个邮资值,并且7就是可以得到的连续的邮资最大值 问题分析: 寻找子集就是子集树 寻找元素排列就是排列树 这是子集树问题,因为要求一个集合S,集合S中的元素满足某种性质。是从n个元素的集合S中,找出S满足某种性质的子集 思路:搜索可行解 解向量:用n元组x[1:n]表示n种不同邮票面值,从小到大排列 约束函数:若选定x[1:i-1],并且取值范围为:1~r,那么x[i]取值范围为x[i-1]+1~r+1 如何确定r的值? 计算x[1:1]的最大连续邮资区间时,直接递归复杂度较高。 尝试计算用不超过m张面值为x[1:i]贴出邮资k所需的最少邮票数为y[k],通过y[k]可以推算出r(最大连续邮资)的值, y[k]可以通过递推在O(n)时间内解决 不懂 算法步骤: 1 初始化数组 2 计算任意邮资需要的最少张数 3 判断是否越界,并更新最优值 4 拷贝数组,对x[i]范围的x[i-1]+1 到 r,更新当前解,递归调用 , 回溯 代码 int n ,m ; //邮票种类数,邮票允许的张帖数 int x[100]; //当前解 int bestx[100];//当前最优解 int y[10000]; //贴出各种邮资所需要的最少邮票数 int maxint; //大整数 int maxl; //邮资上界 int maxvalue; //当前最优值 void backTrace(int i ,int r) { //递推求解任意邮资所需要的最少邮资数 int z[10000]; for(int j = 0 ; j <= x[i-2]*(m-1) ; j++)//这里是x-2 { //如果小于最多邮资数 if(y[j] < m) { for(int k = 1 ; k <= m - y[j] ; k++) { //更新邮资所需的最少张数 if(y[j] + k < y[j + x[i-1] * k] ) { y[j + x[i-1]* k ] = y[j] + k; } } } } //更新最大连续邮资值 while(y[r] < maxint ) { r++; } //判断是否搜索到最优解 if(i > n) { //判断是否超过最优解 if(r - 1 > maxvalue)//?r - 1 { maxvalue = r - 1; //更新最优解 for(int p = 1 ; p <= n ; p++) { bestx[p] = x[p]; } return ;//易错,直接退出 } } //拷贝邮资最少张数数组,用于回溯,对于每个x[i]的可能值进行赋值并求解 for(int k = 1 ; k <= maxl; k++) { z[k] = y[k]; } for(int j = x[i-1] + 1 ; j <= r ; j++) { x[i] = j ; backTrace( i+1 , r);//递归下一个编号 //回溯 for(int k = 1 ; k <= maxl; k++) { y[k] = z[k]; } } } void process() { while(cin >> n >> m) { maxint = 32767; maxl = 1500; maxvalue = 0; //初始化解 memset(x , 0 , sizeof(x)); //初始化邮资所需要的最少张数 for(int i = 1 ; i <= maxl ; i++) { y[i] = maxint; } y[0] = 0; x[1] = 1;//第一个邮票面值必须为1 backTrace(2,1);//第一个参数是邮票编号,第二个参数是最大连续邮资 cout << maxvalue; } } |
10 |
24点游戏: 给玩家4张牌,每张牌的面值在1~13之间,允许其中有数值相同的牌。采用加减乘除,允许中间运算存在小数,并且可以使用括号,但每张牌只能使用一次,尝试构造表达式,使其运算结果为24.
输出:若能得到运算结果为24,输出对应表达式 输入: 11,8,3,5 输出: (11-8)*(3+5)=24 |
编程之美 https://blog.csdn.net/qingyuanluofeng/article/details/47186763 解法2: 定义要计算的初始数据,放于集合A中,定义函数f(A)为对集合A中的元素进行所有可能的四则运算所得到的值,采用分治思想,先将A划分为两个子集A1和A-A1, 其中A1为A的非空真自己,分别计算A1和A-A1中的元素进行四则运算得到的结果集合,即f(A1)和f(A-A1),然后对f(A1)和f(A-A1)这两个集合中的元素进行加减 乘除运算,最后得到的所有集合的并集就是f(A)。 分治:划分,递归求解,合并 给定两个多重集合A和B,定义两个集合中的元素如下: Fork(A,B) = 并{a+b,a-b,b-a,a*b,a/b(b!=0),b/a(a!=0)},(a,b)属于A*B,假设A1中有n个元素,A2中有m个元素,那么将有n*m个(a,b),而每对值需要进行6个计算, Fork(A1,A2) = 2*n*m个元素,需要去重。假设集合A中有n个元素,那么集合A的所有非空真子集个数为2^n-2,则f(A)第一层递推式中共有(2^n-2)/2个Fork函数 可以用二进制数来表示集合和子集,由于只有4个元素,可以采用4位的二进制数来表示集合A及其真子集,设A{a0,a1,a2,a3},当且仅当ai在某一个真子集中时, 该真子集所代表的二进制数对应的第i位才为1,如A1 = {a1,a2,a3}则1110表示A1,若A2 = {a0,a3},那么1001表示A2,所以A的真子集范围为1到14(1到2^n-2), 再用一个大小为2^n-1的数组S来保存f(i)(1<=i<=15),数组S中的每一个元素S[i]都是一个集合(f(i)),其中S[2^n-1]即为集合A中的所有元素通过四则运算和加括号得到 的全部结果,通过检查S[2^n-1],可得知某个输入是否有解。 24Game(Array)//Array为初始输入集合 { for(int i = 1 ; i <= 2^n-1 ; i++) { S[i] = 空集;//初始化将S中的各个集合置为空集,n为集合Array的元素个数,在24点中即为4, } for(int i = 1 ; i < n ; i++) { S[2^i] = {ai};//先对每个只有一个元素的真子集赋值,即为该元素本身 } for(int i = 1 ;i <= 2^n - 1 ; i++)//对每个i都代表着Array的一个真子集 { S[i] = f(i);// } Check(S[2^n-1]);//检查S[2^n-1]中是否有值为24的元素,并返回 } f(int i)//i的二进制表示可代表着集合的一个真子集 { if(S[i] != 空集) { return S[i]; } for(int x = 1 ; x < i ; i++)//只有小于i的x才可能称为i的真子集 { if((x & i) == x)//&为与运算,只有当x & i == x成立时,x才为i的子集,此时i-x为i的另一个真子集,x与i-x共同构成i的一个划分 { S[i] U= Fork(f(x),f(i-x));//U为集合的bing yunsuan ,Fork的过程中,去除重复中间结果 } } } 代码 const double Threshould = 1E-6;//浮点数的误差值 const int CardsNumber = 4; const int ResultValue = 24; double number[CardsNumber]; string result[CardsNumber]; bool dot(int n) { if(n == 1)//递归出口 { if(fabs(number[0] - ResultValue) < Threshould) { cout << result[0] <<endl; } else { return false; } } for(int i = 0 ;i < n ; i++) { for(int j = i + 1 ; j < n ; j++) { double a,b; string expa,expb; a = number[i]; b = number[j]; number[j] = number[n-1];//? expa = result[i]; expb = result[j]; result[j] = result[n-1];//? result[i] = '(' + expa + '+' + expb + ')'; number[i] = a + b; if(dot(n-1)) { return true; } result[i] = '(' + expa + '-' + expb + ')'; number[i] = a - b; if(dot(n-1)) { return true; } result[i] = '(' + expa + '-'+ expb + ')'; number[i] = b - a ; if(dot(n-1)) { return true; } result[i] = '(' + expa + '*'+ expb + ')'; number[i] = a * b; if(dot(n-1)) { return true; } if(b != 0) { result[i] = '(' + expa + '/'+ expb + ')'; number[i] = a / b; if(dot(n-1)) { return true; } } if(a != 0) { result[i] = '(' + expa + '/'+ expb + ')'; number[i] = b / a; if(dot(n-1)) { return true; } } number[i] =a ; number[j] = b; result[i] = expa; result[j] = expb; } } return false; } void process() { int x; for(int i = 0 ; i < CardsNumber ; i++) { char buffer[20]; cout << "the" << i << "th number:"; cin >> x; number[i] = x; itoa(x,buffer,10); result[i] = buffer; } if(dot(CardsNumber)) { cout<< "成功了" <<endl; } else { cout<< "失败了" << endl; } } |
11 |
八皇后问题 设计一种算法,打印八皇后在8*8棋盘上的各种摆法,其中每个皇后都不同行、不同列,也不在对角线上。这里的“对角线”指的是所有的对角线,不只是平分整个棋盘的那两条对角线。 输入: 4(n皇后中的n) 输出: 0 1 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 1 0 1 0 0 0 0 0 0 1 0 1 0 0 |
程序员面试金典 https://blog.csdn.net/qingyuanluofeng/article/details/54293895 关键: 1 如何判定不在对角线: countXY[9] ,存储每个棋子的x-y + 7的值,对于任意元素countXY[i] <= 1,这个没有用, 参见:(1,4),(2,3)是对角线,但是用:|x-y|,或x-y来做判断都不行,因为题目指明了不是中间那条对角线 即任意两点的斜率的绝对值不为1,如果两点x相同或y相同直接排除,这样需要把8个点找出来,进行任意两点比较 2 另一种简单方法,将columns[i]表示横坐标为i对应的点的纵坐标 //判断待摆放位置是否和前面的棋子斜率相同或处于同一竖直线,不需要任意两个点两两比较 for(int i = 1 ; i < count ; i++ ) { //牛逼,斜率比较方式, c[i] - c[j] 与 i-j的绝对值比较 if( columns[i] == columns[count] || abs( columns[i] - columns[count] ) == abs( i - count ) ) { return false; } } 代码 const int MAXSIZE = 10000; int g_matrix[MAXSIZE][MAXSIZE]; bool isOK_simple(int n , int count , int* columns) { if(NULL == columns) { return false; } //判断待摆放位置是否和前面的棋子斜率相同或处于同一竖直线,不需要任意两个点两两比较 for(int i = 1 ; i < count ; i++ ) { //牛逼,斜率比较方式, c[i] - c[j] 与 i-j的绝对值比较 if( columns[i] == columns[count] || abs( columns[i] - columns[count] ) == abs( i - count ) ) { return false; } } return true; } //更简单的方法,我用x[i]表示第i行上的棋子纵坐标y,而不需要用二维数组 void setQueen_Simple(int n , int count , int* columns) { if(count > n) { //输出结果 printResult_simple(columns , n); } else { //确定列的摆放 for(int j = 1 ; j <= n ; j++) { //如果当前位置没有访问过 if( 0 == columns[count] ) { columns[count] = j; //如果可以 if(isOK_simple(n , count , columns )) { setQueen_Simple(n , count + 1 , columns); } //便于回溯 columns[count] = 0; } } } } |
12 |
给定两个字典里的单词,长度相等。编写一个方法,将一个单词变换成另一个单词,一次只改动一个字母。在变换过程中,每一步得到的新单词都必须是字典里里存在的。 |
程序员面试金典 https://blog.csdn.net/qingyuanluofeng/article/details/54632381 关键: 1我卡在了寻找中间替代字符。书上结果用了回溯,果然想法是一样的。 用广度优先,设定原始字符串压入队列,设定每次从队列中弹出字符串和目标字符串比较, 如果相同,则寻找到上一个可以经过修改一个字符等于目标字符串的字符串,再根据该字符串重复上述操作,得到结果; 如果不相同,则寻找变成当前字符串只需改动一个字符的字符串集合,遍历其中每个字符串,如果 没有访问过就加入队列,并设置该字符串可以变成当前字符串。 2 一定要生成当前字符串经过一次字符变换后的字符串集合;先建立字符串指向回溯关系,再判断是否找到 while(!queueStr.empty()) { string value = queueStr.front(); queueStr.pop(); //寻找经过一次字符变换就可以生成的单词v newWords = getOneEditWords(value , words); for(set<string>::iterator it = newWords.begin() ; it != newWords.end() ; it++) { string newWord = *it; //应该先判断,如果新单词在字典中存在,就建立映射,否则映射还没建立,就找到结果,回溯的时候,就无法回溯 if( words.find(newWord) != words.end() ) { //如果新单词没有访问过,就压入队列 if( visitedMap.find(newWord) == visitedMap.end() ) { queueStr.push(newWord); visitedMap[newWord] = true; resultMap[newWord] = value;//设置指向,这个要放到前面去做 } } //说明找到了,直接寻找结果 if(newWord == dst) { resultVector.push_back(newWord); map<string , string>::iterator itFind = resultMap.find(newWord); while(itFind != resultMap.end()) { string previousStr = itFind->second; resultVector.push_back(previousStr); itFind = resultMap.find(previousStr); } return resultVector; } } } 代码: set<string> getOneEditWords(string& word , hash_map<string , int>& words) { set<string> resultSet; if(word.empty()) { return resultSet; } int length = word.length(); for(int i = 0 ; i < length ; i++) { char value = word.at(i); //注意这里要留取待截取字符串前面的位置i,不是i+1 string frontStr = word.substr(0 , i); string backStr = word.substr(i+1); for(char ch = 'a' ; ch <= 'z' ; ch++) { if(ch != value) { //尝试组成新的单词 stringstream stream; stream << frontStr << ch << backStr; string newWord = stream.str(); //如果该单词存在于字典中,则加入结果集 if(words.find(newWord) != words.end()) { resultSet.insert(newWord); } } } } return resultSet; } vector<string> getTransformStrings(string& src, string& dst ,hash_map<string ,int>& words ) { vector<string> resultVector; if(src.empty() || dst.empty() || words.empty()) { return resultVector; } //注意,一定要将字符串全部改成小写(因为我的字典中单词是小写的) transform(src.begin() , src.end() , src.begin() , tolower); transform(dst.begin() , dst.end() , dst.begin() , tolower); queue<string> queueStr; hash_map<string , bool> visitedMap;//是否访问的标记,true表示已经访问过 visitedMap[src] = true; queueStr.push(src); map<string ,string> resultMap; set<string> newWords; while(!queueStr.empty()) { string value = queueStr.front(); queueStr.pop(); //寻找经过一次字符变换就可以生成的单词v newWords = getOneEditWords(value , words); for(set<string>::iterator it = newWords.begin() ; it != newWords.end() ; it++) { string newWord = *it; //应该先判断,如果新单词在字典中存在,就建立映射,否则映射还没建立,就找到结果,回溯的时候,就无法回溯 if( words.find(newWord) != words.end() ) { //如果新单词没有访问过,就压入队列 if( visitedMap.find(newWord) == visitedMap.end() ) { queueStr.push(newWord); visitedMap[newWord] = true; resultMap[newWord] = value;//设置指向,这个要放到前面去做 } } //说明找到了,直接寻找结果 if(newWord == dst) { resultVector.push_back(newWord); map<string , string>::iterator itFind = resultMap.find(newWord); while(itFind != resultMap.end()) { string previousStr = itFind->second; resultVector.push_back(previousStr); itFind = resultMap.find(previousStr); } return resultVector; } } } return resultVector; } |
13 |
Combination Sum Given a set of candidate numbers (C) (without duplicates) and a target number (T), find all unique combinations in C where the candidate numbers sums to T. The same repeated number may be chosen from C unlimited number of times. Note: All numbers (including target) will be positive integers. The solution set must not contain duplicate combinations. For example, given candidate set [2, 3, 6, 7] and target 7, 分析: 这是程序员面试金典的一道题目。求组成数的所有组合。 输入: 4(数组元素个数) 7(目标值) 2 3 6 7 4 6 2 3 6 7 2 7 2 6 输出: 7,2 2 3 6,3 3,2 2 2 no result |
Leecode https://blog.csdn.net/qingyuanluofeng/article/details/54934859 关键: 1 前半部分求解结果,前半部分 与 后半部分结果 进行笛卡尔积 即为最终结果 int curCandidate = candidates.at(curCandidateIndex); vector<vector<int>> results;//后半部分递归求解结果 vector<int> result;//前半部分求解结果,前半部分 与 后半部分结果 进行笛卡尔积 即为最终结果 for(int i = 0 ; i * curCandidate <= target ; i++) { result.clear();//清空上一次结果 for(int j = 0 ; j < i ; j++) { result.push_back(curCandidate); } results = combineSum(candidates , target - i * curCandidate , curCandidateIndex + 1 );//得到多个结果需要和当前结果进行笛卡尔积拼接 int size = results.size(); //进行笛卡尔积拼接 for(int k = 0 ; k < size ; k++) { results.at(k).insert(results.at(k).end() , result.begin() , result.end());//插入到后面,前面是最小部分 totalResults.push_back(results.at(k)); } //如果当前直接等于结果集i * curCandidate = target,直接压入结果中 if(i * curCandidate == target) { totalResults.push_back(result); } } return totalResults; 代码 bool compare(int a, int b) { return a > b; } class Solution { public: vector<vector<int>> combineSum(vector<int>& candidates, int target , int curCandidateIndex) { vector< vector<int> > totalResults; if(candidates.empty() || target <= 0 || curCandidateIndex < 0 || curCandidateIndex >= candidates.size()) { return totalResults; } int curCandidate = candidates.at(curCandidateIndex); vector<vector<int>> results;//后半部分递归求解结果 vector<int> result;//前半部分求解结果,前半部分 与 后半部分结果 进行笛卡尔积 即为最终结果 for(int i = 0 ; i * curCandidate <= target ; i++) { result.clear();//清空上一次结果 for(int j = 0 ; j < i ; j++) { result.push_back(curCandidate); } results = combineSum(candidates , target - i * curCandidate , curCandidateIndex + 1 );//得到多个结果需要和当前结果进行笛卡尔积拼接 int size = results.size(); //进行笛卡尔积拼接 for(int k = 0 ; k < size ; k++) { results.at(k).insert(results.at(k).end() , result.begin() , result.end());//插入到后面,前面是最小部分 totalResults.push_back(results.at(k)); } //如果当前直接等于结果集i * curCandidate = target,直接压入结果中 if(i * curCandidate == target) { totalResults.push_back(result); } } return totalResults; } vector<vector<int>> combinationSum(vector<int>& candidates, int target) { //必须确保候选值从大到小排序 sort(candidates.begin() , candidates.end() , compare ); int curCandidateIndex = 0; vector<vector<int>> results = combineSum(candidates, target , curCandidateIndex); return results; } }; |
参考:
[1]计算机考研--机试指南,王道论坛 组编
[2]剑指offer
[3]算法设计与分析
[4]编程之美
[5]程序员面试金典
[6]leecode
[7]Python程序员面试算法宝典
[8]刘汝佳算法竞赛入门经典
[9]算法导论
[10]编程珠玑
算法 64式 4、回溯算法整理__第1部分_1到13题相关推荐
- 算法 64式 7、搜索算法整理_第1部分_1到15题
1 算法思想 算法分类 搜索算法主要分为: 暴力搜索+剪枝,枚举,广度优先搜索,深度优先搜索,二分查找,哈希查找, A*算法,两边向中间逼近,从中间向两边扩散等 1.1枚举 枚举: 最直白的搜索方式, ...
- 算法 64式 7、搜索算法整理_第4部分_46到60题
1 算法思想 算法分类 搜索算法主要分为: 暴力搜索+剪枝,枚举,广度优先搜索,深度优先搜索,二分查找,哈希查找, A*算法,两边向中间逼近,从中间向两边扩散等 1.1枚举 枚举: 最直白的搜索方式, ...
- 算法 64式 7、搜索算法整理_第3部分_31到45题
1 算法思想 算法分类 搜索算法主要分为: 暴力搜索+剪枝,枚举,广度优先搜索,深度优先搜索,二分查找,哈希查找, A*算法,两边向中间逼近,从中间向两边扩散等 1.1枚举 枚举: 最直白的搜索方式, ...
- 算法 64式 7、搜索算法整理_第2部分_16到30题
1 算法思想 算法分类 搜索算法主要分为: 暴力搜索+剪枝,枚举,广度优先搜索,深度优先搜索,二分查找,哈希查找, A*算法,两边向中间逼近,从中间向两边扩散等 1.1枚举 枚举: 最直白的搜索方式, ...
- (十五)算法设计思想之“回溯算法”
算法设计思想之"回溯算法" 回溯算法是什么? 什么问题适合用回溯算法解决? 适合回溯算法解决的问题 全排列 LeetCode:46.全排列 LeetCode:78.子集 思考题 回 ...
- 极客时间——数据结构与算法(39) 回溯算法:从电影《蝴蝶效应》中学习回溯算法的核心思想
转载地址:https://time.geekbang.org/column/article/74287 我们在第 31 节提到,深度优先搜索算法利用的是回溯算法思想.这个算法思想非常简单,但是应用却非 ...
- JAVA算法:走迷宫回溯算法设计(JAVA版本)
JAVA算法:走迷宫回溯算法设计(JAVA版本) 迷宫数组 int[][] maze = { {0, 1, 0, 0, 0}, {0, ...
- 算法 64式 8、动态规划算法整理_第1部分_1到15题
1 算法思想 动态规划 1.1含义 把问题分解成多阶段或多个子问题,顺序求解各个子问题,最后一个子问题就是初始问题的解. 概念 阶段: 问题分成的顺序的几个环节.例如最长递增子序列中每个字符就是一个阶 ...
- 算法 64式 19、数学算法整理
1 算法思想 2 数学系列 类别-编号 题目 遁去的一 1 特殊乘法 写个算法,对2个小于1000000000的输入,求结果. 特殊乘法举例: 123 * 45 = 1*4 + 1*5 + 2*4 + ...
最新文章
- 用Spring Web Flow和Terracotta搭建Web应用
- 高清视频实时对讲SDK源码
- 直接定址表03 - 零基础入门学习汇编语言74
- python匿名函数调用_python3笔记十六:python匿名函数和高阶函数
- GDCM:gdcm::ImageHelper的测试程序
- python导入同一文件夹下的类_python自定义模块
- Gitter - 高颜值GitHub小程序客户端诞生记
- jhat命令 Java Heap Analyse Tool
- CRNN+CTC (基于CTPN 的end-to-end OCR)
- 【图像检索】基于matlab GUI Hu不变矩图像检索【含Matlab源码 1508期】
- 【百度网盘】老罗android开发视频教程[压缩后3.63G]
- vector的初始化和使用
- java 打包加密_java打包、加密、发布(源代码保护)
- Paper和陈丹琦撞车是一种怎样的体验
- 评:10月PMI指数新高, 带动大盘逆转, 跨年度业绩行情展开
- TalkingData三大产品创新,引领2022数字营销技术新格局
- MM们必败潮物。。。。大眼睛的小秘密哦```````
- 22考研杭师管理科学与工程专业368分经验贴(Python141+政治80+日语71,含数政专书籍与免费课程资源推荐)
- EF 之 System.InvalidOperationException
- Java导出excel中response.setHeader()参数设置
热门文章
- jquery ajax java二级联动_使用Ajax和Jquery配合数据库实现下拉框的二级联动的示例...
- Vim 分屏功能+无插件Vim编程技巧
- 1,10-Phen|邻菲啰啉|邻二氮杂菲|1,10-菲啰啉有机配体-66-71-7
- JS中的数组转变成JSON格式字符串的方法
- 【改进模糊神经网络】基于粒子群-万有引力算法PSOGSA 改进的前馈神经网络研究(Matlab代码实现)
- C#毕业设计——基于C#+asp.net+sqlserver基于C2C模式的网上购物系统设计与实现(毕业论文+程序源码)——网上购物系统
- Mac上的终端Shell命令总结(初级)
- PostgreSQL 源码解读(212)- 后台进程#11(checkpointer-SyncOneBuffer)
- 全新型号,戴尔(Dell) EMC PowerEdge R760机架式服务器产品特性及详细技术参数
- 将ES6代码转换为ES5代码