持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情
N皇后问题
N皇后问题是指在N*N的棋盘上要摆N个皇后,要求任何两个皇后不同行、不同列,也不在同一条斜线上 给定一个正数n,返回n皇后的摆法有多少种 n=1,返回1 n=2 或 n=3,2皇后和3皇后无论怎么摆都不行,返回0 n=8,返回92
代码实现:
//主函数
public static int num1(int n){
if(n<1) return 0;
int[] record = new int[n];//record[i]->第i行的皇后放在了第record[i]列
return process1(0,record,n);
}
//递归函数process1 i:现在要摆第i行的皇后 n:问题规模,n个皇后,棋盘n*n
//record:已经摆好了的皇后 返回值:在当前record的基础上,第i行到第i-1行的皇后摆法数量
public static int process1(int i,int[] record,int n){
if(i == n) {
//递归走完了,说明当前情况是符合条件的一种
return 1;
}
int res = 0;
for(int j = 0;j<n;j++){
//当前第i行的皇后,放在j列上是否会与record中已经摆好的皇后产生冲突
//如果是,认为无效;如果不是,认为有效,把第i行的皇后填到第j列
if(isValid(record,i,j)){
record[i] = j;
res+=process(i+1,record,n);
}
}
return res;
}
//判断是否产生冲突的函数isValid
public static boolean isValid(int[] record, int i, int j){
for(int k = 0;k<i;k++){
if(j == record[k] || Math.abs(record[k]-j) == Math.abs(i-k)){
return false;
}
}
return true;
}
N皇后问题优化
思路:采用二进制中位运算的特殊性,使用一个整形变量limit来代表当前可以填入皇后且不会产生冲突的位置。比如4皇后问题:一开始limit为000..01111(最后四位为1,代表当前可以填入皇后且不会产生冲突的位置有4个),8皇后问题limit则为000...011111111(都是总共32位)。
有几个皇后就从最后开始数几位变成1
以8皇后问题为例:一开始limit为000..011111111,当填入第一行的一个皇后时,limit变化为000...011011111(填入皇后后对应的位置变为0),那么第二行不能填的位置由三种限制产生:第一种是同一列的位置不能填,因此limit1为000...011011111(1为可以填入的位置,0为不可以);第二种限制是左斜线不能填,因此limit2为000...010111111;第三种限制是右斜线不能填,因此limit3为000...011101111。将这三个limit进行位运算上的或运算:limit1 | limit2 |limit3 = 000...010001111(1为可以填入的位置,0为不可以),这样子,我们就知道下一行的皇后不能填的位置就为第1、2、3列(索引从0开始)。
注意:这种优化并没有对N皇后问题的时间复杂度进行优化,时间复杂度仍为O(N^N),位运算只是将N皇后问题的速度提高了一个比较大的常数等级。
代码实现:
//获取n皇后结果的主函数
public static int num2(int n){
if(n<1 || n>32){//整形变量32位,n超过32位int类型不能处理
return 0;
}
int limit = n == 32 ? -1 : (1<<n) - 1;//-1的二进制表示为111...111(32个1)、
return process2(limit,0,0,0);
}
//递归函数process2
// colLim 列的限制,1的位置不能放皇后,0的位置可以
// leftDiaLim 左斜线的限制,1的位置不能放皇后,0的位置可以
// rightDiaLim 右斜线的限制,1的位置不能放皇后,0的位置可以
public static int process2(int limit,int colLim,int leftDiaLim,int rightDiaLim){
if(colLim == limit){
return 1;
}
int mostRightOne = 0;
//pos是候选皇后可以选的所有位置
int pos = limit & (~(colLim | leftDiaLim | rightDiaLim));//pos中0表示不能放皇后的位置,而1表示能放皇后的位置
int res = 0;
while(pos != 0){//pos等于0的话说明没有位置可以选了
mostRightOne = pos & (~pos + 1);//提取出pos中最右侧的1
pos = pos - mostRightOne;//将皇后放在最右侧的1位置处,更新pos
res += process2(limit, colLim | mostRightOne, (leftDiaLim | mostRightOne)<<1,(rightDiaLim | mostRightOne) >>1 );
}
return res;
}
会议安排问题
题目:一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。给你每一个项目开始的时间和结束的时间,试安排宣讲日程,要求会议室进行的宣讲的场次最多。返回这个最多的宣讲场次。
思路:按照结束时间的早晚进行安排,结束时间最早的安排在最前面。
实现代码:
//会议定义
public static class Program{
public int start;//开始时间
public int end;//结束时间
public Program(int start, int end){
this.start = start;
this.end = end;
}
}
public static class ProgramComparator implements Comparator<Program>{
@Override
public int compare(Program o1,Program o2){
return o1.end - o2.end;
}
}
public static int bestArrange(Program[] programs, int timePoint){//timePoint:上一个会议的结束时间
Array.sort(programs,new ProgramComparator());
int result = 0;
for(int i = 0;i<programs.length;i++){
if(timePoint <= programs[i].start){
result++;
timePoint = programs[i].end;
}
}
return result;
}
金条切割问题
题目:一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的金条,不管切成长度多大的两半,都要花费20个铜板。 一群人想整分整块金条,怎么分最省铜板? 例如,给定数组{10,20,30},代表一共三个人,整块金条长度为10+20+30=60.金条要分成10,20,30三个部分。如果先把长度60的尽快分成10和50,花费60;再把长度50的金条分成20和30,花费50;一共花费110铜板; 但是,如果先把长度60的金条分成30和30,花费60;再把长度30的金条分成10和20,花费30,一共花费90铜板。 输入一个数组,返回分隔的最小代价。
思路:从小的开始,先把数组中最小的两个数值a和b相加得到c,数组删去a和b,然后c和数组中最小的数值d相加得e,数组删去d,然后e和数组中最小的元素f相加....直到数组中没有元素时,累加的结果就是最小代价。 所以我们可以维护一个小根堆,每次取堆顶元素即可获得数组中的最小元素
代码实现:
public static int lessMoney(int[] arr){
PriorityQueue<Integer> pQ = new PriorityQueue<>();
for(int i = 0;i<arr.length;i++){
pQ.add(arr[i]);
}
int sum = 0;
int cur = 0;
while(pQ.size()>1){
cur = pQ.poll();
sum += cur;
pQ.add(cur);
}
return sum;
}