-
递归思路:
- 明确问题目标:想清楚自己要解决一个什么样的问题,该问题是否可以拆分成相似的子问题
- 找到回归条件:所谓回归条件,就是到达某一条件之后函数不能再递归下去,向上逐层返回结果
- 拆分问题:找到原问题等价的表达式,即将原问题拆分成和原问题相似的子问题
-
例题分析
1.整数拆分问题
问题:一个整数n,拆分成若干个小于等于m的整数相加的形式,并且左侧的数字要大于右侧的数字,数字顺序颠倒算同一种拆分形式。
(做一下这篇文章的学习笔记,自己做题没看懂,看了这个博主的回答豁然开朗)
整数拆分问题的四种解法_尼奥普兰的博客-优快云博客https://blog.youkuaiyun.com/u011889952/article/details/44813593例:
6=6
5+1
4+2,4+1+1
3+3,3+2+1,3+1+1+1
2+2+2,2+2+1+1,2+1+1+1+1
1+1+1+1+1+1
一共有11种拆分方法。
思路:
首一,一个可以拆分的整数,其拆分得到的整数也可以拆分(n≥3),所以这可以用递归解决。
第二,当n=1时,只有n=n一种情况,不能再拆分,即;当m=1时,不管n多大,都只有一种情况,即n个1相加,后续不能继续拆分;这两种情况就是该问题的回归条件。
第三,将问题拆分。
- 当n=m时,如果拆分种含有n,就只有一种情况;如果不含n,则所拆分得到的数一定不大于n-1,就变成了把n拆分成若干个不大于n-1的整数相加的问题,即
,一共有
种情况。
- 当n>m>1时,①如果拆分中包含m,得到的一组拆分中仍有可能存在m,所以变成了整数n-m拆分成不大于m的若干个数相加的问题,即
种情况;②如果不包含m,则拆分得到的一组整数都不大于m-1,所以问题变成了将n拆分成若干个不大于m-1的整数的问题,即
,一共有
种情况。
- 当n<m时,仍然是
的问题。
代码
int GetPartitionCount(int n,int m) {
if (n==1 || m==1) {//if n==1 or m==1
return 1;
}
else if (n == m) {
return GetPartitionCount(n, n - 1) + 1;
}
else if (n > m&&m > 1) {
return GetPartitionCount(n, m - 1) + GetPartitionCount(n - m, m);
}
else if (n < m) {
return GetPartitionCount(n, n);
}
}
2.棋盘覆盖问题
思路:
- 将问题转化为递归问题:要将问题拆分成几个相同的子问题,所以对于其他三个没有空缺的子棋盘,利用L形骨牌填充到三个子棋盘交会的顶点,如此一来四个子方块都有一个空缺的子棋盘。
- 分情况依次递归:按照从左向右,从上到下的顺序依次递归四个子棋盘,每次将棋盘四等分,有空缺的子棋盘继续递归,填充其他三个没有空缺的棋盘,直到子棋盘的大小为1,每次递归后骨牌数加一(后++)。
源码:
#include <iostream>
#include <iomanip>
using namespace std;
int tile = 1; // 骨牌序号
int** board; // 二维数组模拟棋盘
// (tr,tc)表示初始棋盘左上角的位置, (dr,dc)表示空格子的位置, size=确定棋盘大小,为2的整数次幂
void chessBoard(int tr, int tc, int dr, int dc, int size){
if (size == 1)//回归条件
return;
int s = size / 2; //四分
int t = tile++; //t记录序号
// 判断特殊方格在不在左上角棋盘
if (dr < tr + s && dc < tc + s){//在,就继续递归
chessBoard(tr, tc, dr, dc, s);
}
else{//不在,则填充其他子棋盘
board[tr + s - 1][tc + s - 1] = t; //覆盖右下角
chessBoard(tr, tc, tr + s - 1, tc + s - 1, s); //继续递归
}
// 判断特殊方格在不在右上角棋盘
if (dr < tr + s && dc >= tc + s){
chessBoard(tr, tc + s, dr, dc, s);
}
else{
board[tr + s - 1][tc + s] = t;//覆盖左下角
chessBoard(tr, tc + s, tr + s - 1, tc + s, s);
}
// 判断特殊方格在不在左下角棋盘
if (dr >= tr + s && dc < tc + s){
chessBoard(tr + s, tc, dr, dc, s);
}
else{
board[tr + s][tc + s - 1] = t;//覆盖右上角
chessBoard(tr + s, tc, tr + s, tc + s - 1, s);
}
// 判断特殊方格在不在右下角棋盘
if (dr >= tr + s && dc >= tc + s){
chessBoard(tr + s, tc + s, dr, dc, s);
}
else{
board[tr + s][tc + s] = t;//覆盖左上角
chessBoard(tr + s, tc + s, tr + s, tc + s, s);
}
}
int main(){
int boardSize = 0;
cout << "请输入棋盘边长(2的整数次幂):";
cin >> boardSize;
board = new int*[boardSize];
for (int i = 0; i < boardSize; i++) {
board[i] = new int[boardSize];
for (int j = 0; j < boardSize; j++) {
board[i][j] = 0;
}
}
int dr=0, dc = 0;
cout << "请输入空格子在棋盘中的行、列位置:" << endl;
cin >> dr >> dc;
chessBoard(0, 0, dr-1, dc-1, boardSize); // (0, 0)为顶点
//输出编号
int i, j;
for (i = 0; i < boardSize; i++){
for (j = 0; j < boardSize; j++){
cout <<setw(5)<< board[i][j] ;
}
cout << endl;
cout << endl;
}
return 0;
}
3.循环赛日程表问题
参考:
代码思路:
- 最外层循环(m)控制拆分得到的子棋盘的大小:①控制行列数的初始值;②控制不同大小的子棋盘需要进行几次对角填充。
- 第二层循环(t)控制对角填充时需要多少内层循环几次才能将1个划分的子棋盘填充完整。
- 最内层的2层循环(i,j)控制子棋盘中逐方块填充。
#include <iostream>
#include <iomanip>
using namespace std;
int** table;
//非递归
void roundRobin(int n) {
for (int i = 0; i < n; i++) {//初始化数组第0行
table[0][i] = i + 1;
}
for (int m = 1; m < n; m=m<<1) {//m代表该划分的棋盘边长,每循环依次扩大1倍
for (int t = 1; t <= n / (2*m); t++) {//t代表m级别的划分有几个->一共要做几次交换
for (int i = m; i < 2 * m; i++) {//row控制需要填充的行位置
for (int j = m; j < m * 2; j++) {//col控制需要填充的列位置
//左下角与右下角、左上角与右上角行数相同
//右下角=左上角
table[i][j + (t - 1)* m * 2] = table[i - m][j + (t - 1)*m * 2 - m];
//左下角=右上角
table[i][j + (t - 1)* m * 2 - m] = table[i - m][j + (t - 1) * 2 * m];
}
}
}
}
}
int main()
{
int k = 0, num = 0;
cout << "输入选手个数:";
cin >> num;//选手个数
//为二维数组分配内存
table = new int*[num];
for (int i = 0; i < num; i++) {
table[i] = new int[num];
}
roundRobin(num);
for (int i = 0; i < num; i++) {
for (int j = 0; j < num; j++) {
if (j == 0) {
cout << setw(4) << table[i][j] << " |";
}
else {
cout << setw(4) << table[i][j];
}
}
cout << endl;
}
return 0;
}
对以上i和j的初始值的问题和对角替换部分的代码进行解释:
(想不到很好的解释办法,先用图表示一下)
4.发帖水王
问题描述:
有某一论坛,其中有一发帖水王,不但喜欢发帖,还会回复其他ID发的每个帖子。“水王”发帖数目超过了帖子总数的一半。如果你有一个当前论坛上所有帖子(包括回帖)的列表,其中帖子作者的ID也在表中,你能快速找出这个传说中的Tango水王吗?
拓展:
如果“水王”没有了。但是有3个发帖很多的ID,发帖数目都超过了帖子总数的1/4。如何快速找出他们的ID?
思路:
思路可以用一句话总结:(每个)发帖水王的发帖数-非水王的发帖数总数>0
如果只有1个水王,他的发帖数一定大于1/2,则非水王的发帖总数一定小于1/2;同理,如果有3个水王,每个水王的发帖总数大于1/4,则非水王的发帖总数一定小于1/4,那么每个水王的发帖数-非水王的发帖总数>0。
源码:
#include <iostream>
using namespace std;
int candidates[3] = { 0,0,0 };
int times[3] = { 0,0,0 };
void find(int ID[], int num) {
for (int i = 0; i < num; i++) {
bool change = false, same = false;
for (int j = 0; j < 3; j++) {
if (candidates[j] == ID[i]) {//与某个候选者相同
times[j]++;
same = true;
break;
}
}
if (!same) {//与三个候选者都不同
for (int j = 0; j < 3; j++) {
if (times[j] == 0) {//判断是否有times==0的情况
candidates[j] = ID[i];
times[j] = 1;
change = true;//发生替换
break;
}
}
if (!change) {//没有times==0的候选者
for (int j = 0; j < 3; j++) {
times[j]--;
}
}
}
}
}
int main()
{
//int num = 20;
//int ID[20] = { 2,3,3,1,2,1,4,3,3,1,1,1,2,5,3,3,1,2,2,2 };
int num = 16;
int ID[16] = { 1, 2, 3, 4, 2, 3, 1, 1, 2, 3, 1, 2, 3, 1, 2, 3 };
find(ID, num);
cout << "发帖水王的ID为:" ;
for (int i = 0; i < 3; i++) {
cout << candidates[i] << " ";
}
return 0;
}
(未完待续2023.9.25)