蓝桥杯Python组排列和组合、二进制讲解

文章详细介绍了Python中用于排列和组合的itertools库中的permutations()和combinations()函数,包括它们的使用方法、输出顺序以及常见易错点。此外,还讨论了手写排列和组合代码的方法,并给出了几个例题的解题思路,涉及lanqiaoOJ的编程题目。
摘要由CSDN通过智能技术生成

目录

一、排列

1、Python 的排列函数 permutations()

2、permutations() 按什么顺序输出序列(重要⭐)

3、易错点

二、组合

1、Python的组合函数combinations()

2、注意内容

三、手写排列和组合代码

1、手写排列代码(暴力法)

2、手写组合代码(暴力法)

3、手写组合代码(二进制法)

4、输出n个数中任意m个数的组合

5、输出 n 个数的任意组合(所有子集)

四、例题讲解

1、排列序数(lanqiaoOJ题号269)

1)用 sort() 函数

2)用 sorted() 函数。sorted() 能直接在字符串的内部排序

2、拼数(lanqiaoOJ题号782)

3、火星人(lanqiaoOJ题号572)

4、带分数(lanqiaoOJ题号208)


一、排列

1、Python 的排列函数 permutations()

  • itertools.permutations(iterable, r=None)
  • 功能:连续返回由 iterable 序列中的元素生成的长度为 r 的排列。
  • 如果 r 未指定或为 None,默认设置为 iterable 的长度,即生成包含所有元素的全排列。
from itertools import *
s=['a','b','c']
for element in permutations(s,2):
    # print(element)
    a=element[0]+element[1]
    # 或者这样写:a=''.join(element)
    print(a,end=' ')

2、permutations() 按什么顺序输出序列(重要⭐)

  • 答:按元素的位置顺序输出元素的排列,也就是说,输出排列的顺序是位置的字典序。例如 s = ['b','a','c'],执行 permutations(s),输出 "bac bca abc acb cba cab" ,并不是按字符的字典序输出排列,而是按位置顺序输出。
  • s=['b','a','c'] 的 3 个元素的位置是 'b'=1、'a'=2、'c'=3,输出的排列 “bac bca abc acb cba cab”,按位置表示就是“123 132 213 231 312 321”,这是按从小到大的顺序输出的。
from itertools import *
s=['b','a','c']
for element in permutations(s):
    #print(element)
    a=''.join(element)
    print(a,end=' ')

如果有相同的元素,不同位置的元素被认为不同。例如 s=['a', 'a', 'c'],执行 permutations(s),输出 "ааc aca aaс аcа cаа cаа".

from itertools import *
s=['a','a','c']
for element in permutations(s):
    #print(element)
    a=''.join(element)
    print(a,end=' ')

3、易错点

初学者容易犯一个错误,把元素当成了位置。例如 s = ['1', '3', '2'],执行 permutations(s),输出“132 123 312 321 213 231",看起来很乱,实际上是按 3 个元素的位置 '1'=1、'3'=2、'2'=3 输出有序的排列的。若需要输出看起来正常的 “123 132 213 231 312 321”,可以把 s=['1','3','2'] 先用 sort() 排序为 ['1', '2', '3'],再执行 permutations()。

from itertools import *
s=['1','3','2']
for element in permutations(s):
    #print(element)
    a=''.join(element)
    print(a,end=' ')

如何输出看起来正常的 “123 132 213 231 312 321”?

先把 s = ['1', '3', '2'],用 sort() 排序为 ['1', '2', '3'],再执行 permutations()

from itertools import *
s=['1','3','2']
s.sort()
for element in permutations(s):
    #print(element)
    a=''.join(element)
    print(a,end=' ')

二、组合

1、Python的组合函数combinations()

permutations() 输出的是排列,元素的排列是分先后的,“123” 和 “321” 不同。但是有时只需要输出组合,不用分先后,此时可以用 combinations() 函数。

