2016 第七届 蓝桥杯 JavaB组 剪邮票——全排列和dfs

题目描述

如【图1.jpg】, 有12张连在一起的12生肖的邮票。
现在你要从中剪下5张来,要求必须是连着的。
(仅仅连接一个角不算相连)
比如,【图2.jpg】,【图3.jpg】中,粉红色所示部分就是合格的剪取。

请你计算,一共有多少种不同的剪取方法。

请填写表示方案数目的整数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
思路:该题要是直接用dfs来做的话会遗漏掉一下情况,就如下图这样的
在这里插入图片描述
假如就是剪红色部分的,但你无论从哪来作为起点进行dfs,都不可能得到上图的这种情况的,因为假如是从5开始,从5->6->7之后,他只能选择向上、向下和向左。不可能再返回到6,再从6开始向上或向下的。

既然这种方案不可能,我们选用dfs的初衷就是为了找到五个点,那么我们可以先将这五个点在这个方格中的随机排列(且不重复),就像扔飞镖一样,在这12个区域中击中五个区域。将所有的情况都存起来,然后对这些区域的分布情况进行连通性判断(这里就可以直接使用dfs了)

对于五个点在这个12个格子的随机排列可以使用全排列的方法来进行排列组合出不重复的情况来(对于含重复元素的全排列可参考:LeetCode-46.全排列 II)

代码如下:

public class Question_07 {
    //设置全排列的数组(1表示需要剪的位置)
    static int[] num = {1,1,1,1,1,0,0,0,0,0,0,0};
    //dfs开始搜索的起点坐标
    static int x;
    static int y;
    //上下左右
    static int[] xx = {-1,1,0,0};
    static int[] yy = {0,0,-1,1};
    //剪取的方法
    static int count=0;
    //dfs的次数,初始为1,当次数到达5时说明该五个点是连通的
    static int times;
    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<Integer>();
        //先将初始的全排列数组存入list集合中
        for (int n:num){
            arrayList.add(n);
        }

        int length = num.length;
        //用于存放全排列的所有可能
        LinkedList<ArrayList<Integer>> list = new LinkedList<ArrayList<Integer>>();
        //回溯
        backtrack(length,arrayList,list,0);
        //依次从list中取出全排列的所有组合
        ArrayList<Integer> temp;
        int[][] num;
        int x1;
        int y1;
        while (list.size()>0){
            //从list中弹出一种全排列的组合
            temp=list.poll();
            //调研put方法将组合全排列的组合转换成二维数组(3行4列)
            num = put(temp);
            //初始化dfs开始搜索时的坐标
            x1=x;
            y1=y;
            //查找次数设置为1
            times=1;
            //将起始坐标设为0,表示已访问过
            num[x1][y1]=0;
            //从num[x1][y1]开始按上下左右的顺序进行dfs,判断是否有连通
            dfs(x1,y1,num);
        }
        System.out.println(count);
    }

    private static void dfs(int x,int y,int[][] num) {
        if (times==5){
            count++;
            return;
        }
        for (int i=0;i<4;i++){
            //上下左右进行遍历
            int x1 = x+xx[i];
            int y1 = y+yy[i];
            if (x1>=0&&x1<3&&y1>=0&&y1<4&&num[x1][y1]==1){
                times++;
                num[x1][y1]=0;
                dfs(x1,y1,num);
            }
        }

    }

    //将全排列好的list集合转换成二维数组来
    private static int[][] put(ArrayList<Integer> temp) {
        int[][] stamp = new int[3][4];
        int index=0;
        boolean flag = true;
        for (int i=0;i<3;i++){
            for (int j=0;j<4;j++){
                stamp[i][j]=temp.get(index);
                if (stamp[i][j]==1&&flag){
                    //先找出下标为1的坐标来
                    x=i;
                    y=j;
                    flag=false;
                }
                index++;
            }
        }
        return stamp;
    }

    private static void backtrack(int length, ArrayList<Integer> arrayList,List<ArrayList<Integer>> list ,int first) {
        //表示回溯到最后
        if (first==length){
            list.add(new ArrayList<Integer>(arrayList));
            return;
        }
        for (int i=first;i<length;i++){
            //每次全排序之前判断是否有重复的
            if (canSwap(arrayList,first,i)){
                //将下标为i与first进行交换
                Collections.swap(arrayList,i,first);
                //交换后进行回溯
                backtrack(length,arrayList,list,first+1);
                //回溯后在换回来
                Collections.swap(arrayList,i,first);
            }
        }
    }
    private static boolean canSwap(ArrayList<Integer> list,int begin,int end){
        for(int i=begin;i<end;i++){
            if(list.get(i)==list.get(end)){
                return false;
            }
        }
        return true;
    }
}

正确答案是:116

发布了85 篇原创文章 · 获赞 9 · 访问量 6714

猜你喜欢

转载自blog.csdn.net/weixin_43517302/article/details/104601340
今日推荐