成为dfs高手(一)

憧憬成为dfs高手

我对于dfs的理解可以分为三种,一种是在图/二维数组里对于路径的搜索的暴力;第二种是在树里去用dfs暴力拿一些路径;最后一种就是全排列对于做事顺序的暴力排列。这里主要讲后者。

dfs是一种暴力,搜索的艺术,而且做题也可以很公式化。

以最常见的全排列为例子

java 复制代码
import java.util.Scanner;

public class Main {
    static int n, a[] = new int[10005], f[] = new int[10005],jg[]=new int[10005];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        dfs(0);
    }
    //wz是第几个数字
    static void dfs(int wz) {
        if(wz>n)return;
        if (wz == n) {
            solve();
        }
        for (int i = 0; i < n; i++) {
            if (f[i] == 0) {
                f[i] = 1;
                jg[wz]=i;
                dfs(wz + 1);
                jg[wz]=0;
                f[i] = 0;
            }
        }
    }
    //调用临界解决问题的方法
    static void solve() {
        for (int i = 0; i < n; i++) {
            System.out.printf("%d ",jg[i]);
        }
        System.out.println();
    }
}

可以看到这里其实就已经可以抽象为模板,利用fi作为标记,jgi存数字来枚举全排列。

那么在这个基础还做到什么呢,其实靠着这个全排列就已经解决很多问题了,这个就是最基础的暴力,我们可以把这个全排列的数字来作为ai的索引再在solve函数里做一些逻辑判断来解决问题。

对了这里的全排列是不考虑结果递增顺序的所以会有很多顺序变了,但是实际结果可能意指的,比如不考虑操作顺序的动作里 12 3和 2 1 3是一样的,这个时候要考虑产生递增的操作顺序结果。

java 复制代码
import java.util.Scanner;

public class Main {
    static int n, a[] = new int[10005], f[] = new int[10005],jg[]=new int[10005];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        dfs(0,0);
    }
    //wz是第几个数字
    static void dfs(int wz,int ks) {
        if(wz>n)return;
        if (wz == n) {
            solve();
        }
        for (int i = ks; i < 10; i++) {
            if (f[i] == 0) {
                f[i] = 1;
                jg[wz]=i;
                dfs(wz + 1,i+1);
                jg[wz]=0;
                f[i] = 0;
            }
        }
    }
    //调用临界解决问题的方法
    static void solve() {
        for (int i = 0; i < n; i++) {
            System.out.printf("%d ",jg[i]);
        }
        System.out.println();
    }
}

上面的这个是在10个数字里选3个递增的数字,主要借助ks这个保存上一次的初始值,可以做到顺序不一项结果的暴力枚举。其实这里因为确定了起点fi去掉也没问题了,但是好习惯还是写上。

比如n皇后使用dfs来枚举所有棋子的坐标,这就等价于dfs来实现无法完成的n重for循环,因为实际写代码做不到写n重for循环。

java 复制代码
import java.util.Scanner;

public class Main {
    static int n, a[] = new int[10005], f[] = new int[10005],jg[]=new int[10005],sl;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        sl=n*n;
        dfs(0,0);
    }
    //wz是第几个数字
    static void dfs(int wz,int qs) {
        if (wz == n) {
            solve();
        }
        for (int i = qs; i < sl; i++) {
            if (f[i] == 0) {
                f[i] = 1;
                jg[wz]=i;
                dfs(wz + 1,i+1);
                f[i] = 0;
            }
        }
    }
    //调用临界解决问题的方法
    static void solve() {
        for (int i = 0; i < n; i++) {
            System.out.printf("%d %d\n",jg[i]/n,jg[i]%n);
        }
        System.out.println();
    }
}

我们只需要修改 for (int i = 0; i < sl; i++)这里就完成了n重for循环,再借助qs就完成了对n*n枚举n个递增的数再映射为坐标,接下里只需要对 System.out.printf("%d %d\n",jgi/n,jgi%n);就能实现得到每个皇后的x,y坐标。这只是完成了暴力对所有皇后位置的放,还需要在solve里进行横竖左斜右斜的判断来看这个结果是否符合,不过这样通常是会时间复杂度用的更多,其实更理想的是在

