2020_9_3 每日一题 N皇后

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
在这里插入图片描述
上图为 8 皇后问题的一种解法。 给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。

每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。 示例:

输入:4 输出:[ [".Q…", // 解法 1 “…Q”, “Q…”, “…Q.”],

["…Q.", // 解法 2 “Q…”, “…Q”, “.Q…”] ] 解释: 4
皇后问题存在两个不同的解法。

提示:

皇后彼此不能相互攻击,也就是说:任何两个皇后都不能处于同一条横行、纵行或斜线上。

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/n-queens
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

经典回溯算法,一开始还担心会不会时间不过后来发现是想多了。总之,用一个二维数组记录已经被占据的点位,一行的皇后位置暂定之后开始找下一行可行的皇后位置;每一行内部一个迭代:

class Solution {
    List<List<String>> ans;
    int[] array_ans;
    int[][] occupied;

    void occupy(int row, int col, int add, int n) {
        int tRow = row, tCol = col;
        while(tRow < n) {
            occupied[tRow][tCol] += add;
            ++tRow;
        }
        tRow = row;
        while(tCol >= 0 && tRow < n) {
            occupied[tRow][tCol] += add;
            ++tRow;
            --tCol;
        }
        tRow = row;
        tCol = col;
        while(tCol < n && tRow < n) {
            occupied[tRow][tCol] += add;
            ++tRow;
            ++tCol;
        }
    }

    void arrToAns(int n) {
        List<String> cur = new LinkedList<>();
        StringBuilder builder = new StringBuilder();
        for(int row = 0; row < n; ++row) {
            builder.setLength(0);
            int col = array_ans[row];
            for(int i = 0; i < n; ++i) {
                builder.append(i == col ? 'Q' : '.');
            }
            cur.add(builder.toString());
        }
        ans.add(cur);
    }

    void tryRow(int row, int n) {
        for(int col = 0; col < n; ++col) {
            if(occupied[row][col] == 0) {
                array_ans[row] = col;
                if(row == n - 1) {
                    arrToAns(n);
                } else {
                    occupy(row, col, 1, n);
                    tryRow(row + 1, n);
                    occupy(row, col, -1, n);
                }
            }
        }

    }

    public List<List<String>> solveNQueens(int n) {

        ans = new LinkedList<>();
        array_ans = new int[n];
        occupied = new int[n][n];

        tryRow(0, n);
        return ans;
    }
}

看看题解有没有什么优化的方法。首先,其实判断是否被占领只需要三个整形数组就好了,分别表示列、左上右下对角线和右上左下对角线是否被占领。然后就是很无聊的用原始的代替java封装好的的优化:

class Solution {
    List<List<String>> ans;
    int[] array_ans;
    int[] colOccupied;
    int[] l2rOccupied;
    int[] r2lOccupied;

    boolean isNotOccupied(int row, int col, int n) {
        return colOccupied[col] == 0 && l2rOccupied[row - col + n - 1] == 0 && r2lOccupied[row + col] == 0;
    }

    void occupy(int row, int col, int add, int n) {
        colOccupied[col] += add;
        l2rOccupied[row - col + n - 1] += add;
        r2lOccupied[row + col] += add;
    }

    void arrToAns(int n) {
        List<String> cur = new LinkedList<>();
        char[] builder = new char[n];
        for(int row = 0; row < n; ++row) {
            int col = array_ans[row];
            for(int i = 0; i < n; ++i) {
                builder[i] = (i == col ? 'Q' : '.');
            }
            cur.add(String.valueOf(builder));
        }
        ans.add(cur);
    }

    void tryRow(int row, int n) {
        for(int col = 0; col < n; ++col) {
            if(isNotOccupied(row, col, n)) {
                array_ans[row] = col;
                if(row == n - 1) {
                    arrToAns(n);
                } else {
                    occupy(row, col, 1, n);
                    tryRow(row + 1, n);
                    occupy(row, col, -1, n);
                }
            }
        }

    }

    public List<List<String>> solveNQueens(int n) {
        //回溯看看时间给不给过
        ans = new LinkedList<>();
        array_ans = new int[n];
        colOccupied = new int[n];
        l2rOccupied = new int[2 * n - 1];
        r2lOccupied = new int[2 * n - 1];

        tryRow(0, n);
        return ans;
    }
}

4ms变成了3ms,不错。
题解还给出了一种用位代表对角线是否被占领的方法,但是这种方法并没有节省空间,因为用位表示上面的int[]可以得到相同的空间效果(其实能省n-1bit)…还有就是这种方法需要来回传递表示对角线状况的数,其实更繁琐一点…懒得实现了

另外,翻了下自己一年前第一遍做这个的代码,发现有一个优化。对第一行只搜索前一半,输出答案的时候如果在前一半就输出一个对称的:

class Solution {
    List<List<String>> ans;
    int[] array_ans;
    int[] colOccupied;
    int[] l2rOccupied;
    int[] r2lOccupied;

    boolean isNotOccupied(int row, int col, int n) {
        return colOccupied[col] == 0 && l2rOccupied[row - col + n - 1] == 0 && r2lOccupied[row + col] == 0;
    }

    void occupy(int row, int col, int add, int n) {
        colOccupied[col] += add;
        l2rOccupied[row - col + n - 1] += add;
        r2lOccupied[row + col] += add;
    }

    void arrToAns(int n) {
        List<String> cur = new LinkedList<>();
        char[] builder = new char[n];
        for(int row = 0; row < n; ++row) {
            int col = array_ans[row];
            for(int i = 0; i < n; ++i) {
                builder[i] = (i == col ? 'Q' : '.');
            }
            cur.add(String.valueOf(builder));
        }
        ans.add(cur);
        cur = new LinkedList<>();
        if(array_ans[0] < n / 2) {
            //输出一个对称的
            for(int row = 0; row < n; ++row) {
                int col = array_ans[row];
                for(int i = 0; i < n; ++i) {
                    builder[n - i - 1] = (i == col ? 'Q' : '.');
                }
                cur.add(String.valueOf(builder));
            }
            ans.add(cur);
        }
    }

    void tryRow(int row, int n) {
        int maxCol = row == 0 ? (n + 1) / 2 : n;

        for(int col = 0; col < maxCol; ++col) {
            if(isNotOccupied(row, col, n)) {
                array_ans[row] = col;
                if(row == n - 1) {
                    arrToAns(n);
                } else {
                    occupy(row, col, 1, n);
                    tryRow(row + 1, n);
                    occupy(row, col, -1, n);
                }
            }
        }

    }

    public List<List<String>> solveNQueens(int n) {
        //回溯看看时间给不给过
        ans = new LinkedList<>();
        array_ans = new int[n];
        colOccupied = new int[n];
        l2rOccupied = new int[2 * n - 1];
        r2lOccupied = new int[2 * n - 1];

        tryRow(0, n);
        return ans;
    }
}

嗯,变成了2ms,这可是82%和95%的区别啊!!!顺便我一年前写的纯迭代的回溯还蛮可爱的。也贴在这把纪念一下:

class Solution {
public:
    void printAns(vector<vector<string>> & ans, int * pAns, int n)
{
	string s;
	vector<string> temp;
	//第一个是不管咋样都需要打印的
	for (int j = 0; j < n; j++)
		s.push_back('.');
	for (int i = 0; i < n; i++)
		temp.push_back(s);
	ans.push_back(temp);

	for (int i = 0; i < n; i++)
		ans.back()[i][pAns[i]] = 'Q';

	//从数组打印答案 第一行的数是0到n/2 - 1时打印俩
	if (pAns[0] < n / 2)
	{
		//打印俩
		ans.push_back(temp);
		for (int i = 0; i < n; i++)
			ans.back()[i][n - 1 - pAns[i]] = 'Q';
	}
}

vector<vector<string>> solveNQueens(int n) {
	if (n == 1)
		return{ { "Q" } };
	if (n < 4)
		return{};

	vector<vector<string>> ans;

	//想不出来好方法暴力试凑
	bool * pvCol = new bool[n];//特定列是否被占
	bool * pvLCross = new bool[n * 2 - 1];//左向右对角线 索引计算col - row + n - 1
	bool * pvRCross = new bool[n * 2 - 1];//索引计算col + row
	int * pAns = new int[n];

	for (int i = 0; i < n; i++)
		pAns[i] = pvCol[i] = 0;
	for (int i = 0; i < n * 2 - 1; i++)
		pvLCross[i] = pvRCross[i] = 0;

	int row = 0, col = 0;

	while (pAns[0] <= (n + 1) / 2 - 1)//只用考虑左边一半
	{
		//cout << "row = " << row << endl;
		//cout << "col = " << col << endl << endl;

		if (col == n)//下一行没戏了
		{
			row--;
			col = pAns[row];
			pvCol[col] = false;
			pvLCross[col - row + n - 1] = false;
			pvRCross[col + row] = false;//清空当前位置
			col++;
			continue;
		}

		if (pvCol[col] || pvLCross[col - row + n - 1] || pvRCross[col + row])//如果当前位置不可行
		{
			col++;
			continue;
		}
		else
		{
			pAns[row] = col;
			if (row != n - 1)//当前位置可行 找下一行
			{
				pvCol[col] = true;
				pvLCross[col - row + n - 1] = true;
				pvRCross[col + row] = true;//装上当前位置
				row++;
				col = 0;
			}
			else//找到一组答案
			{
				printAns(ans, pAns, n);
				row--;
				col = pAns[row];
				pvCol[col] = false;
				pvLCross[col - row + n - 1] = false;
				pvRCross[col + row] = false;//清空当前位置
				col++;
			}
		}
	}


	delete[] pvCol;
	delete[] pvLCross;
	delete[] pvRCross;
	delete[] pAns;

	return ans;
}
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值