深度优先搜索(dfs)例题总结

<font size="6px">
一、部分和问题</font>

题目描述:给定整数序列a1,a2,.....,an,判断是否可以从中选出若干数,使他们的和恰好为k。
       1<=n<=20
       -10^8 <= ai <= 10^8
       -10^8 <= k <= 10^8
样例:
输入
       n=4
       a={1,2,4,7}
       k=13
输出
       Yes (13=2+4+7)


思路分析:这道题目首先按照题目进行输入。之后调用dfs1()方法,将数组,k,以及数组的首位置传过去(作为0状态)。然后开始遍历位置,每一个位置都面临两种选择,第一种选择是取这个位置的值,第二种选择是不取这个位置的值。第一种选择不取这个位置的值就去查看下一个位置的值,就对应代码dfs1(arr,k,current+1);,只有状态加一,其余不变。第二种选择取这个位置的值,就将该位置的值存入list中,并获得index。然后调用代码dfs1(arr,k-arr[current],current+1);,可见k的值减去该位置的值,同样状态+1。这题有一个必要的点是要记得回溯,如果这条路走不通,要回溯到原来的状态,就是将该位置的值在list中删除,对应代码list.remove(index); 。最后一步就是确定出口,当k等于0时就输出结果。当k<0或者current==arr.length时说明这条路走不通,返回继续寻找其他路径。

static int kk;
    static ArrayList<Integer> list=new ArrayList<>();           //存放计算过程
    //部分和问题
    public static void main(String[] args) {
        Scanner reader=new Scanner(System.in);
        
        int n=reader.nextInt();
        int[] arr=new int[n];
        for(int i=0;i<arr.length;i++) {                         //按题目输入
            arr[i]=reader.nextInt();
        }
        kk=reader.nextInt();
        
        dfs1(arr,kk,0);                                         //调用深度优先搜索

    }
    
    public static void dfs1(int[] arr,int k,int current) {
        if(k==0) {                                              //出口
            System.out.print("Yes (");
            System.out.print(kk+"=");
            for(int j=0;j<list.size();j++) {
                if(j==list.size()-1) {
                    System.out.print(list.get(j));
                }else {
                    System.out.print(list.get(j)+"+");
                }
            }
            System.out.println(")");
            System.exit(0);
        }
        if(current==arr.length||k<0) {                          //没有找到答案
            return;
        }
        
        dfs1(arr,k,current+1);                                  //第一种情况,不取,状态+1
        
        list.add(arr[current]);                                 //将该状态的数字存入list中
        int index=list.size()-1;                                //记录位置,方便回溯
        dfs1(arr,k-arr[current],current+1);                     //第二种情况,取,状态+1
        list.remove(index);                                     //回溯
    }

<font size="6px">
二、水洼数目问题</font>

题目描述:有一个大小为NM的园子,雨后积起了水,八连通的积水被认为是连接在一起的。请求出园子里总共有多少水洼?(八连通值得是下图中相对W的*的部分)
       ***
       *W*
       ***
限制条件:
       N,M<=100
样例:
输入
       N=10,M=12
园子如下图:

vfew

输出
       3
*

思路分析:这道题目首先按照题目进行输入,将园子存入字符数组中。之后循环遍历园子,查看那个点位存在水洼,就调用dfs2()方法去清除与该点连通的水洼。方法中,以(i,j)为起点,有8个位置可以行走,但是不能重复上一层,故要先清除(i,j)位上的水洼。然后8个位置分为就是k=-1,0,1,j=-1,0,1.然后要排除k=0,j=0的情况,并且注意越界。如果(i+k,j+q)存在连通水洼,则调用方法清除水洼。最后输出结果

