文章目录
31.下一个排列
原题链接:
思路:
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int k = nums.size() - 1;
//从后往前,第一次遍历,找到第一个非降序的位置k-1
while(k > 0 && nums[k-1] >= nums[k]) k--;
//全部逆序的情况,直接反转序列
if(k <= 0){
reverse(nums.begin(), nums.end());
}else{
int t = k;
//从降序序列中,找到最接近nums[k-1]的值,并交换
while(t < nums.size() && nums[t] > nums[k-1]) t++;
swap(nums[t-1], nums[k - 1]);
//在反转降序序列
reverse(nums.begin() + k, nums.end());
}
}
};
32.最长有效括号
原题链接:
思路:
class Solution {
public:
int longestValidParentheses(string s) {
stack<int> stk;
int res = 0;
//枚举每一个字符,start 用于记录非法的右括号位置
for(int i = 0, start = -1; i < s.size(); i++){
//将左括号的下标压入栈
if(s[i] == '(') stk.push(i);
else{
//当前找右括号对应合法括号的最大长度
if(stk.size()){
//将与当前右括号匹配的左括号下标出栈
stk.pop();
if(stk.size()) res = max(res, i - stk.top());
else res = max(res, i - start);
}else{
start = i;
}
}
}
return res;
}
};
33.搜索旋转排序数组
原题链接:
思路:
class Solution {
public int search(int[] nums, int target) {
if(nums == null || nums.length < 1) return -1;
// 二分找pivot, 这里用性质>=nums[0], 所以当找到边界时, 它将是最后一个比nums[0]大的数
int l = 0, r = nums.length-1;
while(l < r){
int mid = l + r + 1 >> 1;
if(nums[mid] >= nums[0]) l = mid;
else r = mid - 1;
}
// 确定target在哪一段中
if(target >= nums[0]) {l = 0;}
else {l = r+1; r = nums.length-1;}
// 二分找目标值
while(l < r){
int mid = l + r + 1 >> 1;
if(target >= nums[mid]) l = mid;
else r = mid - 1;
}
// 这里写成nums[r], 当数组只有一个元素时, 两个二分查找代码都没有走, 而l在上面被+1, 这时会越界, 而r是length-1还是0, 不会产生越界
if(nums[r] == target) return l;
else return -1;
}
}
34.在排序数组中查找元素的第一个和最后一个位置
原题链接:
思路:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
if(nums.empty()) return {-1, -1};
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[r] != target) return {-1,-1};
int L = r;
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;
}
return {L, r};
}
};
35.搜索插入位置
原题链接:
思路:
使用二分,根据两段性,找到要插入位置的前一个节点
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.有效的数独
原题链接:
思路:
1.使用bool数组记录每行、每列、每个九宫格的数字情况
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;
}
}
}
// 判断小方格
for (int i = 0; i < 9; i += 3)
for (int j = 0; j < 9; j += 3) {
memset(st, 0, sizeof st);
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.解数独
原题链接:
思路:
(递归回溯)
预处理出 col、row 和 cell数组。
从 (0,0) 位置开始尝试并递归。遇到 . 时,枚举可以填充的数字
如果成功到达结尾,则返回 true,告知递归可以终止。
class Solution {
public:
//定义行、列、小方阵,用于判断0~9的数字有没有
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] = true;
}
}
//从board左上角开始深搜
dfs(board, 0, 0);
}
bool dfs(vector<vector<char>>& board, int x, int y){
//如果列搜到最右边,则从下一行开始
if(y == 9) x++, y = 0;
//如果x=9,则搜索完成
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]){
board[x][y]='1' + i;
row[x][i] = col[y][i] = cell[x/3][y/3][i] = true;
// 如果搜到了返回true
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.外观数列
原题链接:
思路:
读懂题意就比较简单了,利用双指针,统计相同数字的长度,在连起来
class Solution {
public:
string countAndSay(int n) {
//初始字符串
string s = "1";
//计算到第n项
for(int i = 0; i < n -1; i++){
string t;
for(int j = 0; j <s.size();){
int k = j + 1;
//统计相同数字字符的个数
while(k < s.size() && s[j] == s[k]) k++;
//将个数与数字连接到一起
t += to_string(k - j) + s[j];
//将j更新到k的位置
j = k;
}
s = t;
}
return s;
}
};
39.组合总和
原题链接:
思路:
递归枚举
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> combinationSum(vector<int>& c, int target) {
dfs(c, 0, target);
return ans;
}
void dfs(vector<int>& c, int u, int target){
// 如果搜到了,将path加入答案中
if(target == 0){
ans.push_back(path);
return;
}
//搜不到,返回
if(u == c.size()) return;
//暴力搜索每个数字的数量
for(int i = 0; c[u] * i <= target; i++){
dfs(c, u+1, target-c[u] * i);
path.push_back(c[u]);
}
// 恢复现场
for(int i = 0; c[u] * i <= target; i++){
path.pop_back();
}
}
};
40.组合总和II
原题链接:
思路:
和上一题类似,在暴搜每个数字时加一个个数限制
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> combinationSum2(vector<int>& c, int target) {
//先排序,为了统计每个数字出现的个数
sort(c.begin(), c.end());
dfs(c, 0, target);
return ans;
}
void dfs(vector<int>& c, int u, int target){
if(target == 0){
ans.push_back(path);
return;
}
if(u == c.size()) return;
// 统计当前数字在该数组中有几个
int k = u + 1;
while(k <c.size() && c[u] == c[k]) k++;
int cnt = k - u;
//加一个限制,就是当前数字枚举的个数
for(int i = 0; c[u] * i <= target && i <= cnt; i++){
dfs(c, k, target - c[u] * i);
path.push_back(c[u]);
}
for(int i = 0; c[u] * i <= target && i <= cnt; i++){
path.pop_back();
}
}
};