记录5-6月力扣刷题,持续刷题中~
2024.05
15.三数之和
双指针或者哈希表,注意去重的操作要考虑仔细
cpp
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for (int i = 0; i < nums.size(); i++) {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if (nums[i] > 0) {
return result;
}
// 错误去重a方法,将会漏掉-1,-1,2 这种情况
/*
if (nums[i] == nums[i + 1]) {
continue;
}
*/
// 正确去重a方法
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while (right > left) {
// 去重复逻辑如果放在这里,0,0,0 的情况,可能直接导致 right<=left 了,从而漏掉了 0,0,0 这种三元组
/*
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
*/
if (nums[i] + nums[left] + nums[right] > 0) right--;
else if (nums[i] + nums[left] + nums[right] < 0) left++;
else {
result.push_back(vector<int>{nums[i], nums[left], nums[right]});
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
// 找到答案时,双指针同时收缩
right--;
left++;
}
}
}
return result;
}
};
时间复杂度:O(n^2)
空间复杂度:O(1)
18.四数之和
双指针法,三数之和扩展(多一层for循环)
cpp
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
for (int k = 0; k < nums.size(); k++) {
// 剪枝处理
if (nums[k] > target && nums[k] >= 0) {
break; // 这里使用break,统一通过最后的return返回
}
// 对nums[k]去重
if (k > 0 && nums[k] == nums[k - 1]) {
continue;
}
for (int i = k + 1; i < nums.size(); i++) {
// 2级剪枝处理
if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
break;
}
// 对nums[i]去重
if (i > k + 1 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while (right > left) {
// nums[k] + nums[i] + nums[left] + nums[right] > target 会溢出
if ((long) nums[k] + nums[i] + nums[left] + nums[right] > target) {
right--;
// nums[k] + nums[i] + nums[left] + nums[right] < target 会溢出
} else if ((long) nums[k] + nums[i] + nums[left] + nums[right] < target) {
left++;
} else {
result.push_back(vector<int>{nums[k], nums[i], nums[left], nums[right]});
// 对nums[left]和nums[right]去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
// 找到答案时,双指针同时收缩
right--;
left++;
}
}
}
}
return result;
}
};
时间复杂度:O(n^3)
空间复杂度:O(1)
503.下一个更大的元素II
单调栈的想法,相比较739.每日温度,需要多处理一步循环数组问题
cpp
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
vector<int> result(nums.size(), -1);
if (nums.size() == 0) return result;
stack<int> st;
for (int i = 0; i < nums.size() * 2; i++) {
// 模拟遍历两边nums,注意一下都是用i % nums.size()来操作
while (!st.empty() && nums[i % nums.size()] > nums[st.top()]) {
result[st.top()] = nums[i % nums.size()];
st.pop();
}
st.push(i % nums.size());
}
return result;
}
};
102.二叉树的层序遍历
使用队列进行层序遍历
cpp
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root);
vector<vector<int>> result;
while (!que.empty()) {
int size = que.size();
vector<int> vec;
// 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
result.push_back(vec);
}
return result;
}
};
使用递归法
cpp
class Solution {
public:
void order(TreeNode* cur, vector<vector<int>>& result, int depth)
{
if (cur == nullptr) return;
if (result.size() == depth) result.push_back(vector<int>());
result[depth].push_back(cur->val);
order(cur->left, result, depth + 1);
order(cur->right, result, depth + 1);
}
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
int depth = 0;
order(root, result, depth);
return result;
}
};
以上可以作为模板,对于107.二叉树的层序遍历II,199.二叉树的右视图,637.二叉树的层平均值等问题可以相似地解决
117.填充每个结点的下一个右侧结点指针II
cpp
// DFS
class Solution {
vector<Node *> pre;
public:
Node *connect(Node *root) {
dfs(root, 0); // 根节点的深度为 0
return root;
}
void dfs(Node *node, int depth) {
if (node == nullptr) {
return;
}
if (depth == pre.size()) { // node 是这一层最左边的节点
pre.push_back(node);
} else { // pre[depth] 是 node 左边的节点
pre[depth]->next = node; // node 左边的节点指向 node
pre[depth] = node;
}
dfs(node->left, depth + 1);
dfs(node->right, depth + 1);
}
};
// BFS
class Solution {
public:
Node *connect(Node *root) {
if (root == nullptr) {
return nullptr;
}
vector<Node*> q = {root};
while (!q.empty()) {
vector<Node*> nxt;
for (int i = 0; i < q.size(); i++) {
Node *node = q[i];
if (i) { // 连接同一层的两个相邻节点
q[i - 1]->next = node;
}
if (node->left) {
nxt.push_back(node->left);
}
if (node->right) {
nxt.push_back(node->right);
}
}
q = move(nxt);
}
return root;
}
};
// BFS + 链表
class Solution {
public:
Node *connect(Node *root) {
Node *dummy = new Node();
Node *cur = root;
while (cur) {
dummy->next = nullptr;
Node *nxt = dummy; // 下一层的链表
while (cur) { // 遍历当前层的链表
if (cur->left) {
nxt->next = cur->left; // 下一层的相邻节点连起来
nxt = cur->left;
}
if (cur->right) {
nxt->next = cur->right; // 下一层的相邻节点连起来
nxt = cur->right;
}
cur = cur->next; // 当前层链表的下一个节点
}
cur = dummy->next; // 下一层链表的头节点
}
delete dummy;
return root;
}
};
77.组合问题
cpp
class Solution {
private:
vector<vector<int>> res;
vector<int> path;
void backtracking(int n, int k, int startIndex) {
if (path.size() == k) {
res.push_back(path);
return;
}
for (int i = startIndex; i <= n; i++) {
path.push_back(i);
backtracking(n, k, i + 1); // 从当前数的下一个数开始
path.pop_back();
}
}
public:
vector<vector<int>> combine(int n, int k) {
// k个数若是k层for循环暴力遍历,则不好写出,故用回溯
backtracking(n, k, 1); // 包含从1到n的数
return res;
}
};
cpp
class Solution {
public:
vector<vector<int>> combine(int n, int k) {
vector<vector<int>> ans;
vector<int> path;
function<void(int)> dfs = [&](int i) {
int d = k - path.size(); // 还要选 d 个数
if (d == 0) {
ans.emplace_back(path);
return;
}
for (int j = i; j >= d; --j) {
path.push_back(j);
dfs(j - 1);
path.pop_back();
}
};
dfs(n);
return ans;
}
};
复杂度分析:
- 时间复杂度:分析回溯问题时间复杂度,有个通用的公式:路径长度 x 搜索树的叶子数,对于本题,它等于O(k*C(n,k))
- 空间复杂度:O(k)
53.最大子数组和
动态规划求解,也有贪心的思想,局部最优推导全局最优,在遇到加上负值让结果不如下一个值的时候,重新选择开头值
cpp
class Solution {
public:
int maxSubArray(vector<int>& nums) {
//动态规划,子问题定义为以nums[i]为末尾的最大子数组和,判断当前数是否大于0
if (nums.size() == 0) return 0;
vector<int> dp(nums.size(), 0); // dp[i]表示包括i之前的最大连续子序列和
dp[0] = nums[0];
int result = dp[0];
for (int i = 1; i < nums.size(); i++) {
dp[i] = max(dp[i - 1] + nums[i], nums[i]); // 遇到更大的值或者说原来值为负,重新记录值
if (dp[i] > result) result = dp[i]; // result 保存dp[i]的最大值
}
return result;
}
};
941.有效的山脉数组
数组越界,单调问题,考虑少了,提交了4次。。。
cpp
class Solution {
public:
bool validMountainArray(vector<int>& arr) {
if (arr.size() < 3) return false;
int left = 0, right = arr.size() - 1;
while (left < arr.size() - 1 && arr[left] < arr[left + 1]) left++; // 注意数组越界问题
while (right > 0 && arr[right] < arr[right - 1]) right--;
if (left == right && left != 0 && right != arr.size() - 1) return true; // 单调递增和单调递减不算
return false;
}
};
1002.查找常用字符
哈希表,通过构造数组来模拟
cpp
class Solution {
public:
vector<string> commonChars(vector<string>& A) {
vector<string> result;
if (A.size() == 0) return result;
int hash[26] = {0}; // 用来统计所有字符串里字符出现的最小频率
for (int i = 0; i < A[0].size(); i++) { // 用第一个字符串给hash初始化
hash[A[0][i] - 'a']++;
}
int hashOtherStr[26] = {0}; // 统计除第一个字符串外字符的出现频率
for (int i = 1; i < A.size(); i++) {
memset(hashOtherStr, 0, 101; // 题中字符串长度<=100
for (int j = 0; j < A[i].size(); j++) {
hashOtherStr[A[i][j] - 'a']++;
}
// 更新hash,保证hash里统计26个字符在所有字符串里出现的最小次数
for (int k = 0; k < 26; k++) {
hash[k] = min(hash[k], hashOtherStr[k]);
}
}
// 将hash统计的字符次数,转成输出形式
for (int i = 0; i < 26; i++) {
while (hash[i] != 0) { // 注意这里是while,多个重复的字符
string s(1, i + 'a'); // char -> string
result.push_back(s);
hash[i]--;
}
}
return result;
}
};
cpp
class Solution {
private:
vector<vector<string>> result;
// n 为输入的棋盘大小
// row 是当前递归到***的第几行了
void backtracking(int n, int row, vector<string>& chessboard) {
if (row == n) {
result.push_back(chessboard);
return;
}
for (int col = 0; col < n; col++) {
if (isValid(row, col, chessboard, n)) { // 验证合法就可以放
chessboard[row][col] = 'Q'; // 放置皇后
backtracking(n, row + 1, chessboard);
chessboard[row][col] = '.'; // 回溯,撤销皇后
}
}
}
bool isValid(int row, int col, vector<string>& chessboard, int n) {
int count = 0;
// 检查列
for (int i = 0; i < row; i++) { // 这是一个剪枝
if (chessboard[i][col] == 'Q') {
return false;
}
}
// 检查 45度角是否有皇后
for (int i = row - 1, j = col - 1; i >=0 && j >= 0; i--, j--) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
// 检查 135度角是否有皇后
for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
return true;
}
public:
vector<vector<string>> solveNQueens(int n) {
// 声明所用变量再使用
vector<string> chessboard(n, string(n, '.'));
backtracking(n, 0, chessboard);
return result;
}
};
52.N皇后
cpp
class Solution {
private:
int res = 0;
// vector<vector<string>> way;
void backtracking(int n, int row, vector<string>& chessboard) {
if (row == n) {
// way.push_back(chessboard);
res++;
return;
}
for (int col = 0; col < n; col++) {
if (isValid(n, row, col, chessboard)) {
chessboard[row][col] = 'Q';
backtracking(n, row + 1, chessboard);
chessboard[row][col] = '.';
}
}
}
bool isValid(int n, int row, int col, vector<string>& chessboard) {
for (int i = 0; i < row; i++) { // 检查列
if (chessboard[i][col] == 'Q') return false;
}
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { // 检查副对角线
if (chessboard[i][j] == 'Q') return false;
}
for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { // 检查主对角线
if (chessboard[i][j] == 'Q') return false;
}
return true;
}
public:
int totalNQueens(int n) {
vector<string> chessboard(n, string(n, '.'));
backtracking(n, 0, chessboard);
return res;
}
};
-
时间复杂度:O(n!),n为皇后的数量
-
空间复杂度:O(n)
673.最长递增子序列的个数
cpp
class Solution {
public:
int findNumberOfLIS(vector<int>& nums) {
if (nums.size() <= 1) return nums.size();
vector<int> dp(nums.size(), 1);
vector<int> count(nums.size(), 1);
int maxCount = 0;
for (int i = 1; i < nums.size(); i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
if (dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1;
count[i] = count[j];
} else if (dp[j] + 1 == dp[i]) {
count[i] += count[j];
}
}
if (dp[i] > maxCount) maxCount = dp[i];
}
}
int result = 0;
for (int i = 0; i < nums.size(); i++) {
if (maxCount == dp[i]) result += count[i];
}
return result;
}
};
- 时间复杂度:O(n^2)
- 空间复杂度:O(n)
1971.寻找图中是否存在路径
并查集主要有三个功能。
- 寻找根节点,函数:find(int u),也就是判断这个节点的祖先节点是哪个
- 将两个节点接入到同一个集合,函数:join(int u, int v),将两个节点连在同一个根节点上
- 判断两个节点是否在同一个集合,函数:isSame(int u, int v),就是判断两个节点是不是同一个根节点
这道题目是并查集基础题目,题目中各个点是双向图链接,那么判断 一个顶点到另一个顶点有没有有效路径其实就是看这两个顶点是否在同一个集合里。
cpp
class Solution {
private:
int n = 200001; // 节点数量 <= 20000
vector<int> father = vector<int> (n, 0); // C++里的一种数组结构
// 并查集初始化
void init() {
for (int i = 0; i < n; ++i) { father[i] = i;
}
}
// 并查集里寻根的过程
int find(int u) {
return u == father[u] ? u : father[u] = find(father[u]);
}
// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
// 将v->u 这条边加入并查集
void join(int u, int v) {
u = find(u); // 寻找u的根
v = find(v); // 寻找v的根
if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
father[v] = u;
}
public:
bool validPath(int n, vector<vector<int>>& edges, int source, int destination) {
init();
for (int i = 0; i < edges.size(); i++) {
join(edges[i][0], edges[i][1]);
}
return isSame(source, destination);
}
};
684.冗余连接
树可以看作是一个连通且无环的无向图
cpp
class Solution {
private:
int n = 1001; // 节点数量3 到 1000
vector<int> father = vector<int> (n, 0); // C++里的一种数组结构
// 并查集初始化
void init() {
for (int i = 0; i < n; ++i) {
father[i] = i;
}
}
// 并查集里寻根的过程
int find(int u) {
return u == father[u] ? u : father[u] = find(father[u]);
}
// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
// 将v->u 这条边加入并查集
void join(int u, int v) {
u = find(u); // 寻找u的根
v = find(v); // 寻找v的根
if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
father[v] = u;
}
public:
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
init();
for (int i = 0; i < edges.size(); i++) {
if (isSame(edges[i][0], edges[i][1])) return edges[i];
else join(edges[i][0], edges[i][1]);
}
return {};
}
};
2024.06
2928.给小朋友们分糖果I
容斥原理,列出所有符合的情况再减去不符合的情况
cpp
class Solution {
public:
int c2(int n) {
return n > 1 ? n * (n - 1) / 2 : 0;
}
int distributeCandies(int n, int limit) { // n个糖果分3堆,在n+2(n+3-1)个空位中放挡板
int res = 0;
res = c2(n + 2) - 3 * c2(n + 2 - (limit + 1)) + 3 * c2(n + 2 - 2 * (limit + 1)) -
c2(n + 2 - 3 * (limit + 1));
return res;
}
// int distributeCandies(int n, int limit) { // 嵌套for循环,i,j和n-i-j都需要小于等于limit
// int ans = 0;
// for (int i = 0; i <= limit; i++) {
// for (int j = 0; j <= limit; j++) {
// if (i + j > n) break; // 不符情况
// if (n - i - j <= limit) ans++;
// }
// }
// return ans;
// }
};
复杂度分析
-
时间复杂度:O(1)
-
空间复杂度:O(1)
669.修剪二叉搜索树
递归三部曲:
- 确定递归函数的参数以及返回值
- 确定终止条件
- 确定单层递归的逻辑
cpp
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
if (root == nullptr ) return nullptr;
if (root->val < low) {
TreeNode* right = trimBST(root->right, low, high); // 寻找符合区间[low, high]的节点
return right;
}
if (root->val > high) {
TreeNode* left = trimBST(root->left, low, high); // 寻找符合区间[low, high]的节点
return left;
}
root->left = trimBST(root->left, low, high); // root->left接入符合条件的左孩子
root->right = trimBST(root->right, low, high); // root->right接入符合条件的右孩子
return root;
}
};
复杂度分析:
- 时间复杂度:O(n),其中n为二叉树的结点数目
- 空间复杂度:O(n),递归栈最坏情况下需要O(n)的空间
15.三数之和
问题是考虑的不全,题意是三元组中元素可以重复,三元组不可重复
cpp
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for (int i = 0; i < nums.size(); i++) {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if (nums[i] > 0) {
return result;
}
// 错误去重a方法,将会漏掉-1,-1,2 这种情况
/*
if (nums[i] == nums[i + 1]) {
continue;
}
*/
// 正确去重a方法
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while (right > left) {
// 去重复逻辑如果放在这里,0,0,0 的情况,可能直接导致 right<=left 了,从而漏掉了 0,0,0 这种三元组(0,0,0,0,0)
/*
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
*/
if (nums[i] + nums[left] + nums[right] > 0) right--;
else if (nums[i] + nums[left] + nums[right] < 0) left++;
else {
result.push_back(vector<int>{nums[i], nums[left], nums[right]});
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
// 找到答案时,双指针同时收缩
right--;
left++;
}
}
}
return result;
}
};
- 时间复杂度:排序算法nlogn,for循环和双指针复杂度n^2,所以时间复杂度O(n)
- 空间复杂度:O(1),仅仅用到若干变量
312.戳气球
cpp
// 记忆化搜索方法
class Solution {
public:
vector<vector<int>> rec;
vector<int> val;
public:
int solve(int left, int right) {
if (left >= right - 1) { // 递归结束条件
return 0;
}
if (rec[left][right] != -1) {
return rec[left][right];
}
for (int i = left + 1; i < right; i++) {
int sum = val[left] * val[i] * val[right];
sum += solve(left, i) + solve(i, right);
rec[left][right] = max(rec[left][right], sum);
}
return rec[left][right];
}
int maxCoins(vector<int>& nums) {
int n = nums.size();
val.resize(n + 2);
for (int i = 1; i <= n; i++) {
val[i] = nums[i - 1];
}
val[0] = val[n + 1] = 1;
rec.resize(n + 2, vector<int>(n + 2, -1));
return solve(0, n + 1);
}
};
// 动态规划方法
class Solution {
public:
int maxCoins(vector<int>& nums) {
// 先将num[-1]和nums[n] = 1 添加到数组
int n = nums.size() + 2;
nums.insert(nums.begin(), 1);
nums.push_back(1);
//dp[0][n - 1]为最后要求的答案,假设最后戳破的气球为第i个,状态转移方程:
//dp[start][end] = dp[start][i] + dp[i][end] + nums[start] * nums[i] * nums[end] (start < i < end)
//从状态转移方程看出,每次需要后面遍历的结果,即dp[start][i]和dp[i][end],不需要管start前面的值
vector<vector<int>> dp(n, vector<int>(n, 0));
for (int start = n - 1; start >= 0; start--) {
for (int end = start; end < n; end++) {
if (end - start < 2) continue; // 没有气球可以戳
for (int i = start + 1; i < end; i++) {
dp[start][end] = max(dp[start][end], dp[start][i] + dp[i][end] + nums[start] *
nums[i] * nums[end]);
}
}
}
return dp[0][n - 1];
}
};
复杂度分析:
- 时间复杂度:O(n^3^),其中n是气球数量,状态数为n^2^,状态转移复杂度为O(n^3^)
- 空间复杂度:O(n^2^),n为气球数量
24.两两交换链表中的结点
cpp
class Solution {
public:
ListNode *swapPairs(ListNode* head) {
auto dummy = new ListNode(0, head);
auto node0 = dummy;
auto node1 = head;
while (node1 && node1->next) {
auto node2 = node1->next;
auto node4 = node2->next;
node0->next = node2; // 0->2
node2->next = nede1; // 2->1
node1->next = node3; // 1->3
node0 = node1; // 下一轮交换,0是1
node1 = node3; // 下一轮交换,1是3
}
return dummy->next; // 返回新链表的头结点
}
};
类似于上述方法,使用递归
cpp
class Solution {
public:
ListNode *swapPairs(ListNode* head) {
// 递归结束条件
if (head == nullptr || head->next == nullptr) return head;
auto node1 = head;
auto node2 = node1->next;
auto node3 = node2->next;
node1->next = swapPairs(node3); // 指向递归返回的头结点
node2->next = node1; // 2->1
return node2; // 返回交换后新的头结点
}
};
复杂度分析
- 时间复杂度:O(n),其中n为链表的长度;
- 空间复杂度:O(n),递归需O(n)的栈空间。
151.反转字符串中的单词
双指针,主要处理多余空格和反转字符串
cpp
class Solution {
public:
string reverseWords(string s) {
int m = s.size() - 1;
string res;
while (s[m] == ' ' && m > 0) m--;
int n = m; // 移除末尾空格后,字符串长度
while (m >= 0) {
while (m >= 0 && s[m] != ' ') m--;
res += s.substr(m + 1, n - m) + ' '; // 获取单词并加上空格
while (m >= 0 && s[m] == ' ') m--; // 获取下一个单词末尾位置
n = m;
}
return res.substr(0, res.size() - 1); // 忽略最后一位的空格
}
};
代码随想录参考代码
cpp
class Solution {
public:
void reverse(string& s, int start, int end) { // 看下344.翻转字符串
for (int i = start, j = end; i < j; i++, j--) {
swap(s[i], s[j]);
}
}
void removeExtraSpaces(string& s) { // 去除所有空格并在相邻单词之间添加空格, 快慢指针。
int slow = 0; // 看下27.移除元素
for (int i = 0; i < s.size(); ++i) {
if (s[i] != ' ') { // 遇到非空格就处理,即删除所有空格。
if (slow != 0) s[slow++] = ' '; // 手动控制空格,给单词之间添加空格。slow != 0说明不是第一个单词,需要在单词前添加空格。
while (i < s.size() && s[i] != ' ') { // 补上该单词,遇到空格说明单词结束。
s[slow++] = s[i++];
}
}
}
s.resize(slow); // slow的大小即为去除多余空格后的大小。
}
string reverseWords(string s) {
removeExtraSpaces(s); // 去除多余空格,保证单词之间之只有一个空格,且字符串首尾没空格。
reverse(s, 0, s.size() - 1);
int start = 0; // removeExtraSpaces后保证第一个单词的开始下标一定是0。
for (int i = 0; i <= s.size(); ++i) {
if (i == s.size() || s[i] == ' ') { //到达空格或者串尾,说明一个单词结束。进行翻转。
reverse(s, start, i - 1); //翻转,注意是左闭右闭 []的翻转。
start = i + 1; //更新下一个单词的开始下标start
}
}
return s;
}
};
时间复杂度:O(n)
空间复杂度:O(n)
28.找出字符串中第一个匹配项的下标
暴力遍历,直接寻找是否有匹配项
cpp
class Solution {
public:
int strStr(string haystack, string needle) {
// 字符串匹配项
// return haystack.find(needle);
int m = haystack.size();
int n = needle.size();
for (int i = 0; i + n <= m; i++) { // 注意这里<=,因为可能两个字符串都是一个字符
bool flag = true;
for (int j = 0; j < n; j++) {
if (haystack[i + j] != needle[j]) {
flag = false;
break;
}
}
if (flag) return i; // 一次遍历完是否都匹配
}
return -1;
}
};
KMP算法
最长前后缀:最长的不包含最后一个字符的所有以第一个字符开头的连续子串,后缀是不包含第一个字符的所有以最后一个字符结尾的连续子串
cpp
class Solution {
public:
void getNext(int* next, const string& s) {
int j = -1;
next[0] = j;
for (int i = 1; i < s.size(); i++) { // i指向文本串,j+1指向模式串
while (j >= 0 && s[i] != s[j + 1]) { // 不相等向前回退
j = next[j];
}
if (s[i] == s[j + 1]) { // 找到相同的前后缀
j++;
}
next[i] = j; // 将j的长度赋给next[i]
}
}
int strStr(string haystack, string needle) {
if (needle.size() == 0) {
return 0;
}
vector<int> next(needle.size());
getNext(&next[0], needle);
int j = -1;
for (int i = 0; i < haystack.size(); i++) {
while (j >= 0 && haystack[i] != needle[j + 1]) {
j = next[j];
}
if (haystack[i] == needle[j + 1]) {
j++;
}
if (j == (needle.size() - 1)) {
return (i - needle.size() + 1);
}
}
return -1;
}
}
- 时间复杂度:O(n + m)
- 空间复杂度:O(m),只需要保存字符串needle的前缀表
257.二叉树的所有路径
cpp
// 深度优先搜索方法
class Solution {
public:
void construct_paths(TreeNode* root, string path, vector<string>& paths) {
if (root != nullptr) {
path += to_string(root->val);
if (root->left == nullptr && root->right == nullptr) { // 当前节点是叶子节点
paths.push_back(path); // 把路径加入到答案中
} else {
path += "->"; // 当前节点不是叶子节点,继续递归遍历
construct_paths(root->left, path, paths);
construct_paths(root->right, path, paths);
}
}
}
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> paths;
construct_paths(root, "", paths);
return paths;
}
};
// 广度优先搜索方法
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> paths;
if (root == nullptr) {
return paths;
}
queue<TreeNode*> node_queue;
queue<string> path_queue;
node_queue.push(root);
path_queue.push(to_string(root->val));
while (!node_queue.empty()) {
TreeNode* node = node_queue.front();
string path = path_queue.front();
node_queue.pop();
path_queue.pop();
if (node->left == nullptr && node->right == nullptr) {
paths.push_back(path);
} else {
if (node->left != nullptr) {
node_queue.push(node->left);
path_queue.push(path + "->" + to_string(node->left->val));
}
if (node->right != nullptr) {
node_queue.push(node->right);
path_queue.push(path + "->" + to_string(node->right->val));
}
}
}
return paths;
}
};
- 时间复杂度:O(n^2^)
- 空间复杂度:O(n^2^)
700.二叉搜索树中的搜索
写个简单题
cpp
// 递归法
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
if (root == nullptr || root->val == val) return root;
if (root->val > val) return searchBST(root->left, val); // 二叉搜索树的性质,有序性
if (root->val < val) return searchBST(root->right, val);
return nullptr; // 没找到
}
}
// 迭代法
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
while (root != nullptr) {
if (root->val > val) root = root->left;
else if (root->val < val) root = root->right;
else return root; // 搜索到结点
}
return nullptr;
}
}
迭代法时间复杂度
- 时间复杂度:O(n),其中n是二叉搜索树的节点数。最坏情况下二叉搜索树是一条链,且要找的元素比链末尾的元素值还要小(大),这种情况下我们需要迭代n次。
- 空间复杂度:O(1),没有用到额外空间。