1. DFS算法一般步骤
void dfs(int step)
{
if(边界成立)
{
走到最深处
。。。。。。
return;
}
for(尝试每一种可能的状态)
{
if(如果这种状态可行){ //剪枝
把这种可能的状态标记,表示走过
继续下一步dfs(step+1) //状态转移
把这种标记去除 //回溯
}
}
}
2. 数独
问题描述:
你一定听说过“数独”游戏。
如下图所示,玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个同色九宫内的数字均含1-9,不重复。
数独的答案都是唯一的,所以,多个解也称为无解。
本图的数字据说是芬兰数学家花了3个月的时间设计出来的较难的题目。但对会使用计算机编程的你来说,恐怕易如反掌了。
本题的要求就是输入数独题目,程序输出数独的唯一解。我们保证所有已知数据的格式都是合法的,并且题目有唯一的解。
格式要求,输入9行,每行9个数字,0代表未知,其它数字为已知。
输出9行,每行9个数字表示数独的解。
输入:005300000
800000020
070010500
400005300
010070006
003200080
060500009
004000030
000009700程序应该输出:
145327698
839654127
672918543
496185372
218473956
753296481
367542819
984761235
521839764
代码
package DFS;
import java.util.Scanner;
/**
* @author: DreamCode
* @file: 数独.java
* @time: 2022年3月27日-下午2:53:20
*/
public class 数独 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
char[][] arr = new char[9][9];
for (int i = 0; i < 9; i++) {
arr[i] = scanner.nextLine().toCharArray();
}
DFS(arr, 0, 0);
}
private static void DFS(char[][] arr, int x, int y) {
// TODO DFS遍历,x,y为当前遍历位置的坐标
if (x == 9) { // 出口,二维表是从左到右,从上到下遍历
print(arr);// 打印二维表
return;
}
if (arr[x][y] == '0') { // 当前位置为0,需要进行填数
for (int i = 1; i <= 9; i++) {
if (check(arr, x, y, i)) { // 填入当前数合规
arr[x][y] = (char) (i + '0');
DFS(arr, x + (y + 1) / 9, (y + 1) % 9); // x与y的取值范围都是[0~8]
}
}
arr[x][y] = '0'; // 回溯
} else {// 当前位置不为0,直接进入下一层;
DFS(arr, x + (y + 1) / 8, (y + 1) % 8); // x与y的取值范围都是[0~8]
}
}
private static void print(char[][] arr) {
// TODO 打印字符二维表
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j]);
}
System.out.println();
}
}
private static boolean check(char[][] arr, int x, int y, int num) {
// TODO 检查当前填入的数是否合规
// 行检查
for (int i = 0; i < 9; i++) {
if ((arr[x][i] - '0') == num) {
return false;
}
}
// 列检查
for (int i = 0; i < 9; i++) {
if ((arr[i][y] - '0') == num) {
return false;
}
}
// 方格检查
for (int i = (x / 3) * 3; i < (x / 3 + 1) * 3; i++) {
for (int j = (y / 3) * 3; j < (y / 3 + 1) * 3; j++) {
if ((arr[i][j] - '0') == num) {
return false;
}
}
}
return true;
}
}
3. 部分和
问题描述:
给定整数序列a1,a2,…,an,判断是否可以从中选出若干数,使它们的和恰好为k. 1≤n≤20,-108≤ai≤108,-108≤k≤108
样例:
输入
n=4
a={1,2,4,7}
k=13输出:
Yes (13 = 2 + 4 + 7)
思路解析:
思路1:遍历其非空子集列表,如果子集满足则输出;
思路2:DFS遍历所有组合情况,按照约束条件进行剪枝
代码:
package DFS;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Scanner;
/**
* @author: DreamCode
* @file: 部分和.java
* @time: 2022年3月27日-下午3:19:53
*/
public class 部分和 {
static List<Integer> container = new ArrayList<>(); // 记录当前选择的数字
static int K;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int k = scanner.nextInt();
K = k;
Integer[] arr = new Integer[n];
for (int i = 0; i < n; i++) {
arr[i] = scanner.nextInt();
}
// 对数组arr从小到大排序,保证结果是按照字典序从小打到输出各数
Arrays.sort(arr);
DFS(arr, k, 0); // 深搜算法
SubSet(arr, k); //遍历子集
}
private static void SubSet(Integer[] arr, int k) {
// TODO 采用二进制求非空子集解决部分和问题
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 < o2 ? -1 : 1;
}
});
int len = arr.length;
ArrayList<ArrayList<Integer>> container = new ArrayList<>();
for (int i = (int) Math.pow(2, arr.length) - 1; i > 0; i--) { // 获取每位的二进制数
ArrayList<Integer> subSet_container = new ArrayList<>();
for (int j = len - 1; j >= 0; j--) { // 分别获取数字的每个二进制数字
if (((i >> j) & 1) == 1) { // 该二进制位为1,表示选择
subSet_container.add(arr[j]);
}
}
container.add(subSet_container);
}
FindResult(container, k); // 获取正确结果
}
private static void FindResult(ArrayList<ArrayList<Integer>> container, int k) {
// TODO 从非空子集中找到满足约束条件的结果
for (int i = container.size() - 1; i >= 0; i--) { // 从后往前遍历
int sum = 0;
for (int j = container.get(i).size() - 1; j >= 0; j--) {
sum += container.get(i).get(j);
}
if (sum == k) {
System.out.print("yes (" + K + " = ");
for (int j = container.get(i).size() - 1; j >= 0; j--) {
System.out.print(j == 0? container.get(i).get(j) : container.get(i).get(j) + " + ");
}
System.out.println(")");
}
}
}
private static void DFS(Integer[] arr, int k, int i) {
// TODO 判断第i位数是否需要选择,两种分支,要么选,要么不选
if (i == arr.length || k < 0) { // 出口:如果当前已经选完了一遍所有数或者K已经不足以支持继续选择
if (k == 0) { // 满足约束条件
System.out.print("yes (" + K + " = ");
for (int j = 0; j < container.size(); j++) {
System.out.print(j == container.size() - 1 ? container.get(j) : container.get(j) + " + ");
}
System.out.println(")");
}
return;
}
container.add(arr[i]);
DFS(arr, k - arr[i], i + 1); // 选择第i位数
container.remove(container.indexOf(arr[i])); // 回溯
DFS(arr, k, i + 1); // 不选择第i位数
}
}
4. 水洼数目
问题描述
有一个大小为 N*M 的园子,雨后积起了水。八连通的积水被认为是连接在一起的。请求出
园子里总共有多少水洼?(八连通指的是下图中相对 W 的的部分)*** *W* ***
限制条件
N, M ≤ 100
样例:
输入
N=10, M=12园子如下图('W’表示积水, '.'表示没有积水)
W........WW. .WWW.....WWW ....WW...WW. .........WW. .........W.. ..W......W.. .W.W.....WW. W.W.W.....W. .W.W......W. ..W.......W.
输出
3
思路解析:
DFS问题,如果当前为W,则将当前W变为 .同时向其8个方向中是W的进行状态转移,每一次遍历能活动一个水洼,也能消除一个水洼。直到没有W,当前的遍历次数即为水洼数目
package DFS;
import java.util.Scanner;
/**
* @author: DreamCode
* @file: 水洼数目.java
* @time: 2022年3月27日-下午4:14:17
*/
public class 水洼数目 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
int M = scanner.nextInt();
char[][] arr = new char[N][M];
for (int i = 0; i < N; i++) {
arr[i] = scanner.next().toCharArray();
}
int count = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if (arr[i][j] == 'W') {
DFS(arr, i, j);
count++;
}
}
}
System.out.println(count);
}
private static void DFS(char[][] arr, int x, int y) {
// TODO DFS
arr[x][y] = '.';
for (int i = -1; i < 2; i++) {
for (int j = -1; j < 2; j++) {
if ((x + i) >= 0 && (x + i) < arr.length && (y + j) >= 0 && (y + j) < arr[x].length
&& arr[x + i][y + j] == 'W') { // 该方向可以扩展
DFS(arr, x + i, y + j);
}
}
}
}
}
5. n皇后问题
问题描述:
请设计一种算法,解决著名的n皇后问题。这里的n皇后问题指在一个n*n的棋盘上放置n个棋子, 使得每行每列和每条对角线上都只有一个棋子,求其摆放的方法数。给定一个int n,请返回方法数,保证n小于等于15
思路解析:
DFS+剪枝,这里的剪枝主要是:同行不能出现两个,同列不能出现两个,正负对角线不能出现两个(正对角线y-x相同,负对角线x+y相同)
package DFS;
import java.util.Iterator;
import java.util.Scanner;
/**
* @author: DreamCode
* @file: n皇后问题.java
* @time: 2022年3月28日-下午8:42:30
*/
public class n皇后问题 {
static int ans;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[][] arr = new int[n + 1][n + 1];
DFS(arr, 1); // 从第一行开始选择
System.out.println(ans);
}
private static void DFS(int[][] arr, int row) {
// TODO DFS+剪枝
if(row==arr.length) {
ans++;
print(arr);
return;
}
for (int i = 1; i < arr[row].length; i++) { // 从1到n列总共n中列选择
if (check(arr, row, i)) {// 当前位置可以放个皇后
arr[row][i] = 1;
DFS(arr, row + 1);
arr[row][i] = 0;// 回溯
}
}
}
private static void print(int[][] arr) {
// TODO 打印棋盘
for(int i=1;i<arr.length;i++) {
for(int j=1;j<arr[i].length;j++) {
System.out.print(arr[i][j]);
}
System.out.println();
}
System.out.println();
}
private static boolean check(int[][] arr, int row, int col) {
// TODO 剪枝
for (int i = 1; i < arr.length; i++) {// 检查当前列
if (arr[i][col] == 1 && i != row) {
return false;
}
}
for (int i = 1; i < arr[row].length; i++) {// 检查当前列
if (arr[row][i] == 1 && i != col) {
return false;
}
}
for (int i = 1; i < arr.length; i++) {// 检查对角线
for (int j = 1; j < arr[i].length; j++) {
if ((i - j) == (row - col) && arr[i][j] == 1) {// 正对角线
return false;
}
if ((i + j) == (row + col) && arr[i][j] == 1) {// 副对角线
return false;
}
}
}
return true;
}
}
6. 素数环
问题描述:
输入正整数n,对1-n进行排列,使得相邻两个数之和均为素数,输出时从整数1开始,逆时针排列。同一个环应恰好输出一次。n<=16
输入:
6
输出:
1 4 3 2 5 6
1 6 5 2 3 4
思路解析:
方法1:全排列结合逐个判断;
方法2:DFS结合剪枝
package DFS;
import java.util.Scanner;
/**
* @author: DreamCode
* @file: 素数环.java
* @time: 2022年3月29日-下午2:43:27
*/
public class 素数环 {
static int[] rec;
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
int n=scanner.nextInt();
int arr[]=new int[n];
rec=new int[n];
rec[0]=1;
DFS(1);
}
private static void DFS(int k) {
if(k==rec.length) {
if(isPrime(rec[k-1]+rec[0])) {
printArr();
return;
}
}
for(int i=2;i<=rec.length;i++) { //从第k位可以填的数未2~n
if(check(k,i)) {
rec[k]=i;
DFS(k+1);
rec[k]=0;
}
}
}
private static void printArr() {
// TODO 打印rec数组
for(int i:rec) {
System.out.print(i+" ");
}
System.out.println();
}
private static boolean check(int k, int num) {
// TODO 检查第k位置放数字num是否符合要求
if(!isPrime(num+rec[k-1])) {
return false;
}
for(int i=0;i<k;i++) { //是否出现过
if(rec[i]==num) {
return false;
}
}
return true;
}
private static boolean isPrime(int num) {
// TODO 判断num是否为素数
for(int i=2;i<=Math.sqrt(num);i++) {
if(num%i==0) {
return false;
}
}
return true;
}
}
7. 困难的串
问题描述:
如果一个字符串包含两个相邻的重复子串,则称它为容易的串,其他串称为困难的串,如:BB,ABCDACABCAB,ABCDABCD都是容易的,A,AB,ABA,D,DC,ABDAB,CBABCBA都是困难的。
输入正整数n,L,输出由前L个字符(大写英文字母)组成的,字典序第n小的困难的串。
例如,当L=3时,前7个困难的串分别为:A,AB,ABA,ABAC,ABACA,ABACAB,ABACABA
n指定为4的话,输出ABAC
思路解析:
DFS遍历+回溯+剪枝;困难串的判定方法:从尾到头,按照字符长度由1到n/2逐渐遍历,判断是否有重复字串,
例如ABA,当前需要判断加字母C是否为困难的串,则先判断A与C,再判断AB与AC,结果都为false,所以为困难的串
package DFS;
import java.util.Scanner;
/**
* @author: DreamCode
* @file: 困难的串.java
* @time: 2022年3月29日-下午7:45:11
*/
public class 困难的串 {
static int L;
static int n;
static int count=0;
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
n=scanner.nextInt();
L=scanner.nextInt();
DFS("");
}
private static void DFS(String prefix) {
// TODO 当前字符串的前缀
if(count==n) {
System.out.println(prefix);
System.exit(0);
}
for(int i=0;i<L;i++) {
char c = (char)(i+'A');
if(isHard(prefix,c)) {
count++;
DFS(prefix+c);
}
}
}
private static boolean isHard(String prefix, char c) {
// TODO 判断当前前缀prefix加如字符c以后是否是困难的串
int count=0; //记录当前有重复字符的长度
for(int j=prefix.length();j>0;j-=2) {
String s1 = prefix.substring(j-1,prefix.length()-count);
String s2 = prefix.substring(prefix.length()-count)+c;
if(s1.equals(s2)) {
return false;
}else {
count+=1;
}
}
return true;
}
}