31. 下一个排列
分析
应该尽可能的保证排列的高位不变, 变低位, 这样的话才是比当前字典序大的最小的
因此可以从后往前去考虑, 如果到某一位的时候, 当前数与后面的数构成降序关系, 那么当前这位不可能变大,
因为前面这些数保证不变, 当前数已经是后面数中最大的数了, 因此当前位的数字不可能变得更大, 因此当前位不能动任何手脚.
也就是说, 只要后面是降序(不一定严格降序), 那么当前位的数字不可能发生任何变化, 如果想让当前字典序变得大一些, 应该找到一个非降序的位置, 前一个数比后面一个数稍微小一些, 那么前一个数可以稍微变大一些, 因为后面的数中存在比当前数更大的数, 那么当前这位数应该变大成什么数呢, 变大成后面的数中, 比当前数大的最小的数(即大于当前数的且最接近当前数的数),
应该将后面这个匹配的数与当前数交换, 交换完后, 后面的排列应该越小越好(将后面的数变成升序)
步骤:
找到第1个非降序的位置, 找到比当前数大的最小的数, 交换完(后面还是降序, 因为选出来的数是比当前数大的最小的数), 再将后面的数逆序
类似题 leetcode 60
code
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int k = nums.size() - 1;
while (k > 0 && nums[k - 1] >= nums[k]) k --;
if (k <= 0) { // 说明整个数列都是降序的
reverse(nums.begin(), nums.end());
}else {
int t = k;
while (t < nums.size() && nums[t] > nums[k - 1]) t ++;
swap(nums[k - 1], nums[t - 1]);
reverse(nums.begin() + k, nums.end());
}
}
};
32. 最长有效括号
分析
合法的括号序列
(
)
数量相等- 任意前缀中
(
数量 >=)
数量
首先, 可以先考虑将整个序列分成若干段.(???)
可以证明任何一个合法序列存在于某一段里, 而不会跨越两段之间
怎么去分段, 从前往后考虑, 记录下左括号数量和右括号数量, 找到第一个不合法的位置(即:第一个前缀)
数量 >(
数量), 在不合法的位置右边划1条线
命题: 任何一个合法的括号序列, 不会横跨这条线
证明: 假设存在合法区间1横跨了不合法区间(图中三角线后面划的线的的地方), 那么1所在区间(
>=)
数量, 由于三角形前面所在区间括号不合法, 因此2所在区域(
<)
, 2区间 - 1区间 那么3区间也不合法, 因此存在比三角形更靠前的位置, 有不合法区间, 与图中三角形选出来为第1个不合法区间位置,矛盾
重新开始计数, 再找到)
大于(
数量的位置, 再划1条线, 找到所有满足)
比较多的位置, 划1条线. 刚才已经证明了任何合法的括号序列, 不会横跨这条线 , 因此找合法的括号序列, 都只需要在每一段内部去找.
每一段内部有一个性质, 除了最后一个)
位置, 左括号的数量 >= 右括号的数量
因此在每一段内部, 除了最后一个位置, 都满足(
>= )
, 意味着任何一个)
都能找到匹配的(
, 在去考虑这一个段内最大长度时候, 可以对所有合法的序列, 按照右括号的位置来分类. 即: 对每一个右括号, 求一下以这个右括号为右端点的最长的合法序列的左端点在什么位置.
把每个右括号都枚举一遍后, 再取max就是整个的最大长度
怎么找靠左的位置, 使得这一段是合法的?
栈, 将所有妹子放到栈里, 每来一个男生, 那么就将妹子拉走, 那么最靠左的位置是 栈顶元素的后面一个位置, 因为当前栈顶元素无法匹配右括号, 因此最大匹配的左端点 = 当前剩余的栈顶元素的下一个位置, 特殊情况:栈为空的话, 整个区间都是合法区间(从起点到当前位置全取)
每个元素进栈出栈1次, O(n)
code
class Solution {
public:
int longestValidParentheses(string s) {
stack<int> stk;
int res = 0;
for (int i = 0, start = -1; i < s.size(); i ++ ){ // start表示起点的前一个位置
if (s[i] == '('){
stk.push(i);
}else {
if (stk.size()){ // 先要判断栈不空, 才能继续做
stk.pop(); // pop完后, 需要考虑栈顶是否为空, 计算最大合法区间
if (stk.size()){
res = max(res, i - stk.top()); // 合法的左端点位置 = stk.top() + 1, 因为计算区间 后面还要 + 1, 抵消了
}else { // 如果当前位置是‘)’ , 且栈为空, 表示右括号无法匹配, 那么划线, 并更新下一段的起点
res = max(res, i - start);
}
}else {
start = i;
}
}
}
return res;
}
};
33. 搜索旋转排序数组
81.搜索旋转排序数组 II 简单版本
两次二分, 第1次找分界点, 第二次找答案
code
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = nums.size() - 1;
int l = 0, r = n;
while (l < r){
int mid = l + r + 1 >> 1;
if (nums[mid] >= nums[0]) l = mid;
else r = mid - 1;
}
if (target >= nums[0]) r = l, l = 0;
else l ++, r = n;
while (l < r){
int mid = l + r >> 1;
if (nums[mid] >= target) r = mid;
else l = mid + 1;
}
if (nums[r] != target) return -1;
return r;
}
};
34. 在排序数组中查找元素的第一个和最后一个位置
分析
二分模板题
code
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
if (nums.empty()) return {-1, -1};
vector<int> res;
int l = 0, r = nums.size() - 1;
while (l < r){
int mid = l + r >> 1;
if (nums[mid] >= target) r = mid;
else l = mid + 1;
}
if (nums[l] != target) return {-1, -1};
else res.push_back(l);
l = 0, r = nums.size() - 1;
while (l < r){
int mid = l + r + 1 >> 1;
if (nums[mid] <= target) l = mid;
else r = mid - 1;
}
if (nums[r] != target) return {-1, -1};
else res.push_back(r);
return res;
}
};
35. 搜索插入位置
分析
二分模板题
注意: 右边界可以取到
code
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int l = 0, r = nums.size();
while (l < r){
int mid = l + r >> 1;
if (nums[mid] >= target) r = mid;
else l = mid + 1;
}
return l;
}
};
36. 有效的数独
分析
code
class Solution {
public:
bool isValidSudoku(vector<vector<char>>& board) {
bool st[9];
// 行判断
for (int i = 0; i < 9; i ++){
memset(st, 0, sizeof st);
for (int j = 0; j < 9; j ++){
if (board[i][j] != '.'){
int t = board[i][j] - '1';
if (st[t]) return false;
st[t] = true;
}
}
}
// 列判断
for (int i = 0; i < 9; i ++){
memset(st, 0, sizeof st);
for (int j = 0; j < 9; j ++){
if (board[j][i] != '.'){
int t = board[j][i] - '1';
if (st[t]) return false;
st[t] = true;
}
}
}
// 3x3小方格判断
// i, j 表示3x3小方格, 最左上角位置, 每次偏移3格子
for (int i = 0; i < 9; i += 3)
for (int j = 0; j < 9; j += 3){
memset(st, 0, sizeof st);
// x, y代表3x3小方格内部偏移
for (int x = 0; x < 3; x ++)
for (int y = 0; y < 3; y ++){
if (board[i + x][j + y] != '.'){
int t = board[i + x][j + y] - '1';
if (st[t]) return false;
st[t] = true;
}
}
}
return true;
}
};
37. 解数独
分析
怎么能快速判断当前空位可以填哪些数
需要记录下 每行, 每列, 每个小方阵, 哪些数已经填过了, 开个bool数组就可以了
code
class Solution {
public:
bool row[9][9], col[9][9], cell[3][3][9];
void solveSudoku(vector<vector<char>>& board) {
memset(row, 0, sizeof row);
memset(col, 0, sizeof col);
memset(cell, 0, sizeof cell);
for (int i = 0; i < 9; i ++ )
for (int j = 0; j < 9; j ++ )
if (board[i][j] != '.'){
int t = board[i][j] - '1';
row[i][t] = col[j][t] = cell[i / 3][j / 3][t] = 1;
}
dfs(board, 0, 0);
}
bool dfs(vector<vector<char>> &board, int x, int y){
if (y == 9) y = 0, x ++;
if (x == 9) return true;
if (board[x][y] != '.') return dfs(board, x, y + 1);
for (int i = 0; i < 9; i ++ ){
if (!row[x][i] && !col[y][i] && !cell[x / 3][y / 3][i]) {
row[x][i] = col[y][i] = cell[x / 3][y / 3][i] = true;
board[x][y] = i + '1';
if (dfs(board, x, y + 1)) return true;
board[x][y] = '.';
row[x][i] = col[y][i] = cell[x / 3][y / 3][i] = false;
}
}
return false;
}
};
38. 外观数列
分析
给了一段数字, 将相同的一段读出来即可
外循环i
操作n - 1次, 内循环j
每次读当前字符串s, 然后再新建一个k
指针, 去判断与s[j]
相等的字符的个数
遍历到一段相同的, 就更新t, 然后将j
指针更新成k
code
class Solution {
public:
string countAndSay(int n) {
string s = "1";
for (int i = 0; i < n - 1; i ++ ){
string t; // 每次新的一项用t来表示
for (int j = 0; j < s.size();){
int k = j + 1;
while (k < s.size() && s[k] == s[j]) k ++;
t += to_string(k - j) + s[j];
j = k;
}
s = t;
}
return s;
}
};
39. 组合总和
分析
顺序: 按照当前每个数选几个来搜
联动 leetcode 47全排列, 90子集II
code
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
vector<vector<int>> combinationSum(vector<int>& cs, int target) {
dfs(cs, 0, target);
return res;
}
void dfs(vector<int>& cs, int u, int target){
if (target == 0){
res.push_back(path);
return ;
}
if (u == cs.size()) return ; // 当前枚举完最后一个数, 且还没有合法方案, 直接返回
for (int i = 0; cs[u] * i <= target; i ++ ){ // i表示枚举当前数选几个, 但不能超过target
dfs(cs, u + 1, target - cs[u] * i);
path.push_back(cs[u]);
}
for (int i = 0; cs[u] * i <= target; i ++)
path.pop_back();
}
};
40. 组合总和 II
分析
可能包含重复元素, 但每个元素只能选1次.
举个例子: 可能包含3个3, 但是每个3只能选1次
上一题枚举的时候, 枚举到 > target为止, 这一题, 不仅要被总和限制, 还要被个数限制
在循环的时候 加个限制
需要将每个数的个数求出来, 排个序, 相同的数挨在一起, 方便求出相同数的个数
code
注意递归下一层, 从不相同的数开始
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
vector<vector<int>> combinationSum2(vector<int>& cs, int target) {
sort(cs.begin(), cs.end());
dfs(cs, 0, target);
return res;
}
void dfs(vector<int>& cs, int u, int target){
if (target == 0){
res.push_back(path);
return;
}
if (u == cs.size()) return;
int k = u;
while (k < cs.size() && cs[k] == cs[u]) k ++;
int cnt = k - u;
for (int i = 0; cs[u] * i <= target && i <= cnt; i ++ ){
dfs(cs, k, target - cs[u] * i); // 注意这里下一层要从k开始了
path.push_back(cs[u]);
}
for (int i = 0; cs[u] * i <= target && i <= cnt; i ++ )
path.pop_back();
}
};