from itertools import *
s=['1','3','2']
for element in combinations(s,2):
    #print(element)
    a=''.join(element)
    print(a,end=' ')

2、注意内容

如果序列 s 中有相同的字符,且 s 是用 [ ] 表示的数组,那么 s 中不同位置的元素被认为不同。

from itertools import *
s=['1','1','3','2']
for element in combinations(s,2):
    #print(element)
    a=''.join(element)
    print(a,end=' ')

如果要去重怎么办?用集合,s用 { } 表示。

from itertools import *
s={'1','1','3','2'}
for element in combinations(s,2):
    #print(element)
    a=''.join(element)
    print(a,end=' ')    # 多次输出的顺序是不定的

如果要去重且输出按字典序:先用 set() 去重,再转为 list,再排序

from itertools import *
s={'1','1','3','2'}
t=list(set(s))
t.sort()
#print(t)
for element in combinations(t,2):
    #print(element)
    a=''.join(element)
    print(a,end=' ')    # 多次输出的顺序是不定的

三、手写排列和组合代码

  • 在某些场景下,系统排列函数并不能用,需要手写代码实现排列组合。
  • 在 “DFS与排列组合” 中给出了基于 DFS 的手写方法。
  • 下面给出几种简单的手写方法。

1、手写排列代码(暴力法)

从 {1,2,3,4} 中选 3 个的排列,有 24 种。最简单直接无技巧的手写排列这样写:

s=[1,2,3,4]
for i in range(4):
    for j in range(4):
        if j!=i:
            for k in range(4):
                if k!=j and k!=i:
                    print("%d%d%d"%(s[i],s[j],s[k]),end=",")

【优缺点】简单且效果很好,但是非常笨拙。如果写 5 个以上的数的排列组合,代码冗长无趣。

2、手写组合代码(暴力法)

  • 排列数需要分先后,组合数不分先后。
  • 把求组合的代码,去掉 if,然后按从小到大打印即可。
  • 从 {1,2, 3,4} 中选 3 个的组合有 4 种。
s=[1,2,3,4]
for i in range(4):  
    for j in range(i+1,4):
        for k in range(j+1,4):
            print("%d%d%d"%(s[i],s[j],s[k]),end=",")

3、手写组合代码(二进制法)

一个包含 n 个元素的集合 {a0, a1, a2, a3, ..., an-1},它的子集有 {φ}, {a0}, {a1}, {a2}, ..., {a0, a1, a2}, ..., {a0, a1, a2, a3, ..., an-1},共 2n 个。

用二进制的概念进行对照是最直观的,子集正好对应了二进制。例如 n=3 的集合 {a0, a1, a2},它的子集和二进制数的对应关系是:

每个子集对应了一个二进制数。二进制数中的每个 1,对应了子集中的某个元素。而且,子集中的元素,是不分先后的,这正符合组合的要求。

下面的代码通过处理每个二进制数中的 1,打印出了所有的子集。

#include<bits/stdc++.h>
using namespace std;
int a[]={1,2,3,4,5,6,7,8,9,10,11,12,13,14};

void print_subset(int n){
	for(int i=0;i<(1<<n);i++){
		for(int j=0;j<n;j++){  //打印一个子集,即打印 i 的二进制数中所有的 1 
			if(i&(1<<j)){	   //从 i 的最低位开始,逐个检查每一位,如果是 1,打印 
				cout<<a[j]<<" ";
			}
		}	
		cout<<"; ";
	}
}

int main() {
	int n=3;
	print_subset(n);
	return 0;
}

输出:

; 1 ; 2 ; 1 2 ; 3 ; 1 3 ; 2 3 ; 1 2 3 ;

4、输出n个数中任意m个数的组合

  • 根据上面子集生成的二进制方法,一个子集对应一个二进制数:一个有m个元素的子集,它对应的二进制数中有m个1。
  • 所以问题转化为:查找 1 的个数为 m 个的二进制数,这些二进制数对应了需要打印的子集。
  • 如何判断二进制数中 1 的个数为 m 个? 简单的方法是对这个 n 位的二进制数逐位检查,共需要检查 n 次。