jkava 复制代码
  for (int i = 0; i < sl; i++) {
            if (f[i] == 0) {
                f[i] = 1;
                jg[wz]=i;
                dfs(wz + 1);
                f[i] = 0;
            }
        }

这个期间就进行剪枝判断,而不是全枚举完再判断,这里主要是提供思路。

那我们接下来看一下组合总和:

给定一个无重复元素 的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。
candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是不同的。

可以发现区别是一个数字可以重复选还有判断条件不再是根据数字的数量而是总和。那么我们先看数字重复选怎么实现,这里要在fi上动手。

java 复制代码
import java.util.Scanner;

public class Main {
    static int n, a[] = new int[10005], f[] = new int[10005], jg[] = new int[10005];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        dfs(0, 0);
    }

    //wz是第几个数字
    static void dfs(int wz, int ks) {
        if(wz>n)return;
        if (wz == n) {
            solve();
        }
        for (int i = ks; i < n; i++) {
            jg[wz] = i;
            dfs(wz + 1, i );
            jg[wz] = 0;
        }
    }

    //调用临界解决问题的方法
    static void solve() {
        for (int i = 0; i < n; i++) {
            System.out.printf("%d ", jg[i]);
        }
        System.out.println();
    }
}

去掉fi,再把qs不加1传参就可以得到有重复数字的递增序列了。

那么针对这道问题,需要把递归临界从数量改为总和。

java 复制代码
import java.util.Scanner;

public class Main {
    static int n, a[] = new int[10005],mub,f[] = new int[10005], jg[] = new int[10005],f1;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        mub=sc.nextInt();
        for (int i = 0; i < n; i++) {
            a[i]=sc.nextInt();
        }
        dfs(0, 0);
    }

    static void dfs(int sum, int ks) {
        if(sum>mub)return;
        if (sum == mub) {
            solve();
        }
        for (int i = ks; i < n; i++) {
            jg[f1++] = a[i];
            dfs(sum+a[i], i );
            jg[f1--] = 0;
        }
    }

    //调用临界解决问题的方法
    static void solve() {
        for (int i = 0; i < f1; i++) {
            System.out.printf("%d ", jg[i]);
        }
        System.out.println();
    }
}

这样就完成了,遇到问题的时候思路不要乱,大胆去想。

相关推荐
蛋蛋的就会顺顺的27 分钟前
hot100——矩阵
java·数据结构·算法·leetcode·力扣
老歌老听老掉牙33 分钟前
麻花钻切屑形态演变的力学机制与临界条件分析
python·算法·钻头
zhangfeng11331 小时前
国产GPU/AI算力芯片现状(修订完整版 · 截至2026年9月
人工智能·算法·ai编程·npu
学代码的CJY1 小时前
从代码出发理解时间复杂度与空间复杂度
数据结构·算法
白帽攻防录2 小时前
SRC 挖洞:WSO2 JWT 算法混淆绕过深度复盘,CVE-2026-5430 不支持的算法怎么变成管理员
网络·算法·安全·网络安全·jwt
小O的算法实验室2 小时前
IEEE TEVC,基于精确与DQN群智能调度方法用于多目标异构USV
算法
可乐ea3 小时前
AI Agent 工具调用准确性评测:选择错误与参数错误分开测
大数据·人工智能·算法·大模型·工具调用·ai智能体·agent评测
上位机妹子3 小时前
C 语言 数组删除指定元素(快慢指针法)
c语言·数据结构·算法
心中有你02143 小时前
【路径规划】A*寻路算法最通俗易懂讲解(C语言完整实现+详细注释)
c语言·开发语言·算法
papaofdoudou4 小时前
初中数学-整数整除判定法全解:从 2 到 19 的方法、依据与证明
算法