LeetCode 51. N 皇后
因为每行每列每条对角线都只能有一个皇后,所以可以将各行的皇后映射到同一列上,枚举每列每条对角线是否有皇后。
接着分析,对角线上的皇后。
皇后坐标为(i,j)
- 正对角线映射到一个数组dg中,由于此时
i = j + c1
的c1可能为负数,所以需要加上一个偏移量保证c1为正数,用dg[i - j + n]
记录这条正对角线 - 反对角线映射到一个数组udg,此时
i = - j + c2
,c2必定为正数,用udg[ i + j ]
记录这条反对角线
在回溯的过程中只要满足!col[i] && !dg[u - i + n] && !udg[u + i]
这个条件就表示当前的坐标能够容纳新的皇后,如此去递归枚举每个位置,找出所有合法的皇后排列即可
class Solution {
public:
int n;
vector<bool> col, dg, udg;
vector<vector<string>> res;
vector<string> path;
vector<vector<string>> solveNQueens(int _n) {
n = _n;
col = vector<bool>(n);
dg = udg = vector<bool>(n * 2);
path = vector<string>(n, string(n, '.'));
dfs(0);
return res;
}
void dfs(int u) {
if(u == n) {
res.push_back(path);
return;
}
for(int i = 0; i < n; i ++ ) {
if(!col[i] && !dg[u - i + n] && !udg[u + i]) {
col[i] = dg[u - i + n] = udg[u + i] = true;
path[u][i] = 'Q';
dfs(u + 1);
path[u][i] = '.';
col[i] = dg[u - i + n] = udg[u + i] = false;
}
}
}
};
LeetCode 52. N皇后 II
思路同LeetCode 51. N 皇后,返回 (int)res.size()
即可
class Solution {
public:
int n;
vector<bool> col, dg, udg;
vector<vector<string>> res;
vector<string> path;
int totalNQueens(int _n) {
n = _n;
col = vector<bool>(n);
dg = udg = vector<bool>(n * 2);
path = vector<string>(n, string(n, '.'));
dfs(0);
return (int)res.size();
}
void dfs(int u) {
if(u == n) {
res.push_back(path);
return;
}
for(int i = 0; i < n; i ++ ) {
if(!col[i] && !dg[u - i + n] && !udg[u + i]) {
col[i] = dg[u - i + n] = udg[u + i] = true;
path[u][i] = 'Q';
dfs(u + 1);
path[u][i] = '.';
col[i] = dg[u - i + n] = udg[u + i] = false;
}
}
}
};
LeetCode 53. 最大子序和
- 设 f(i) 表示以第 i 个数字为结尾的最大连续子序列的 总和 是多少。
- 初始化
f(0) = nums[0]
- 动态转移方程
f(i) = max( f(i - 1) + nums[i] , nums[i])
即下一步的决策有两种选择
(1)nums[i]
加上前面序列的和
(2)以nums[i]
为开始的新的数字序列 - 最终答案
res = max(f(i))
,0 <= i < n
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
vector<int> f(n + 1, INT_MIN);
f[0] = nums[0];
for(int i = 1; i < n; i ++ ) {
f[i] = max(nums[i], f[i - 1] + nums[i]);
}
int res = INT_MIN;
for(int i = 0; i <= n; i ++ ) {
res = max(res, f[i]);
}
return res;
}
};
可以使用单一一个变量 s 代替 f 数组将空间复杂度优化到一个常数的效果
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int res = INT_MIN;
for(int i = 0, s = 0; i < nums.size(); i ++ ) {
if(s < 0) s = 0;
s += nums[i];
res = max(res, s);
}
return res;
}
};
分治法
- 对于一个区间 [l,r],维护四个值,分别是:总和 sum;非空最大子段和 s;前缀非空最大子段和 ls;后缀非空最大子段和 rs。
- 分别递归左右子区间。
- 合并时,对于 sum 则是左右子区间的 sum 之和。
- 对于 s,则有三种情况取最大值:左区间的 s;右区间的 s;左区间的 rs 加上右区间的 ls。
- 对于 ls,则有两种情况取最大值:左区间的 ls;左区间的 sum 加上右区间的 ls。
- 对于 rs 同理。
- 合并后返回递归的结果。
class Solution {
public:
struct Node {
int sum, s, ls, rs;
};
Node build(vector<int>& nums, int l, int r) {
if(l == r) {
int v = max(nums[l], 0);
return {nums[l], v, v, v};
}
int mid = (l + r) >> 1;
auto L = build(nums, l, mid);
auto R = build(nums, mid + 1, r);
Node res;
res.sum = L.sum + R.sum;
res.s = max(max(L.s, R.s), L.rs + R.ls);
res.ls = max(L.ls, L.sum + R.ls);
res.rs = max(R.rs, R.sum + L.rs);
return res;
}
int maxSubArray(vector<int>& nums) {
int res = INT_MIN;
for(auto x : nums) res = max(res, x);
if(res < 0) return res;
auto t = build(nums, 0, nums.size() - 1);
return t.s;
}
};
LeetCode 54. 螺旋矩阵
蛇形矩阵的变化题目,做法类似于BFS
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> res;
int n = matrix.size();
if(!n) return res;
int m = matrix[0].size();
//作为方向偏移量
int dx[] = {0, 1, 0, -1};
int dy[] = {1, 0, -1, 0};
//记录对应的数字是否被遍历过
vector<vector<bool>> st(n, vector<bool>(m));
for(int i = 0, x = 0, y = 0, d = 0; i < n * m; i ++ ) {
res.push_back(matrix[x][y]);
st[x][y] = true;
int a = x + dx[d], b = y + dy[d];
if(a < 0 || a >= n || b < 0 || b >= m || st[a][b]) {
d = (d + 1) % 4;
a = x + dx[d], b = y + dy[d];
}
x = a, y = b;
}
return res;
}
};
LeetCode 55. 跳跃游戏
与LeetCode 45. 跳跃游戏 II 类似
用一个临时变量 cur 记录当前能跳到的离起点最远的距离是多少
如果当前i > cur
那么就表示无法继续进行跳跃,那么就不能跳到终点
如果能成功走完for循环,那么就能够跳到终点
class Solution {
public:
bool canJump(vector<int>& nums) {
int n = nums.size();
int cur = 0;
for(int i = 0; i < n; i ++ )
{
if(i > cur) return false;
cur = max(cur, i + nums[i]);
}
return true;
}
};
LeetCode 56. 合并区间
思想方法类似于贪心
- 先给每个区间按左端点排序
- 遍历每个区间
如果第二个区间起点小于第一个区间终点,那么就是有交集,将第一个区间的终点更新为第二个区间的终点
如果没有交集,那么将前面更新完毕的区间存储起来
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& segs) {
if(segs.size() <= 1) return segs;
vector<vector<int>> res;
sort(segs.begin(), segs.end());
int st = segs[0][0], ed = segs[0][1];
for(auto seg : segs)
{
if(seg[0] > ed)
{
res.push_back({st, ed});
st = seg[0], ed = seg[1];
}
else ed = max(ed, seg[1]);
}
res.push_back({st, ed});
return res;
}
};
LeetCode 57. 插入区间
(一)思路类比LeetCode 56. 合并区间
将新的区间插入原有区间后排序,再执行合并的操作
class Solution {
public:
vector<vector<int>> insert(vector<vector<int>>& segs, vector<int>& newSeg) {
vector<vector<int>> res;
if(segs.empty()){
res.push_back(newSeg);
return res;
}
segs.push_back(newSeg);
sort(segs.begin(), segs.end());
int st = segs[0][0], ed = segs[0][1];
for(auto seg : segs)
{
if(seg[0] > ed)
{
res.push_back({st, ed});
st = seg[0], ed = seg[1];
}
else ed = max(ed, seg[1]);
}
res.push_back({st, ed});
return res;
}
};
(二)更简洁的 O ( n ) O(n) O(n) 做法
- 将原区间数组中左边和新区间没有交集的部分添加进res数组
- 合并与新区间有交集的区间
- 将合并完得到的区间添加进res数组
- 将位于新区间右边的部分添加进res数组
class Solution {
public:
vector<vector<int>> insert(vector<vector<int>>& segs, vector<int>& newSeg) {
vector<vector<int>> res;
int k = 0;
//将原区间数组中左边和新区间没有交集的部分添加进res数组
while(k < segs.size() && segs[k][1] < newSeg[0])
res.push_back(segs[k ++ ]);
//合并与新区间有交集的区间
if(k < segs.size()) {
newSeg[0] = min(newSeg[0], segs[k][0]);
while(k < segs.size() && segs[k][0] <= newSeg[1])
newSeg[1] = max(newSeg[1], segs[k ++ ][1]);
}
//将合并完得到的区间添加进res数组
res.push_back(newSeg);
//将位于新区间右边的部分添加进res数组
while(k <segs.size()) res.push_back(segs[k ++ ]);
return res;
}
};
LeetCode 58. 最后一个单词的长度
(一)双指针算法,用空格的位置计算长度
class Solution {
public:
int lengthOfLastWord(string s) {
if(s == "" || s == " ") return 0;
//去掉空格
while(s.back() == ' ') s.pop_back();
int pre = 0, i = 1;
//记录上一个空格的位置
for(; i <= s.size(); i ++ )
if(s[i - 1] == ' ')
pre = i;
pre ++ ;
return i - pre;
}
};
(二)从后往前遍历
class Solution {
public:
int lengthOfLastWord(string s) {
for(int i = s.size() - 1; i >= 0; i -- ) {
if(s[i] == ' ') continue;
int j = i - 1;
while(j >= 0 && s[j] != ' ') j -- ;
return i - j;
}
return -1;
}
};
(三) stringstream的用法
class Solution {
public:
int lengthOfLastWord(string s) {
stringstream ssin(s);
int res = 0;
string word;
while(ssin >> word) res = word.size();
return res;
}
};
LeetCode 59. 螺旋矩阵 II
思路和LeetCode 54. 螺旋矩阵完全一致,稍作修改即可
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0));
int dx[] = {0, 1, 0, -1};
int dy[] = {1, 0, -1, 0};
for(int i = 0, d = 0, x = 0, y = 0; i < n * n; i ++ ) {
res[x][y] = i + 1;
int a = x + dx[d], b = y + dy[d];
if(a < 0 || b < 0 || a >= n || b >= n || res[a][b]){
d = (d + 1) % 4;
a = x + dx[d], b = y + dy[d];
}
x = a, y = b;
}
return res;
}
};
LeetCode 60. 排列序列
nextPermutation
class Solution {
public:
string res;
string getPermutation(int n, int k) {
for(int i = 1; i <= n; i ++ ) res += to_string(i);
for(int i = 0; i < k - 1; i ++ )
next_permutation(res.begin(), res.end());
return res;
}
};
爆搜,会超时
class Solution {
public:
vector<string> res;
int n;
string getPermutation(int _n, int k) {
n = _n;
vector<bool> st(n, false);
dfs(0, "", st);
return res[k - 1];
}
void dfs(int u, string path, vector<bool>& st) {
if(u == n) {
res.push_back(path);
return;
}
for(int i = 0; i < n; i ++ ) {
if(!st[i]) {
st[i] = true;
path += ('1' + i);
dfs(u + 1, path, st);
path.pop_back();
st[i] = false;
}
}
}
};
回溯 + 剪枝
剪枝思路:将搜索树的无关节点跳过
为了便于理解,我们这里给出一个例子的具体操作:n=4,k=14
首先我们将所有排列按首位分组:
-
1 + (2, 3, 4的全排列)
-
2 + (1, 3, 4的全排列)
-
3 + (1, 2, 4的全排列)
-
4 + (2, 3, 4的全排列)
接下来我们确定第 k=14k=14 个排列在哪一组中。每组的排列个数是 3!=6 个,所以第14个排列在第3组中,所以首位已经可以确定,是3。
然后我们再将第3组的排列继续分组: -
31 + (2, 4的全排列)
-
32 + (1, 4的全排列)
-
34 + (1, 2的全排列)
接下来我们判断第 k=14 个排列在哪个小组中。我们先求第 14 个排列在第三组中排第几,由于前两组每组有6个排列,所以第14个排列在第3组排第 14−6∗2=2
在第三组中每个小组的排列个数是 2!=2个,所以第 k 个排列在第1个小组,所以可以确定它的第二位数字是1。
依次类推,可以推出第14个排列是 3142。
图示:
class Solution {
public:
vector<bool> st;
//阶乘打表
int arr[10] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
int n, k;
string getPermutation(int _n, int _k) {
n = _n, k = _k;
st.resize(n + 1);
string path;
dfs(0, path);
return path;
}
void dfs(int u, string& path) {
if (u == n) return;
int cnt = arr[n - 1 - u];
for (int i = 1; i <= n; i ++) {
if (st[i]) continue;
if (cnt < k) {
k -= cnt;
continue;
}
path += (i + '0');
st[i] = true;
dfs(u + 1, path);
return;
}
}
};
循环计数法,思路与上述的回溯剪枝思路一致
class Solution {
public:
string getPermutation(int n, int k) {
string res;
vector<bool> st(10);
for(int i = 0; i < n; i ++ ) {
int fact = 1;
//阶乘数
for(int j = 1; j <= n - i - 1; j ++ ) fact *= j;
for(int j = 1; j <= n; j ++ ) {
if(!st[j]) {
//如果k不在这个分支里面
if(fact < k) k -= fact;
else {
res += to_string(j);
st[j] = true;
break;
}
}
}
}
return res;
}
};