有一个更快的方法,可以直接定位二进制数中 1 的位置,跳过中间的 0。

用到一个神奇操作:k = k & (k-1),功能是消除 k 的二进制数的最后一个 1。连续进行这个操作,每次消除一个 1,直到全部消除,操作次数就是 1 的个数。例如二进制数 1011,经过连续 3 次操作后,所有 1 都消除了:

1011 & (1011 - 1) = 1011 & 1010 = 1010

1010 & (1010 - 1) = 1010 & 1001 = 1000

1000 & (1000 - 1) = 1000 & 0111 = 0000

利用这个操作,可以计算出二进制数中 1 的个数。用 num 统计1的个数,具体步骤是:

1)用 k=k & (k-1) 清除 k 的最后一个 1;

2)num++;

3)继续上述操作,直到k=0。

5、输出 n 个数的任意组合(所有子集)

输出按字典序输出,从小到大。(下面的代码已经说明了二进制的精髓)

a=[1,2,3,4,5,6,7,8,9,10,11,12,13,14]
def print_set(n,m):
    for i in range(2**n):   #2**n可以写成1<<n
        num,k=0,i           #num统计i中1的个数,k用来处理i
        while k>0:
            k=k&(k-1)       #清除k的最后一个1
            num+=1
        if num==m:
            for j in range(n):
                if i&(2**j):
                    print(a[j],end="")
            print(";",end='')
n,m=4,3
print_set(n,m)

四、例题讲解

1、排列序数(lanqiaoOJ题号269)

【题目描述】

如果用 a b c d 这 4 个字母组成一个串,有 4!=24 种。现在有不多于 10 个两两不同的小写字母,给出它们组成的串,你能求出该串在所有排列中的序号吗?

【输入描述】输入一行,一个串。

【输出描述】输出一行,一个整数,表示该串在其字母所有排列生成的串中的序号。注意:最小的序号是 0。下面给出两种代码,分别用 sort() 和 sorted() 排序,然后用permutions()求排列。

1)用 sort() 函数

sort() 不能直接在字符串内部排序。可以这样:把字符串转换成数组,对数组排序后,再转换回字符串,就得到了最小字符串。

from itertools import *
olds=input()
news=list(olds)
news.sort()
cnt=0
for ele in permutations(news):
    a=''.join(ele)  #把所有元素拼回成字符串
    if olds==a:
        print(cnt)
        break
    cnt+=1

2)用 sorted() 函数。sorted() 能直接在字符串的内部排序

from itertools import *
olds=input()
news=sorted(olds)
print(olds,news)
a=[]
for ele in permutations(news):
    a.append(ele)
print(a.index(tuple(olds)))

2、拼数(lanqiaoOJ题号782)

【题目描述】

设有 n 个正整数 a1, a2, ..., an,将它们联接成一排,相邻数字首尾相接,组成一个最大的整数。 n<20。

最简单粗暴的方法,是先得到这 n 个整数的所有排列,然后找其中最大的。但是这个方法的复杂度是 O(n!),当 n =20 时,有 20! =2×1018 种排列,超时。

from itertools import *
N=int(input())
ans=""
nums=list(map(str,input().split())) #按字符的形式读入
for ele in permutations(nums):  #每次输出一个全排列
    a="".join(ele)
    #print(a)
    if ans<a:
        ans=a       #在所有串中找最大的
print(ans)
    

暴力排列不行,可以用排序吗? 本题不能直接对数字排序然后首尾相接,例如“7, 13”,应该输出“713”,而不是“137”。注意到这其实是按两个数字组合的字典序排序,也就是把数字看成字符串来排序。本题的 n 很小,用较差的排序算法也行,例如交换排序。第 3~6 行用交换排序对所有的数 (按字符串处理) 进行排序,复杂度 O(n^2)。

