参考视频bilibil fjnuzs
一、全排列
1.题目分析
大概意思就是给你一堆数字,把他们的全排列输出来。不要保存,直接就是一棵多分枝的树,每一层每一层走下去,到了树叶的时候输出,再返回到上一层去其他分支。也可理解为递归的时候,上一层(数组前面部分)都固定了,当前这层你把每个数都试了一遍。
2.伪代码
代码如下(示例):
设一共有n层,目前在t层,A数组去记录当前排列
fun(t)
if(t=n)
print(A)
else
for 每个数字都遍历一遍
A[t] <-> A[i]
fun(t+1)
A[i]<->A[t]
举个例子,输入1234567
假设在2这一层了,那么2这个位置的和134567都换了一遍。假设某一次,它被换成某一个数比如4时,前面就确定了14,3又开始和142567换……递归下去。
然后我们讨论一下为什么要换回来,有点类似于回到树的上一层,简单来说如果不换回来会出现相同的排列的数。当他到了第7层输出了之后,假设输出7654321,换回来是在1654327,接下来时7和65432分别换了。不换的话继续循环,是1可以和65432换,是很不一样的。
二、格雷码
对于给定的正整数n,格雷码为满足如下条件的一个编码序列:
(1) 序列由2n个编码组成,每个编码都是长度为n的二进制位串。
(2) 序列中无相同的编码。
(3) 序列中位置相邻的两个编码恰有一位不同。
例如:n=2时的格雷码为:{00, 01, 11, 10}。
思路:和全排列是一个道理。用树结构存储每一个01结点,可以得到一棵完全二叉树,这样就能用数组存储这棵树。为了使序列中位置相邻的两个编码恰有一位不同,一棵树子节点01则旁边一个为10。采用先序遍历,保留父节点,每到一个子节点就能输出一个格雷码。
通过0110重复的规律还能对二叉树进行裁枝,从下标为6开始,将下标对4求余后能得到正确的元素。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int times = input.nextInt();
for(int k=0; k<times; k++) {
int num = input.nextInt();
ArrTree t = new ArrTree(num);
t.order();
}
}
}
class ArrTree{
private int[] tree = {2,0,1, 0,1,1,0};
private int[] arr;
private int num;
private int len;
public ArrTree(int num) {
this.num = num;
this.len = (int) Math.pow(2,num+1)-2;
this.arr = new int[num];
}
public void print(int[] arr) {
for(int i=0; i<arr.length; i++) {
System.out.printf("%d ",arr[i]);
}
System.out.print("\n");
}
public void order() {
//index = 1树的下标
//i = 0存当前二进制数的数组下标
order(0, 1);
order(0, 2);
}
public void order(int i, int index) {
if(index > 6) {
arr[i] = tree[(index-7)%4+3];
}else {
arr[i] = tree[index];
}
if(i == num-1) {
print(arr);
return;
}
//左递归
if((index*2+1) < len) {
order(i+1, index*2+1);
}
//右递归
if((index*2+2) <= len) {
order(i+1, index*2+2);
}
}
}
三、邮票问题
设有n种不同面值a1, a2,…, an的邮票,规定每封信最多贴m张邮票。对于给定的m,n,求出最大的邮资连续区间。例如,给定n=3,m=3,邮票面值分别为2, 3, 5,则最大的邮资连续区间为[2,13]。
输入:输入的第一行是一个正整数n,表示测试例个数。接下来几行是n个测试例的数据,每个测试例的数据由两行组成,其中第一行含两个正整数n和m (1<=n, m<=10),表示有n种邮票,每封信最多贴m张邮票;第二行含n个正整数,表示n种邮票的面值。同一行整数之间用一个空格隔开。
思路:采用深度优先遍历,不贴邮票表示为0,这样每一层可以循环四次,每次循环到了最后一层就去记录这个数。记录方式是,以全部贴最大面值时的值为一个数组最后一个元素下标;当找到对应下标,就把这个元素置为1。最后将这个数组遍历一次记录连续为1的长度和起始下标。
import java.util.Arrays;
import java.util.Scanner;
public class Stamp {
static int n;
static int m;
static int[] tree;
//static int count = 0;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int times = in.nextInt(); //测试次数
for(int t=0; t<times; t++) {
n = in.nextInt(); //n种邮票
m = in.nextInt(); //最多贴m张
tree = new int[n+1];
tree[0] = 0; //不贴上去
for(int i=1; i<n+1; i++) {
tree[i] = in.nextInt();
}
System.out.println(Arrays.toString(tree));
int[] value = new int[tree[n]*m+1];
dfs(1, 0, value);
System.out.println(Arrays.toString(value));
Search(value);
}
}
public static void dfs(int t, int sum, int[] res) {
for(int i=0; i<=n; i++) {
sum += tree[i];
if(t == m) {
res[sum] = 1;
sum -= tree[i];
}else {
dfs(t+1, sum, res);
sum -= tree[i];
}
}
}
public static void Search(int[] a) {
int lastLen = 1;
int lastIndex = 1;
int thisLen = 0;
int thisIndex = 1;
boolean flag = true;
for(int i=1; i<a.length; i++) {//把0屏蔽掉
if(a[i] == 1) {//连续相加
++thisLen;
if(flag) {
thisIndex = i;
flag = false;
}
}else {//断开记录
if(lastLen < thisLen) {
lastLen = thisLen;
lastIndex = thisIndex;
flag = true;
thisLen = 0;
}
}
}
System.out.printf("%d %d", lastIndex, lastIndex+lastLen-1);
}
}