// 水洼数目问题
    static char[][] arr;                            //字符数组,存放园子
    static int N, M;    
    static int count=0;                             //记录水洼数目                            
    
    public static void main(String[] args) {
        Scanner reader = new Scanner(System.in);
        N = reader.nextInt();
        M = reader.nextInt();
        arr = new char[N][];
        
        for (int i = 0; i < N; i++) {
            arr[i] = reader.next().toCharArray();   //初始化园子
        }
        
        for (int i = 0; i < N; i++) {               //循环遍历园子的每一个位置  
            for (int j = 0; j < M; j++) {
                if (arr[i][j] == 'W') {             //如果是W
                    dfs2(i, j);                     //调用函数,清除水洼
                    count++;                        //水洼数+1
                }
            }
        }
        System.out.println(count);
    }

    private static void dfs2(int i, int j) {
        arr[i][j] = '.';                            //清除该水洼
        
        //遍历查询与该点连通的水洼
        for (int k = -1; k < 2; k++) {              //-1,0,1        
            for (int q = -1; q < 2; q++) {          //-1,0,1    
                if (k == q && q == 0) {
                    continue;
                }
                if (i + k >= 0 && j + q >= 0 && i + k <= N - 1 && j + q <= M - 1) {         //限定范围
                    if (arr[i + k][j + q] == 'W') {         //如果存在连通的水洼
                        dfs2(i + k, j + q);                 //递归调用,清除水洼
                    }

                }
            }
        }
    }

<font size="6px">
三、n皇后问题</font>

题目描述:请设计一种算法,解决著名的n皇后问题。这里的n皇后问题指在一个nn的棋盘上放置n个棋子,使得每行每列和每条对角线上都只有一个棋子,求其摆放的方法数。
给定一个int n,请返回方法数,保证n小于等于15
*

思路分析:这道题目首先按照题目进行输入,创建一个整形数组,用来放每一行皇后的位置,调用dfs3()方法,从第0行开始遍历。因为有n行,要放n个皇后所以肯定是每行一个皇后,故for (int i = 0; i < n; i++)用来遍历每一行中的每一个位置,for (int j = 0; j < row; j++)用来遍历数组中之前每一行的皇后的位置。然后对(row,i)位置判断是否能放皇后,如果不能放,就置judge为false。判断的条件主要有三个。1、同一列上不能有多个皇后。2、正对角线上不能有多个皇后(正对角线上x和y之和相等)3、反对角线上不能有多个皇后(反对角线上y-x的差相等)。如果judge为true,就将该位置存入整形数组,递归调用下一行。如果方法无法进行下去,就要回溯。最后要设置出口。

//n皇后问题
    static int[] arr;                                       // 整形数组,用来存放每一行皇后的位置
    static int count;                                       // 记录有多少种解法

    public static void main(String[] args) {
        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt();
        arr = new int[n];

        dfs3(n, 0);                                         // 调用深度优先搜索

        System.out.println(count); // 打印结果
    }

    private static void dfs3(int n, int row) {
        if (row == n) {                                     //出口
            count++;
            return;
        }
        for (int i = 0; i < n; i++) {                       //遍历每一行中的每个位置
            
            boolean judge = true;                           //初始化
            
            for (int j = 0; j < row; j++) {                 //遍历row行以前的皇后的
                
                if (arr[j] == i || row + i == j + arr[j] || i - row == arr[j] - j) {    //判定该位置是否能放皇后
                    judge = false;                          //不能放皇后
                }
                
            }
            if (judge) {                                    //如果judge为true
                arr[row] = i;                               //将row行i号位放置皇后
                dfs3(n, row + 1);                           //递归调用下一行的皇后
                arr[row] = 0;                               //方法不成功,回溯
            }
        }
    }

<font size="6px">
四、素数环问题</font>

题目描述:对于正整数n,对1~n进行排列,使得相邻两个数之和均为素数,输出时从整数1开始,逆时针排列。同一个环应恰好输出一次
n<=16
如输入:6
输出:
1 4 3 2 5 6
1 6 5 2 3 4