n=int(input())
nums=input().split()    #按字符读入
# print(nums)   
for i in range(0,n-1):  #交换排序
    for j in range(i+1,n):
        if nums[j]+nums[i]>nums[i]+nums[j]:
            nums[j],nums[i]=nums[i],nums[j]
print(''.join(nums))

3、火星人(lanqiaoOJ题号572)

【题目描述】

给出 N 个数的排列,输出这个排列后面的第 M 个排列。

【输入描述】

第一行有一个正整数 N,1<=N<=10000。第二行是一个正整数 M。下一行是 1 到 N 个整数的一个排列,用空格隔开。

【输出描述】

输出一行,这一行含有 N 个整数,表示原排列后面第 M 个排列。每两个相邻的数中间用一个空格分开,不能有多余的空格。

用 Python 编码比较麻烦,因为 Python 的 permutations() 函数是按元素位置进行排列的输出的。只能这样编码:先把 n 个数排序成最小排列,然后从这个最小排列开始 permutations(),遇到题目给定的起始排列后,再往后数到第m个排列,输出。但是这个代码会超时,因为浪费了很多计算。

from itertools import *
from copy import *
n=int(input())
m=int(input())
nums=list(map(str,input().split()))
back=deepcopy(nums)
k=0
flag=0
nums.sort()
for ele in permutations(nums):
    if list(ele)==back:
        flag=1
    if flag==1:
        if k==m:
            a=''.join(ele)
            print(a)
            break   #退出for循环!
        k+=1

一种高效的方法:从当前排列开始,暴力地寻找下一个排列。对于当前排列,从后往前比较,寻找 nums[i-1] < nums[i] 的位置,把 nums[i-1] 与 i 到末尾中比 nums[i-1] 大的最小数交换,再将 i-1 之后的数进行翻转 (从小到大排序),可以得到比当前排列大的最小排列。

n=int(input())
m=int(input())
nums=list(map(int,input().split()))

def find_next(nums):
    for i in range(n-1,0,-1):
        if nums[i]>nums[i-1]:
            for j in range(n-1,i-1,-1):
                if nums[j]>nums[i-1]:
                    nums[j],nums[i-1]=nums[i-1],nums[j]
                    return nums[:i]+nums[:i-1:-1]

for i in range(m):
    nums=find_next(nums)
print(''.join([str(i) for i in nums]))

4、带分数(lanqiaoOJ题号208)

【题目描述】

100 可以表示为带分数的形式:100 = 3 + 69258 / 714。还可以表示为:100 = 82 + 3546 / 197。

注意特征:带分数中,数字 1~9 分别出现且只出现一次(不包含0)。

类似这样的带分数,100 有 11 种表示法。输入一个整数,输出它有多少种表示法。

【输入描述】

从标准输入读入一个正整数 N (N < 1000×1000)。

【输出描述】

程序输出该数字用数码 1~9 不重复不遗漏地组成带分数表示的全部种数。

典型的排列题。题目中说 “数字 1~9 分别出现且只出现一次”,用暴力排列:对所有 1~9 的排列,验证有几个符合要求。9 个数只有 9!=362880 种排列,不会超时。

from itertools import *
n=int(input())
bit=len(str(n))     #n的位数
cnt=0
for num in permutations("123456789"):
    a,b,c=0,0,0
    for a1 in range(bit):   #a1: a的位数,a肯定比n短
        a=int("".join(num[:a1+1]))  #一个a
        bLast=(n-a)*int(num[-1])%10  #b的尾数,(n-a)c%10
        if bLast==0:        #b的尾数不可能等于0,因为只用到1~9
            continue
        b1=num.index(str(bLast))    #根据b的尾数,确定b的长度
        if b1<=a1 or b1>=8:
            continue
        b=int(''.join(num[a1+1:b1+1]))
        c=int(''.join(num[b1+1:]))
        if b%c==0 and n==a+b//c:
            cnt+=1
print(cnt)

以上,蓝桥杯Python组排列和组合、二进制讲解

祝好

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吕飞雨的头发不能秃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值