牛客题解 | 没有重复项数字的全排列

news/2025/2/21 9:58:17/文章来源:https://www.cnblogs.com/wc529065/p/18728646

题目

题目链接

题目主要信息:
  • 给定一个数组,求这组数字的全排列
  • 数组无重复元素
  • 以数字在数组中的位置靠前为优先级,按字典序排列输出
举一反三:

学习完本题的思路你可以解决如下题目:

BM56. 有重复项数字的全排列

BM58. 字符串的排列

BM60. 括号生成

递归+回溯(推荐使用)

知识点:递归与回溯

递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。

如果是线型递归,子问题直接回到父问题不需要回溯,但是如果是树型递归,父问题有很多分支,我需要从子问题回到父问题,进入另一个子问题。因此回溯是指在递归过程中,从某一分支的子问题回到父问题进入父问题的另一子问题分支,因为有时候进入第一个子问题的时候修改过一些变量,因此回溯的时候会要求改回父问题时的样子才能进入第二子问题分支。

思路:

全排列就是对数组元素交换位置,使每一种排列都可能出现。因为题目要求按照字典序排列输出,那毫无疑问第一个排列就是数组的升序排列,它的字典序最小,后续每个元素与它后面的元素交换一次位置就是一种排列情况,但是如果要保持原来的位置不变,那就不应该从它后面的元素开始交换而是从自己开始交换才能保证原来的位置不变,不会漏情况。

如何保证每个元素能和从自己开始后的每个元素都交换位置,这种时候我们可以考虑递归。为什么可以使用递归?我们可以看数组[1,2,3,4],如果遍历经过一个元素2以后,那就相当于我们确定了数组到该元素为止的前半部分,前半部分1和2的位置都不用变了,只需要对3,4进行排列,这对于后半部分而言同样是一个全排列,同样要对从每个元素开始往后交换位置,因此后面部分就是一个子问题。那我们考虑递归的几个条件:

  • 终止条件: 要交换位置的下标到了数组末尾,没有可交换的了,那这就构成了一个排列情况,可以加入输出数组。
  • 返回值: 每一级的子问题应该把什么东西传递给父问题呢,这个题中我们是交换数组元素位置,前面已经确定好位置的元素就是我们返还给父问题的结果,后续递归下去会逐渐把整个数组位置都确定,形成一种排列情况。
  • 本级任务: 每一级需要做的就是遍历从它开始的后续元素,每一级就与它交换一次位置。

如果只是使用递归,我们会发现,上例中的1与3交换位置以后,如果2再与4交换位置的时候,我们只能得到3412这种排列,无法得到1432这种情况。这是因为遍历的时候1与3交换位置在2与4交换位置前面,递归过程中就默认将后者看成了前者的子问题,但是其实我们1与3没有交换位置的情况都没有结束,相当于上述图示中只进行了第一个分支。因此我们用到了回溯。处理完1与3交换位置的子问题以后,我们再将其交换回原来的情况,相当于上述图示回到了父节点,那后续完整的情况交换我们也能得到。

//遍历后续的元素
for(int i = index; i < num.size(); i++){ //交换二者swap(num, i, index); //继续往后找recursion(res, num, index + 1); //回溯swap(num, i, index); 
}

具体做法:

  • step 1:先将数组排序,获取字典序最小的排列情况。
  • step 2:递归的时候根据当前下标,遍历后续的元素,交换二者位置后,进入下一层递归。
  • step 3:处理完一分支的递归后,将交换的情况再换回来进行回溯,进入其他分支。
  • step 4:当前下标到达数组末尾就是一种排列情况。

图示:

alt

Java实现代码:

import java.util.*;
public class Solution {//交换位置函数private void swap(ArrayList<Integer> num, int i1, int i2{ int temp = num.get(i1);num.set(i1, num.get(i2));num.set(i2, temp);}public void recursion(ArrayList<ArrayList<Integer>> res, ArrayList<Integer> num, int index){//分枝进入结尾,找到一种排列if(index == num.size() - 1){res.add(num);}else{//遍历后续的元素for(int i = index; i < num.size(); i++){ //交换二者swap(num, i, index); //继续往后找recursion(res, num, index + 1); //回溯swap(num, i, index); }}}public ArrayList<ArrayList<Integer>> permute(int[] num) {//先按字典序排序Arrays.sort(num);  ArrayList<ArrayList<Integer> > res = new ArrayList<ArrayList<Integer>>();ArrayList<Integer> nums = new ArrayList<Integer>();//数组转ArrayListfor(int i = 0; i < num.length; i++) nums.add(num[i]);recursion(res, nums, 0);return res;}
}

C++实现代码:

class Solution {
public:void recursion(vector<vector<int> > &res, vector<int> &num, int index){//分枝进入结尾,找到一种排列if(index == num.size() - 1) res.push_back(num);else{//遍历后续的元素for(int i = index; i < num.size(); i++){ //交换二者swap(num[i], num[index]); //继续往后找recursion(res, num, index + 1); //回溯swap(num[i], num[index]); }}}vector<vector<int> > permute(vector<int> &num) {//先按字典序排序sort(num.begin(), num.end()); vector<vector<int> > res;//递归获取recursion(res, num, 0); return res;}
};

Python实现代码:

class Solution:def recursion(self, res:List[List[int]], num:List[int], index:int):#分枝进入结尾,找到一种排列if index == len(num) - 1:  res.append(num)else:#遍历后续的元素for i in range(index, len(num)): #交换二者temp = num[i]num[i] = num[index]num[index] = temp#继续往后找self.recursion(res, num, index + 1) #回溯temp = num[i]num[i] = num[index]num[index] = tempdef permute(self , num: List[int]) -> List[List[int]]:#先按字典序排序num.sort() res = list(list())#递归获取self.recursion(res, num, 0) return res

复杂度分析:

