首页 > 其他分享 >递归与回溯

递归与回溯

时间:2024-08-14 21:54:42浏览次数:12  
标签:递归 int chessboard cols 回溯 皇后 col row

递归

1. 含义

递归:函数(方法)直接或间接调用自身

2. 调用过程

如果递归调用没有终止,将会一直消耗栈空间  最终导致栈内存溢出(Stack Overflow) 所以必需要有一个明确的结束递归的条件  也叫作边界条件、递归基

 3. 基本思想

1. 拆解

把规模大的问题变成规模较小的同类型问题

规模较小的问题又不断变成规模更小的问题

规模小到一定程度可以直接得出它的解

2.求解

由最小规模问题的解得出较大规模问题的解

由较大规模问题的解不断得出规模更大问题的解

最后得出原来问题的解

3.总结

凡是可以利用上述思想解决问题的,都可以尝试使用递归

很多链表、二叉树相关的问题都可以使用递归来解决因为链表、二叉树本身就是递归的结构

4. 使用套路

① 明确函数的功能

     首先搞清楚这个函数的干嘛用的,能完成什么功能?

② 明确原问题与子问题的关系

     先不要去思考里面代码怎么写

     寻找 f(n) f(n – 1) 的关系   

③ 明确递归基(边界条件)

     递归的过程中,子问题的规模在不断减小,当小到一定程度时可以直接得出它的解

     寻找递归基,相当于是思考:问题规模小到什么程度可以直接得出解?

 5.练习题目

练习1 - 斐波那契数列

斐波那契数列:1、1、2、3、5、8、13、21、34、······ 发现: F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n≥3)  
    int fib(int n) {
		if (n <= 2) return 1;
		return fib(n - 1) + fib(n - 2);
	}

缺点:出现了特别多的重复计算 

 优化一:记忆化
用数组存放计算过的结果,避免重复计算
	int fib(int n) {
		if (n <= 2) return 1;
		int[] array = new int[n + 1];
		array[1] = array[2] = 1;
		for (int i = 3; i <= n; i++) {
			array[i] = array[i - 1] + array[i - 2];
		}
		return array[n];
	}
优化二:变量存储

用两个变量存储值,第二个即为结果

	int fib5(int n) {
		if (n <= 2) return 1;
		int first = 1,second = 1;
		for(int i = 3; i <= n; i++) {
			second = first + second;
			first = second - first;
		}
		return second;
	}

练习2 - 上楼梯

楼梯有 n 阶台阶,上楼可以一步上 1 阶,也可以一步上 2 阶,走完 n 阶台阶共有多少种不同的走法?

假设 n 阶台阶有 f(n) 种走法,第 1 步有 2 种走法

  ✓ 如果上 1 阶,那就还剩 n – 1 阶,共 f(n – 1) 种走法

  ✓ 如果上 2 阶,那就还剩 n – 2 阶,共 f(n – 2) 种走法

所以 f(n) = f(n – 1) + f(n – 2)

 递推式与跟斐波那契数列几乎一样,但边界条件不同

    int climbStairs(int n) {
		if (n <= 2) return n;
		return climbStairs(n - 1) + climbStairs(n - 2);
	}

练习3 - 汉诺塔

把 A 的 n 个盘子移动到 C(盘子编号是 [1, n] )  每次只能移动1个盘子  大盘子只能放在小盘子下面

 有 2 种情况:

当 n == 1时,直接将盘子从 A 移动到 C

当 n > 1时,可以拆分成3大步骤

   ① 将 n – 1 个盘子从 A 移动到 B    ② 将编号为 n 的盘子从 A 移动到 C    ③ 将 n – 1 个盘子从 B 移动到 C

✓ 步骤 ① ③ 明显是个递归调用

	// 将 n 个盘子从 A 挪动到 C
    void hanoi(int n, String A, String B, String C) {
		if (n == 1) {
			move(n, A, C);
			return;
		}
        // 将n上面的n - 1个盘子从A挪到B
		hanoi(n - 1, A, C, B);
        // 把第n个盘子从A挪到C
		move(n, A, C);
        // 最后将B的n - 1个盘子挪到C上
		hanoi(n - 1, B, A, C);
	} 

	void move(int no, String from, String to) {
		System.out.println("将" + no + "号盘子从" + from + "移动到" + to);
	}

 回溯

