最全排列组合算法详解以及套路总结一文突破

最全排列组合算法详解以及套路总结一文突破1 排列组合问题排列组合是经典的算法问题 相关的内容中学阶段就学习过 在讲算法实现之前 我们先简单复习一下排列组合的相关定义 排列 英文名称为 Permutation 简称 P 假设有一个数组 1 2 3 4 5 我们需要将数组中的所有元素进行排序 那么第一个位置 我们可以选择五个数字的任何一个 共有 5 种选择 第二个位置 可以选择剩余四个数字的任何一个 共有 4 种选择 第三个位置 可以选择剩余三个数字中的任何一个 共有 3 种选择 第四个位置 可以选择剩余两个数字中的任何一个 共有 2 种选择 最后一个位置

1.排列组合问题

很多时候我们不做全排列,比如5个元素,我们只需要取3个进行排序,按照前面的分析,很容易得知排列的个数为 5 ∗ 4 ∗ 3 = 60 5*4*3=60 543=60种,后面的 2 ∗ 1 2*1 21两种情况被舍弃掉了。因此,从N个元素中选择k个做排列,公式也很容易写出来: P ( N , k ) = N ! ( N − k ) ! P(N, k) = \frac{N!}{(N-k)!} P(N,k)=(Nk)!N!

2.所有子集

二话不说,先上代码,后面再分析具体思路。

import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; public class SubSet { public static int[] nums = {1, 2, 3}; public static ArrayList 
  
    > result = new ArrayList<>(); public static void subset(ArrayList 
   
     inner, int start) { for(int i=start; i 
    
      (inner)); subset(inner, i+1); inner.remove(inner.size()-1); } } public static void main(String[] args) { ArrayList 
     
       inner = new ArrayList<>(); result.add(inner); subset(inner, 0); for(ArrayList 
      
        each: result) { System.out.println(StringUtils.join(each, ",")); } } } 
       
      
     
    
  

上面代码的输出为

 1 1,2 1,2,3 1,3 2 2,3 3 

刚好8种情况,而且看输出结果,符合我们的预期。

上面的分析过程,实际上就是代码中函数不停压栈然后回溯调用的过程。建议同学们可以实际debug一下,看一看代码运行过程,会理解得更加深刻。

3.从n个元素中选择k个组合

第二部分是求所有的子集,如果我们限定子集元素的个数,即从n个元素中选择k个元素组合,就是常见的 C ( n , k ) C(n, k) C(n,k)问题。

解法思路基本与上面相同,先看代码。

import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; public class SelectK { public static int[] nums = {1, 2, 3}; public static ArrayList 
  
    > result = new ArrayList<>(); public static void select(ArrayList 
   
     inner, int start, int k) { for(int i=start; i 
    
      (inner)); inner.remove(inner.size()-1); continue; } select(inner, i+1, k); inner.remove(inner.size()-1); } } public static void main(String[] args) { ArrayList 
     
       inner = new ArrayList<>(); int k = 2; select(inner, 0, k); for(ArrayList 
      
        each: result) { System.out.println(StringUtils.join(each, ",")); } } } 
       
      
     
    
  

结果为:

1,2 1,3 2,3 

与求所有子集不一样的地方在于,只有当inner中的元素个数为k时,才将inner添加到result中。同时,添加完毕以后,先将最后一个元素删除,然后就可以直接continue,结束本次循环。

4.n个元素的全排列

按照我们之前的分析,n个不重复的元素,全排列的情况总共为 n ! n! n!种。假设数组{1, 2, 3},那么全排列一共有6种情况。

还是先上代码