思路分析:这道题目首先按照题目进行输入,创建一个整形数组,用来存放素数环每一个位置的数字。首先将1存入数组,然后调用dfs4()方法。for (int i = 1; i < n + 1; i++)尝试将每一个数字填入到数组中,然后for (int j = 0; j < count; j++)循环遍历是否数组中已经存在这个数。如果不是,接着判断相邻两个数之和是否为素数,如果是素数,递归调用下一个数,将本次循环的数传给下一次循环,以便判断相邻的数。最后设置出口,要记得判断最后一个数字和第一个数字能否形成素数环,如果可以,就输出结果。

    // 素数环问题
    static int[] arr;                                       //整形数组,存放每一个位置的值
    static int count;                                       //数组中的个数

    public static void main(String[] args) {
        Scanner reader = new Scanner(System.in);
        int n = reader.nextInt();
        arr = new int[n];
        
        arr[0] = 1;                                         //将1存入数组
        count++;
        
        dfs4(n, 1);                                         //调用深度优先搜索
    }

    private static void dfs4(int n, int k) {
        if (count == n) {                                   //出口
            if (judgePrimeNumber(arr[0] + arr[n-1])) {      //判断最后一个数字和第一个数字之和是不是素数
                for (int i = 0; i < n ; i++) {
                    System.out.print(arr[i]);               //输出
                }
                System.out.println();
            }
            return;
        }
        for (int i = 1; i < n + 1; i++) {                   //尝试用每一个数加入到数组中
            boolean judge = true;
            for (int j = 0; j < count; j++) {               //判断这个数是否已经被选过
                if (i == arr[j]) {
                    judge = false;
                }
            }
            if (judge == true) {
                if (judgePrimeNumber(k+i)) {                //判断相邻的数之和是否为素数
                    arr[count] = i;                         //添加到数组中
                    count++;
                    dfs4(n, i);                             //递归调用下一个数
                    count--;
                    arr[count] = i;                         //回溯

                }
            }
        }

    }
    public static boolean judgePrimeNumber(int k) {          //判断是否为素数
        int q;
        for (q = 2; q < k; q++) {
            if (k % q == 0) {
                return false;
            }
        }
        return true;
    }

<font size="6px">
五、困难的串问题</font>

题目描述:如果一个字符串包含两个相邻的重复子串,则称它为容易的串,其他的串称为困难的串。
如:BB,ADCDACABCAB,ABCDABCD都是容易的串,D,DC,ADBAB,CBABCBA都是困难的。
输入正整数n,L,输出由前L个字符组成的,字典序第n小的困难的串。
例如,当L=3时,前7个困难的串分别为:
A,AB,ABA,ABAC,ABACA,ABACAB,ABACABA
n指定为4的话,输出ABAC

思路分析:这道题目首先按照题目进行输入,创建一个count用来存放第几个。调用dfs5()方法。按照字典序遍历,然后调用isHard()方法判断是否把i加进去也构成苦难的串,如果成立,就将i添加到prefix的末尾,count++,继续递归调用,直到最后输出。判断苦难的串的方法,是从后往前遍历。

    //困难的串
    static int L,n;                                     
    static int count=0;                                     //用来记录第几个
    public static void main(String[] args) {
        Scanner reader=new Scanner(System.in);
        n=reader.nextInt();
        L=reader.nextInt();
        dfs5("");                                           //调用深度优先搜索
    }
    private static void dfs5(String prefix) {
        for(int i='A';i<='A'+L+1;i++) {                     //按照字典序遍历
            if(isHard(prefix,i)) {                          //判断将i加进字符串中,是否还构成困难的串
                String x=prefix+i;                          //将i加入字符串中
                count++;
                if(count==n) {                              //出口
                    System.out.println(x);
                    System.exit(0);
                }
                dfs5(x);                                    //递归调用,继续寻找
            }
        }
        
    }
    private static boolean isHard(String prefix, int i) {    //判断将i加进字符串中,是否还构成困难的串
        int cnt=0;
        for(int j=prefix.length()-1;j>=0;j-=2) {             //从末至尾判断是否是苦难的串
            String x1=prefix.substring(j,j+cnt+1);
            String x2=prefix.substring(j+cnt+1)+i;
            if(x1.equals(x2)) {                              //是否相同,则为容易的串
                return false;
            }
            cnt++;
        }
        return true;
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容