  • 时间复杂度:\(O(n*n!)\),n个元素的数组进行全排列的递归,每次递归都要遍历数组
  • 空间复杂度:\(O(n)\),递归栈的最大深度为数组长度n,res属于返回必要空间

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/887481.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

牛客题解 | 最长无重复子数组

牛客输入输出题单题解题目 题目链接 题目主要信息:题目给定一个数组,要找到其中最长的无重复的子数组的长度 子数组必须是数组中连续的一段举一反三: 学习完本题的思路你可以解决如下题目: BM90. 最小覆盖子串 方法:滑动窗口(推荐使用) 知识点1:滑动窗口 滑动窗口是指在数…

牛客题解 | 最长的括号子串

牛客输入输出题单题解题目 题目链接 题目主要信息:一个长度为\(n\)的仅包含左右括号的字符串 计算最长的格式正确的括号子串的长度举一反三: 学习完本题的思路你可以解决如下题目: BM65 最长公共子序列(二) BM66.最长公共子串 BM71.最长上升子序列(一) BM73 最长回文子串 BM…

牛客题解 | 最长公共前缀

牛客输入输出题单题解题目 题目链接 题目主要信息:给定一个字符串数组,其中有n个字符串,求所有字符串的最长公共前缀 公共前缀是指所有字符串都共有的前面部分的子串,从第一个字符开始举一反三: 学会了本题的思路,你将可以解决类似的字符串问题: BM83. 字符串变形 BM85. …

牛客题解 | 旋转数组

牛客输入输出题单题解题目 题目链接 题目主要信息:一个长度为\(n\)的数组,将数组整体循环右移\(m\)个位置(\(m\)可能大于\(n\)) 循环右移即最后\(m\)个元素放在数组最前面,前\(n-m\)个元素依次后移 不能使用额外的数组空间举一反三: 学习完本题的思路你可以解决如下题目:…

牛客题解 | 括号生成

牛客输入输出题单题解题目 题目链接 题目主要信息:求n对括号的全部合法组合,左右括号之间任意组合,只要合法就行 需要输出所有的结果举一反三: 学习完本题的思路你可以解决如下题目: BM55. 没有重复项数字的全排列 BM56. 有重复项数字的全排列 BM58. 字符串的排列 方法:递…

牛客题解 | 按之字形顺序打印二叉树

牛客输入输出题单题解题目 题目链接 题目的主要信息:给定一个二叉树,返回该二叉树的之字形层序遍 第一层从左向右,下一层从右向左,一直这样交替举一反三: 学习完本题的思路你可以解决如下题目: JZ32. 从上往下打印二叉树 JZ78. 把二叉树打印成多行 方法一:非递归层次遍历…

牛客题解 | 接雨水问题

牛客输入输出题单题解题目 题目链接 题目主要信息:给定一个整型数组,数组每个元素表示下图所示的每列灰色柱子高度,数值都是非负数 在雨水(图中蓝色部分)不超过边界的情况下,问最多能有多少蓝色的格子 数组以外的区域高度视为0举一反三: BM93. 盛水最多的容器 方法:双指…

牛客题解 | 合并两个有序的数组

牛客输入输出题单题解题目 题目链接 题目主要信息:A与B是两个升序的整型数组,长度分别为\(n\)和\(m\) 需要将数组B的元素合并到数组A中,保证依旧是升序 数组A已经开辟了\(m+n\)的空间,只是前半部分存储的数组A的内容举一反三: 学习完本题的思路你可以解决如下题目: BM4. 合…

牛客题解 | 合并二叉树

牛客输入输出题单题解题目 题目链接 题目的主要信息:合并(相加)二叉树位置相同的节点 缺少的节点用另一棵树来补,若都缺则返回NULL举一反三: 学习完本题的思路你可以解决如下题目: BM28. 二叉树的最大深度 BM29. 二叉树中和为某一值的路径(一) BM31. 对称的二叉树 BM33…

牛客题解 | 反转字符串

牛客输入输出题单题解题目 题目链接 题目的主要信息:输入一个只包含小写字母的字符串 输出该字符串反转后的字符串举一反三: 学习完本题的思路你可以解决如下题目: BM87. 合并两个有序数组 BM88. 判断是否为回文字符串 方法一:双指针交换(推荐使用) 知识点:双指针 双指针…

DeepSeek+AnythingLLM,搭建本地AI知识库,真的太香了!三分钟搞定智能助手,小白也能轻松上手!

1、🌟 痛点暴击:你的知识管理还在原始时代吗? 你是否每次查找文档翻遍文件夹,会议纪要总在关键时刻“失踪”? 别慌!今天揭秘一个“真香”组合——DeepSeek+AnythingLLM,轻松搭建本地知识库,AI秒变你的“第二大脑”! 2、🚀 为什么选DeepSeek+AnythingLLM?三大优势碾…

H3C-msr-36-10引导损坏重装引导升级

1、去官网下载准备升级用的软件ipe 2、在用户 PC(假设 IP 地址为 192.168.0.23)上运行 TFTP Server 程序(3CDaemon软件),以 及正确的文件保存目录,并把待升级文件保存在 TFTP Server 的工作目录下。3、在用户 PC 上运行终端仿真程序,启动路由器,键入<Ctrl+B>,进…