1. 含义

通过选择不同的岔路口来通往目的地(找到想要的结果)

每一步都选择一条路出发,能进则进,不能进则退回上一步(回溯),换一条路再试

 2. 练习 - 八皇后问题

在8x8格的国际象棋上摆放八个皇后,使其不能互相攻击:任意两个皇后都不能处于同一行、同一列、同一斜线上请问有多少种摆法?

回溯 + 剪枝 

    // 数组索引是行号,数组元素是列号
	int[] cols;
	// 一共有多少种摆法
	int ways;
	
    // 放置n个皇后
	void placeQueens(int n) {
		if (n < 1) return;
		cols = new int[n];
		place(0);
		System.out.println(n + "皇后一共有" + ways + "种摆法");
	}

	// 从第row行开始摆放皇后
	void place(int row) {
        // 到达最后一行的下一行
		if (row == cols.length) {
			ways++;
			return;
		}
		
        // 遍历每一行的所有格子,在循环中发生回溯
		for (int col = 0; col < cols.length; col++) {
			if (isValid(row, col)) {
				// 在第row行第col列摆放皇后
				cols[row] = col;
				place(row + 1);
				// 回溯发生在这
			}
		}
	}
	
	// 判断第row行第col列是否可以摆放皇后
	boolean isValid(int row, int col) {
		for (int i = 0; i < row; i++) {
			// 第col列已经有皇后
			if (cols[i] == col) {
				return false;
			}
			// 第i行的皇后跟第row行第col列格子处在同一斜线上
			if (row - i == Math.abs(col - cols[i])) {
				return false;
			}
		}
		return true;
	}
}

51. N 皇后

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

示例 1:

输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。

示例 2:

输入:n = 1
输出:[["Q"]]

提示:

  • 1 <= n <= 9
class Solution {
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> res = new ArrayList<>();//[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
        List<Integer> path = new ArrayList<>();//[1,3,0,2] 存储第i行的皇后在第path.get(i)列
        int[] chessboard = new int[n];//[1,3,0,2] 存储第i行的皇后在第chessboard[i]列
        Arrays.fill(chessboard, -1);
        dfs(res, path, n, 0, chessboard);
        return res;
    }
    //棋盘的宽度就是for循环的长度,递归的深度就是棋盘的高度
    public void dfs(List<List<String>> res, List<Integer> path, int n, int row, int[] chessboard) {
        if(row == n) {
            List<String> list = helper(path);//[1,3,0,2]->[".Q..","...Q","Q...","..Q."]
            res.add(list);
            return;
        }
        //对于index行,遍历每一列
        for(int cols = 0; cols < n; cols++) {
            if(!isValid(row, cols, chessboard)) continue;//判断(row,cols)能否放皇后,不能放皇后,则越过。

            path.add(cols);
            chessboard[row] = cols;
            dfs(res, path, n, row + 1, chessboard);
            path.remove(path.size() - 1);
            chessboard[row] = -1;
        }
    }
    
    //[1,3,0,2]->[".Q..","...Q","Q...","..Q."]
    public List<String> helper(List<Integer> path) {
        List<String> board = new ArrayList<>();
        int n = path.size();
        for(int i = 0; i < n; i++) {
            char[] temp = new char[n];
            Arrays.fill(temp, '.');//"...."
            temp[path.get(i)] = 'Q';//".Q.."
            board.add(new String(temp));
        }
        return board;
    }
    //判断(row,col)能否放皇后
    public boolean isValid(int row, int col, int[] chessboard) {
        //从chessboard中,取出每一个皇后,判断是否与(row,col)的皇后冲突
        for(int i = 0; i < row; i++) {
            //同一列
            if(chessboard[i] == col) {
                return false;
            }
            //同"/"方向
            if(i + chessboard[i] == row + col) {
                return false;
            }
            //同"\"方向
            if(i - chessboard[i] == row - col) {
                return false;
            }
        }

        return true;//没有任何冲突,(row,col)可以放皇后
    }
}

 

