剪邮票
剪邮票
题目:
如【图1.jpg】, 有12张连在一起的12生肖的邮票。
现在你要从中剪下 55 张来,要求必须是连着的。
(仅仅连接一个角不算相连) 比如,【图2.jpg】,【图3.jpg】中,粉红色所示部分就是合格的剪取。
请你计算,一共有多少种不同的剪取方法。
知识点:
- 全排列+去重
- DFS
- 判断有几个连通块
知识点一:全排列
所谓全排列,就是打印出字符串中所有字符的所有排列。例如输入字符串abc
,则打印出 a、b、c 所能排列出来的所有字符串 abc
、acb
、bac
、bca
、cab
和 cba
。
方法一:暴力
给出的数比较少可以用暴力循环求解,以1,2,3为例
public static void main(String[] args) {
for(int i=1;i<=3;i++)
{
for(int j=1;j<=3;j++) {
for(int k=1;k<=3;k++) {
if(i!=j&&j!=k&&k!=i) {
System.out.println(i+" "+j+" "+k);
}
}
}
}
}
方法二: DFS
public class Main1 {
static int[] a = { 1, 1, 3, 4 };
static int[] path = new int[4];// 存放结果的数组
static boolean[] flag = new boolean[4];// 判断该数是否已经被用过
static int res = 0;
public static void main(String[] args) {
dfs(0);
System.out.println(res);
}
static void dfs(int step) {
if (step == 4) {
for (int i = 0; i < 4; i++) {
System.out.print(path[i] + " ");
}
res++;
System.out.println();
return;
}
for (int i = 0; i < 4; i++) {
if (flag[i] == false) {//没有被用过的元素可以抓入到path中
flag[i] = true;//标记为已访问过
path[step] = a[i];//将a[i]放入path[step]中
dfs(step + 1);//递归
flag[i] = false;//回溯
}
}
return;
}
}
去重:
如果a
中元素有重复的话,全排列也会有重复的
如何避免重复?
相同元素抓取时,无论先抓哪个造成的结果都是一样的,规定在抓取相同元素时,排列在前的元素先被抓,要避免抓取一个元素时,前面有与之相同的元素未被抓取。
在dfs中加入判断
if (i > 0 && a[i] == a[i - 1] && flag[i - 1] == false)//现在准备选取的元素和上一个元素相同,但是上一个元素还没被使用
continue;
static void dfs(int step) {
if (step == 4) {
for (int i = 0; i < 4; i++) {
System.out.print(path[i] + " ");
}
res++;
System.out.println();
return;
}
for (int i = 0; i < 4; i++) {
if (i > 0 && a[i] == a[i - 1] && flag[i - 1] == false)
continue;
if (flag[i] == false) {
flag[i] = true;
path[step] = a[i];
dfs(step + 1);
flag[i] = false;
}
}
return;
}
方法三:交换法
给定任意个数字,求其全排列
助于理解的点
public class Main1 {
static int[] a = { 1, 1, 3, 4 };
static int len;
public static void main(String[] args) {
len = a.length;
dfs(0);
}
static void dfs(int step) {
if (step == len) {
for (int i = 0; i < len; i++)
System.out.print(a[i] + " ");
System.out.println();
return;
}
for (int i = step; i < len; i++) {
int t = a[i];
a[i] = a[step];
a[step] = t;
dfs(step + 1);
t = a[i];
a[i] = a[step];
a[step] = t;
}
return;
}
}
去重
去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。用编程的话描述就是第i个数与第j个数交换时,要求[i,j)中没有与第j个数相等的数。
public class Main1 {
static int[] a = { 1, 1, 3, 4 };
static int len;
public static void main(String[] args) {
len = a.length;
dfs(0);
}
// 判断是否要交换
static boolean isSwap(int start, int end) {
for (; start < end; start++) {
if (a[start] == a[end])
return false;
}
return true;
}
static void dfs(int step) {
if (step == len) {
for (int i = 0; i < len; i++)
System.out.print(a[i] + " ");
System.out.println();
return;
}
for (int i = step; i < len; i++) {
if (isSwap(step, i)) {
int t = a[i];
a[i] = a[step];
a[step] = t;
dfs(step + 1);
t = a[i];
a[i] = a[step];
a[step] = t;
}
}
return;
}
}
思路:
一开始有一个误区:觉得可以直接DFS ,×
DFS完成不了T型的,如
因为DFS一下子只能往一个方向走,不能同时在x和y方向上走,如果先1,2,3,4,在回溯到6,3和4会被回溯消失。
所以,可以将问题分为两部分:
-
12个邮票任选5个
- 全排列去重问题,在12个里选5个不重复的
-
这5个是否满足题意,满足即为一种解法,让计数变量ans++;
-
将一维数组
a
转化为二维数组g
,再判断这五个方块是否为连通块static int[][] next = { { 1, 0 }, // 向下 { -1, 0 }, // 向上 { 0, 1 }, // 向右 { 0, -1 }// 向左 }; // 判断有几个连通块 static void dfs(int i, int j) { g[i][j] = 0; for (int i1 = 0; i1 < 4; i1++) { int x = i + next[i1][0]; int y = j + next[i1][1]; if (x >= 0 && x < 3 && y >= 0 && y < 4 && g[x][y] == 1) dfs(x, y); } }
-
Code(DFS求全排列)
public class Main {
static long res = 0;
static int path[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
static boolean[] flag = new boolean[12];
static int a[] = { 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 };// 它的每个排列代表12选5的1个方案
static int[][] g = new int[3][4];
static int[][] next = { { 1, 0 }, // 向下
{ -1, 0 }, // 向上
{ 0, 1 }, // 向右
{ 0, -1 }// 向左
};
// 判断有几个连通块中的dfs
static void dfs(int i, int j) {
g[i][j] = 0;
for (int i1 = 0; i1 < 4; i1++) {
int x = i + next[i1][0];
int y = j + next[i1][1];
if (x >= 0 && x < 3 && y >= 0 && y < 4 && g[x][y] == 1)
dfs(x, y);
}
}
// 判断有几个连通块
static boolean check() {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
if (path[i * 4 + j] == 1)
g[i][j] = 1;
else
g[i][j] = 0;
}
}
int cnt = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
if (g[i][j] == 1) {
dfs(i, j);
cnt++;
}
}
}
return cnt == 1;
}
// 全排列
static void df(int k) {
if (k == 12) {
if (check())
res++;
return;
}
for (int i = 0; i < 12; i++) {
if (i > 0 && a[i] == a[i - 1] && flag[i - 1] == false)
continue;
if (flag[i] == false) {
flag[i] = true;
path[k] = a[i];
df(k + 1);
flag[i] = false;
}
}
return;
}
public static void main(String[] args) {
df(0);
System.out.println(res);
}
}
Code(交换法求全排列)
public class Main{
static long res = 0;
static int a[] = { 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 };// 它的每个排列代表12选5的1个方案
static int[][] g = new int[3][4];
static int[][] next = { { 1, 0 }, // 向下
{ -1, 0 }, // 向上
{ 0, 1 }, // 向右
{ 0, -1 }// 向左
};
// 判断有几个连通块中的dfs
static void dfs(int i, int j) {
g[i][j] = 0;
for (int i1 = 0; i1 < 4; i1++) {
int x = i + next[i1][0];
int y = j + next[i1][1];
if (x >= 0 && x < 3 && y >= 0 && y < 4 && g[x][y] == 1)
dfs(x, y);
}
}
// 判断有几个连通块
static boolean check() {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
// 将1维数组转化为2维数组
if (a[i * 4 + j] == 1)
g[i][j] = 1;
else
g[i][j] = 0;
}
}
int cnt = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
if (g[i][j] == 1) {
// dfs几次则代表有几个连通块
dfs(i, j);
cnt++;
}
}
}
return cnt == 1;
}
// 判断是否要交换
static boolean isSwap(int start, int end) {
for (; start < end; start++) {
if (a[start] == a[end])
return false;
}
return true;
}
// 全排列
static void df(int k) {
if (k == 12) {
if (check())
res++;
return;
}
for (int i = k; i < 12; i++) {
if (isSwap(k, i)) {
int t = a[i];
a[i] = a[k];
a[k] = t;
df(k + 1);
t = a[i];
a[i] = a[k];
a[k] = t;
}
}
return;
}
public static void main(String[] args) {
df(0);
System.out.println(res);
}
}
参考视频:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)