CONTENTS
- LeetCode 51. N 皇后(困难)
- LeetCode 52. N 皇后 II(困难)
- LeetCode 53. 最大子序和(中等)
- LeetCode 54. 螺旋矩阵(中等)
- LeetCode 55. 跳跃游戏(中等)
LeetCode 51. N 皇后(困难)
【题目描述】
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
N 皇后问题研究的是如何将 N 个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 N 皇后问题的解决方案。
每一种解法包含一个不同的 N 皇后问题的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
【示例1】
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。
【示例2】
输入:n = 1
输出:[["Q"]]
【提示】
1 ≤ n ≤ 9 1\le n\le 9 1≤n≤9
【分析】
N 皇后裸题,DFS 爆搜每一行能放置皇后的位置即可,可以使用 col[i]
、dg[i]
以及 udg[i]
分别表示某一列、正对角线与反对角线是否能放皇后(由于我们是按行枚举的因此不用判断某一行是否可以放置)。由于正对角线为
y
=
x
+
b
y=x+b
y=x+b,因此可以用
y
−
x
y-x
y−x 唯一确定一条正对角线(可以通过统一加上
n
n
n 避免越界);同理可以用
y
+
x
y+x
y+x 确定一条反对角线。
【代码】
class Solution {
public:
vector<vector<string>> res;
vector<bool> col, dg, udg;
vector<vector<string>> solveNQueens(int n) {
col = vector<bool>(n);
dg = udg = vector<bool>(n << 1); // 对角线的数量为2n-1
vector<string> board(n, string(n, '.')); // 初始化棋盘全为'.'
dfs(board, 0); // 从第0行开始搜
return res;
}
void dfs(vector<string>& board, int x)
{
if (x == board.size()) { res.push_back(board); return; }
for (int y = 0; y < board.size(); y++)
if (!col[y] && !dg[y - x + board.size()] && !udg[y + x])
{
board[x][y] = 'Q';
col[y] = dg[y - x + board.size()] = udg[y + x] = true;
dfs(board, x + 1);
col[y] = dg[y - x + board.size()] = udg[y + x] = false;
board[x][y] = '.';
}
}
};
LeetCode 52. N 皇后 II(困难)
【题目描述】
N 皇后问题 研究的是如何将 n
个皇后放置在 n × n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回 N 皇后问题不同的解决方案的数量。
【示例1】
输入:n = 4
输出:2
解释:如上图所示,4 皇后问题存在两个不同的解法。
【示例2】
输入:n = 1
输出:1
【提示】
1 ≤ n ≤ 9 1\le n\le 9 1≤n≤9
【分析】
与上一题一样,只需要记录方案数而不需要记录整个棋盘。
【代码】
class Solution {
public:
vector<bool> col, dg, udg;
int totalNQueens(int n) {
col = vector<bool>(n);
dg = udg = vector<bool>(n << 1);
return dfs(n, 0);
}
int dfs(int n, int x)
{
if (x == n) return 1;
int res = 0;
for (int y = 0; y < n; y++)
if (!col[y] && !dg[y - x + n] && !udg[y + x])
{
col[y] = dg[y - x + n] = udg[y + x] = true;
res += dfs(n, x + 1);
col[y] = dg[y - x + n] = udg[y + x] = false;
}
return res;
}
};
LeetCode 53. 最大子序和(中等)
【题目描述】
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组是数组中的一个连续部分。
【示例1】
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
【示例2】
输入:nums = [1]
输出:1
【示例3】
输入:nums = [5,4,-1,7,8]
输出:23
【提示】
1
≤
n
u
m
s
.
l
e
n
g
t
h
≤
1
0
5
1\le nums.length\le 10^5
1≤nums.length≤105
−
1
0
4
≤
n
u
m
s
[
i
]
≤
1
0
4
-10^4\le nums[i]\le 10^4
−104≤nums[i]≤104
【分析】
我们先分析
O
(
n
)
O(n)
O(n) 的算法,用动态规划考虑:令 f[i]
表示所有以 nums[i]
结尾的区间中的最大和,那么状态转移有以下两种情况:
- 区间长度等于1:
f[i] = nums[i]
; - 区间长度大于1:
f[i] = f[i - 1] + nums[i]
因此可以得到状态转移方程为:f[i] = max(nums[i], f[i - 1] + nums[i]) = nums[i] + max(0, f[i - 1])
,由于 f[i]
只和 f[i - 1]
有关,因此我们可以只使用一个变量记录 f[i - 1]
的值即可。
现在我们考虑如何用分治法求解,其实分治法就是线段树维护动态最大字段和的简化版,当前数组的最大子段所在的区间可能有以下几种情况:
- 在左子区间中,结果即为左子区间的最大子段;
- 在右子区间中,结果即为右子区间的最大子段;
- 横跨左右两个子区间,结果即为左子区间的最大后缀加上右子区间的最大前缀;
求解最大前缀与最大后缀时可能还会有以下几种情况:
- 最大前缀横跨左右两个子区间,那么最大前缀为左子区间的总和加上右子区间的最大前缀;
- 最大后缀横跨左右两个子区间,那么最大后缀为右子区间的总和加上左子区间的最大后缀;
因此我们需要处理出每个区间的最大子段、最大前缀、最大后缀以及总长度这四个信息。
【代码】
【动态规划实现】
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int res = INT_MIN;
for (int i = 0, f = 0; i < nums.size(); i++)
f = nums[i] + max(f, 0), res = max(res, f);
return res;
}
};
【分治法实现】
class Solution {
public:
struct Node {
int sum, lmax, rmax, tmax; // 区间和,最大前缀,最大后缀,最大子段和
};
int maxSubArray(vector<int>& nums) {
auto t = build(nums, 0, nums.size() - 1);
return t.tmax;
}
Node build(vector<int>& nums, int l, int r)
{
if (l == r) return { nums[l], nums[l], nums[l], nums[l] }; // 递归到了长度为1的结点
int mid = l + r >> 1;
auto lnode = build(nums, l, mid), rnode = build(nums, mid + 1, r);
// 线段树中的pushup操作
Node res;
res.sum = lnode.sum + rnode.sum;
res.lmax = max(lnode.lmax, lnode.sum + rnode.lmax);
res.rmax = max(rnode.rmax, rnode.sum + lnode.rmax);
res.tmax = max(max(lnode.tmax, rnode.tmax), lnode.rmax + rnode.lmax);
return res;
}
};
LeetCode 54. 螺旋矩阵(中等)
【题目描述】
给你一个 m
行 n
列的矩阵 matrix
,请按照顺时针螺旋顺序,返回矩阵中的所有元素。
【示例1】
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
【示例2】
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
【提示】
m
=
=
m
a
t
r
i
x
.
l
e
n
g
t
h
m == matrix.length
m==matrix.length
n
=
=
m
a
t
r
i
x
[
i
]
.
l
e
n
g
t
h
n == matrix[i].length
n==matrix[i].length
1
≤
m
,
n
≤
10
1\le m, n\le 10
1≤m,n≤10
−
100
≤
m
a
t
r
i
x
[
i
]
[
j
]
≤
100
-100\le matrix[i][j]\le 100
−100≤matrix[i][j]≤100
【分析】
分别设置向右、向下、向左、向上四个方向向量,然后模拟一遍即可,走出界或是已经遍历过了改变一下方向即可。
【代码】
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
int n = matrix.size(), m = matrix[0].size();
int dx[] = { 0, 1, 0, -1 }, dy[] = { 1, 0, -1, 0 };
vector<int> res;
for (int i = 0, x = 0, y = 0, d = 0; i < n * m; i++) // 总共遍历n*m个点
{
res.push_back(matrix[x][y]);
matrix[x][y] = INT_MIN; // 遍历过的数修改为INT_MIN
int nx = x + dx[d], ny = y + dy[d];
if (nx < 0 || nx >= n || ny < 0 || ny >= m || matrix[nx][ny] == INT_MIN) d = (d + 1) % 4;
x += dx[d], y += dy[d];
}
return res;
}
};
LeetCode 55. 跳跃游戏(中等)
【题目描述】
给你一个非负整数数组 nums
,你最初位于数组的第一个下标。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标,如果可以,返回 true
;否则,返回 false
。
【示例1】
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
【示例2】
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
【提示】
1
≤
n
u
m
s
.
l
e
n
g
t
h
≤
1
0
4
1\le nums.length\le 10^4
1≤nums.length≤104
0
≤
n
u
m
s
[
i
]
≤
1
0
5
0\le nums[i]\le 10^5
0≤nums[i]≤105
【分析】
本题和第45题差不多,我们从小到大枚举
i
i
i,并同时维护
i
i
i 之前所有点能跳到的最远距离
m
a
x
_
d
i
s
max\_dis
max_dis,如果 max_dis < i
,说明
i
i
i 之前没有点能够跳到
i
i
i 了,直接返回 false
即可。
【代码】
class Solution {
public:
bool canJump(vector<int>& nums) {
for (int i = 0, max_dis = 0; i < nums.size(); i++)
{
if (max_dis < i) return false;
max_dis = max(max_dis, i + nums[i]);
}
return true;
}
};