标签:递归,int,chessboard,cols,回溯,皇后,col,row
From: https://blog.csdn.net/2301_80395772/article/details/141173007

相关文章

  • vue 组件调用组件自身,递归调用组件自身
    父组件<template><divclass="page-box"><!--<child><templatev-slot:default="scope"><div>slot</div><div>{{scope.data1}}</div>......
  • 进阶 Java冒泡排序递归法 有点难度哦
    简介这里有用到递归的冒泡排序思路,难度对新手很大,不明白递归和冒泡排序的小伙子可以先看看我的其他两个文章。连接在这里:Java冒泡排序https://blog.csdn.net/ouhexie/article/details/140985984?spm=1001.2014.3001.5501Java递归算法https://blog.csdn.net/ouhexie/articl......
  • 回溯算法介绍以及模板
    回溯算法的理解:回溯算法可以理解为一颗树形结构,即一颗n叉树,当遍历到叶子节点的时候,我们就到达了递归的终点,此时我们应该往上走。回溯法解决的问题都可以抽象为树形结构,是的,我指的是所有回溯法的问题都可以抽象为树形结构!因为回溯法解决的都是在集合中递归查找子集,集合的大小就......
  • Java解决递归造成的堆栈溢出问题
    在Java中,递归造成的堆栈溢出问题通常是因为递归调用的深度过大,导致调用栈空间不足。解决这类问题的一种常见方法是使用非递归的方式重写算法,即使用迭代替代递归。1.方法一:非递归的方式重写算法(迭代替代递归)下面通过一个典型的递归例子——计算斐波那契数列的第n项,来演示如何用迭......
  • Java基础入门18:File、IO 流1(方法递归、字符集、IO流-字节流)
    File和IO流FileFile是java.io.包下的类,File类的对象,用于代表当前操作系统的文件(可以是文件、或文件夹)。IO流用于读写数据的(可以读写文件,或网络中的数据...)File代表文件IO流用来读写数据File创建对象创建File类的对象注意:File对象既可以代表文件、也可以代表文......
  • leetcode递归(LCR 141. 训练计划 III)
    前言经过前期的基础训练以及部分实战练习,粗略掌握了各种题型的解题思路。现阶段开始专项练习。递归大部分题解可以使用迭代方式求解,使用递归是为了熟悉递归的解题思路。描述给定一个头节点为 head 的单链表用于记录一系列核心肌群训练编号,请将该系列训练编号 倒序 记录......
  • 回溯 70 年代的中国:朴素岁月中的奋斗与希望
    70年代的中国,是一段承载着特殊历史记忆的岁月。刚刚经历过文化大革命的洗礼,社会正处在一个独特的历史氛围之中。在那个时代,人们的生活条件相对艰苦,但在这艰苦之中,却蕴含着一种独特的朴素和真实,衣食住行的方方面面都深深烙印着那个时代的鲜明特征。 在那个时代的着装方面,......
  • LeetCode 22. 括号生成 回溯写法详解
    22.括号生成22.括号生成题目来源题目分析题目难度题目标签题目限制解题思路核心算法步骤代码实现代码解读性能分析测试用例扩展讨论优化写法其他实现总结22.括号生成题目来源22.括号生成题目分析给定一个数字n,表示生成括号的对数,要求设计一个函数生成所......
  • LeetCode 216. 组合总和 III 回溯写法详解
    216.组合总和III216.组合总和III题目来源题目分析题目难度题目标签题目限制解题思路核心算法步骤代码实现代码解读性能分析测试用例扩展讨论优化写法其他实现总结216.组合总和III题目来源216.组合总和III题目分析题目要求找出所有相加之和为n的k......
  • 算法笔记|Day22回溯算法IV
    算法笔记|Day22回溯算法IV☆☆☆☆☆leetcode491.递增子序列题目分析代码☆☆☆☆☆leetcode46.全排列题目分析代码☆☆☆☆☆leetcode47.全排列II题目分析代码☆☆☆☆☆leetcode332.重新安排行程(待补充)题目分析代码☆☆☆☆☆leetcode51.N皇后(待补充)题目分析......