算法篇07、递归之回溯算法--排列、组合、子集、N皇后、数独等

[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

题解如下所示,首先是递归终止条件,当list中元素个数和nums数组的个数相同时说明是其中一个排列结果,将结果放入List中,这里注意一点应该新建一个List,因为传入的list是个引用,如果直接使用,最后存入的所有list就是同一个了;

递归过程其实就是多叉树的递归过程,开一个for循环,循环遍历数组中的元素,然后在循环中递归调用自身,因为全排列不能有重复元素,因此如果list中已经包含了当前遍历到的元素,就continue继续下一次循环;看,这不就是一棵n叉树的递归吗~n是数组的长度,只不过在递归过程中去除了重复元素而已;还有一点需要注意,在递归前后元素分别进入list和移除list,这是因为递归完此元素包含的分支之后应该将该元素移除,继续下一个元素的递归;

//leetcode 46 全排列
List<List> res46 = new ArrayList<>();

public List<List> permute(int[] nums) {
if (nums == null || nums.length == 0) {
return res46;
}
LinkedList list = new LinkedList<>();
generatePermutation(nums, list);
return res46;
}

private void generatePermutation(int[] nums, LinkedList list) {
//递归终止条件,list中元素个数和nums数组的个数相同时说明是其中一个结果
//在添加时为什么要new LinkedList<>,而不是直接传入list,因为这里的list是一个引用,如果传list,那么最后所有的list都是相同的;
if (list.size() == nums.length) {
res46.add(new LinkedList<>(list));
return;
}

//递归调用过程,其实原理就是多叉树的递归
for (int i = 0; i < nums.length; i++) {
if (list.contains(nums[i])){
continue;
}else {
list.addLast(nums[i]);
generatePermutation(nums, list);
list.removeLast();
}
}
}

2、leetcode 77–组合

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

示例:

输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

题解如下所示,组合跟排列的解题逻辑基本一致,首先是递归终止条件,当list中的元素个数等于k时表示是一种组合结果,然后放入总的结果中,这里同样注意不要直接传入list,而是要新建一个list;

递归过程也是多叉树的递归过程,开一个for循环遍历从start到n,start是我们传入的一个开始索引,因为组合中的元素也是不能重复的,因此在递归过程中开始索引传入start+1即可;递归前后元素进入list和元素移除list的逻辑和排列完全相同,这也是回溯算法的标准逻辑过程;

//leetcode 77 组合
List<List> res77 = new LinkedList<>();

public List<List> combine(int n, int k) {
LinkedList list = new LinkedList<>();
backtrackCombination(n, k, 1, list);
return res77;
}

private void backtrackCombination(int n, int k, int start, LinkedList list) {
if (list.size() == k) {
res77.add(new LinkedList<>(list));
return;
}

for (int i = start; i <= n; i++) {
list.addLast(i);
backtrackCombination(n, k, start + 1, list);
list.removeLast();
}
}

3、leetcode 78–子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

题解如下所示,理解了排列和组合的逻辑之后,再来理解子集问题就比较简单了,其实都是相同的一类逻辑,n叉树的递归;

这里主要注意两点,第一点还是在加入list时不要传入list,应该新建一个list;第二点就是开始因子start,这里跟组合不同,这里开始因子应该从0开始,因为空集也是子集并且数组索引从0开始的,而且递归过程中传入的start是i+1而不是start+1,传start+1会出现很多重复的元素;

//leetcode 78 子集
List<List> res78 = new LinkedList<>();

public List<List> subsets(int[] nums) {
if (nums == null || nums.length == 0) {
return res78;
}
LinkedList temp = new LinkedList<>();
subsets(nums, 0, temp);
return res78;
}

private void subsets(int[] nums, int start, LinkedList temp) {
res78.add(new LinkedList<>(temp));

for (int i = start; i < nums.length; i++) {
temp.addLast(nums[i]);
subsets(nums, i + 1, temp);
temp.removeLast();
}
}

4、leetcode 51–N皇后

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

皇后彼此不能相互攻击,也就是说:任何两个皇后都不能处于同一条横行、纵行或斜线上。

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

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

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

题解如下所示,这里同样需要一个开始位置因子,这里的开始因子表示行row,下面可以看到递归的时候也是row+1递归下一行;递归终止条件就是row == chess.length,这表示chess这个二维数组中已经有一种皇后摆放的结果了,然后我们可以把这个结果放入总结果中,这里放入的时候采用了一点小技巧,将chess[][]二维数组转变为list集合;

递归过程就是开一个for循环遍历每一行中的所有列,然后在循环中递归调用下一行;这里最关键的逻辑是isValid(char[][] chess, int row, int col)这个函数,它用来判断chess二维数组中当前row行和col列是否可以摆放皇后Q,按照题目要求,需要不同行、不同列、不能在同一对角线上,因为我们是一行一行递归的,因此行不需要判断;列只需要判断上方,因为下方还没有递归到;对角线同样只需要判断上方的对角线,下方的也没有递归到,但又分为左上对角线和右上对角线两种情况;总共就这三种情况,代码中都有注释;

//leetcode 51 N皇后 大名鼎鼎的N皇后问题,做哭了快
List<List> res51 = new LinkedList<>();

public List<List> solveNQueens(int n) {
//新建一个棋盘
char[][] chess = new char[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
chess[i][j] = ‘.’;
}
}
traceBackQueue(chess, 0);
return res51;
}

private void traceBackQueue(char[][] chess, int row) {
if (row == chess.length) {
res51.add(generateList(chess));
return;
}

for (int col = 0; col < chess.length; col++) {
if (isValid(chess, row, col)) {
chess[row][col] = ‘Q’;
traceBackQueue(chess, row + 1);
chess[row][col] = ‘.’;
}
}
}

private boolean isValid(char[][] chess, int row, int col) {
//上方的列有皇后攻击,下方不用判断,因为下方还没有递归到
for (int i = 0; i < row; i++) {
if (chess[i][col] == ‘Q’) {
return false;
}
}

//左上对角线有皇后攻击
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i–, j–) {
if (chess[i][j] == ‘Q’) {
return false;
}
}

//右上对角线有皇后攻击
for (int i = row - 1, j = col + 1; i >= 0 && j < chess.length; i–, j++) {
if (chess[i][j] == ‘Q’) {
return false;
}
}

return true;
}

//将二维数组char[][]转换为List

尾声

如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

这里,笔者分享一份从架构哲学的层面来剖析的视频及资料给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。

Android进阶学习资料库

一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!

大厂面试真题

PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

《2019-2021字节跳动Android面试历年真题解析》


《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
dle知识点、常见算法题汇总。)

[外链图片转存中…(img-EdOdUJVl-1715353529152)]

《2019-2021字节跳动Android面试历年真题解析》

[外链图片转存中…(img-CYAEcSFQ-1715353529153)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 14
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值