import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; public class PermutationN { public static int[] nums = {1, 2, 3}; public static ArrayList 
  
    > result = new ArrayList<>(); public static void permuation(ArrayList 
   
     inner) { if (inner.size() == nums.length) { result.add(new ArrayList<>(inner)); return; } for(int i=0; i 
    
      inner = new ArrayList<>(); permuation(inner); for(ArrayList 
     
       each: result) { System.out.println(StringUtils.join(each, ",")); } } } 
      
     
    
  

思路是不是还算比较清晰。同样的,如果看上去有点晕,也建议大家去IDE中debug,观察一下函数递归调用的整个流程。

inner.contains(nums[i]) 这一行起的作用,是判断该元素有没有被访问,实际中更常见的另外一种写法是用一个visit数组来记录元素被访问的情况,下面我们采用visit数组的写法来表示一下。

import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; public class PermutationN { public static int[] nums = {1, 2, 3}; public static ArrayList 
  
    > result = new ArrayList<>(); public static boolean[] visit = new boolean[nums.length]; public static void permuation(ArrayList 
   
     inner, boolean[] visit) { if (inner.size() == nums.length) { result.add(new ArrayList<>(inner)); return; } for(int i=0; i 
    
      inner = new ArrayList<>(); permuation(inner, visit); for(ArrayList 
     
       each: result) { System.out.println(StringUtils.join(each, ",")); } } } 
      
     
    
  

5.n个有重复元素的全排列

import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.Arrays; public class PermutationDuplicate { public static int[] nums = {1, 2, 1}; public static ArrayList 
  
    > result = new ArrayList<>(); public static boolean[] visit = new boolean[nums.length]; public static void permuation(ArrayList 
   
     inner, boolean[] visit) { if (inner.size() == nums.length) { result.add(new ArrayList<>(inner)); return; } for(int i=0; i 
    
      0 && nums[i] == nums[i-1] && !visit[i-1]) { continue; } inner.add(nums[i]); visit[i] = true; permuation(inner, visit); inner.remove(inner.size()-1); visit[i] = false; } } public static void main(String[] args) { Arrays.sort(nums); ArrayList 
     
       inner = new ArrayList<>(); permuation(inner, visit); for(ArrayList 
      
        each: result) { System.out.println(StringUtils.join(each, ",")); } } } 
       
      
     
    
  
 if (i > 0 && nums[i] == nums[i-1] && !visit[i-1]) { continue; } 

6.套路总结

上面各个case挨个解完以后,下面我们来总结一下排列组合问题的套路。

先看排列问题:

result = [] def permutation(路径, 选择列表): if 满足结束条件: result.add(路径) return for 选择 in 选择列表: 做选择 permutation(路径, 选择列表) 撤销选择 

再看看组合问题

result = [] def permutation(路径, 选择列表): for 选择 in 选择列表: 做选择 permutation(路径, 选择列表) 撤销选择 

组合的套路,本质也是回溯法的使用。与排列稍微不一样的地方在于,组合问题的结束条件可以不用写出来,等循环结果即可。


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/179321.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午3:51
下一篇 2026年3月26日 下午3:52


相关推荐

  • java keydown_键盘事件keydown、keypress、keyup随笔整理总结

    java keydown_键盘事件keydown、keypress、keyup随笔整理总结英文输入法 事件触发顺序 keydown gt keypress gt keyup 中文输入法 firfox 输入触发 keydown 回车确认输入触发 keyupchrome 输入触发 keydown keyup 回车确认输入只触发 keydownIE 输入触发 keydown keyup 回车确认输入触发 keydown keyupSafari 输入触发 keydown keyup 回车确认输

    2026年3月16日
    2
  • 启动、关闭ubuntu Linux防火墙

    启动、关闭ubuntu Linux防火墙由于 LInux 原始的防火墙工具 iptables 过于繁琐 所以 ubuntu 默认提供了一个基于 iptable 之上的防火墙工具 ufw sudoufwstatu 检查防火墙的状态 sudoufwversi 防火墙版本 ubuntu 系统默认已安装 ufw 2 启用运行以上两条命令后 防火墙在系统启动时自动开启 关闭所有外部对本机的访问 但本机访问外部正常打开或关闭某个端口 例如 sudoufwallow 允许所有的外部 IP 访问本机的 25 tcp smtp 端口 sudo

    2025年10月27日
    6
  • jsNavigator对象的讲解_javascript自定义对象

    jsNavigator对象的讲解_javascript自定义对象 JSnavigator对象 转自:http://blog.163.com/tgaosh@126/blog/static/139818624201012651556709/ navigator是一个独立的对象,他用于提供用户所使用的浏览器以及操作系统等信息,以navigator对象属性的形式来提供。————————————-…

    2025年9月2日
    11
  • 安卓支付宝抢红包脚本软件

    安卓支付宝抢红包脚本软件软件使用条件 1 安卓手机 2 已经 ROOT3 没有了 ps 软件可能会提示不支持你的分辨率请无视 使用方法 1 下载软件打开 小米手机可能要设置下悬浮窗 2 给予软件 root 权限 3 点启动 4 打开支付宝等到倒计时结束马上按音量 键 5 抢红包结束马上按音量 ps 由于用的是找色代码结束没及时按音量 软件会乱点

    2026年3月19日
    2
  • 迭代法求行列式(线性代数公式)

    线性代数行列式计算之迭代法声明与简介线性代数行列式计算之迭代法是利用行列式逐阶展开式会发现或总结出n阶和n-1阶、n-2阶以及剩余阶的关系式,进而推算出整个行列式的最终结果。比如可以由或反过来(),总之能找出一个逐级演变的推导关系式。迭代法又称之为递推法。迭代法正向迭代根据给的行列式可以直观的找出n阶和n-1阶的关系式,这种方法叫做直接迭代法。详见如下示例:计算n阶行列式:#1思路Step1先观察行列式的特点,再整理思路Step2如果我们对第…

    2022年4月11日
    42
  • 浅析@ResponseBody注解作用和原理

    浅析@ResponseBody注解作用和原理    @ResponseBody这个注解通常使用在控制层(controller)的方法上,其作用是将方法的返回值以特定的格式写入到response的body区域,进而将数据返回给客户端。当方法上面没有写ResponseBody,底层会将方法的返回值封装为ModelAndView对象。    假如是字符串则直接将字符串写到客户端,假如是一个对象,此时会将对象转化为json串然后写到客户…

    2022年5月28日
    50

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号