2024年刷题记录
马上要开始找实习了,又开始重启刷题计划了!加油冲冲冲!刷题的顺序follow代码随想录的60天刷题计划!感谢FuCosmo的总结!之前都是按照C++的语法进行刷题的,这次也同样使用C++。
Day 1 数组
这些题过年前都刷过了,所以过的快一些。通过写一些题解的方式来,帮助自己回顾这些方法,记住一些核心点。
704. 二分查找
- 训练是否取等号,这里选择的是双边都闭合的空间
- 对于mid的计算方式,有两种mid = (left + right) / 2或者是mid = left + (left - right) / 2
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int mid;
while (left <= right) {
mid = (left + right) / 2;
if (nums[mid] == target){
return mid;
} else if (nums[mid] < target){
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
};
27. 移除元素
- 双指针的思想
- 一个指针用来遍历数组中的所以元素(指向当前将要处理的元素)
- 一个指针用来记录下一个将要赋值的位置
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int i = 0;
int j = 0;
while (i < nums.size()) {
if (nums[i] != val) {
nums[j] = nums[i];
j++;
}
i++;
}
return j;
}
};
977. 有序数组的平方
- 暴力的解放,利用sort函数
- 基本的语法
- vector的创建
vector<int> ans;
vector<int> ans(n);
- vector中添加元素
ans.push_back(num);
- vector的排序
sort(ans.begin(), ans.end());
- vector的创建
- 时间复杂度是 O(nlogn),其中 n 是数组 nums的长度。
- 空间复杂度是 O(logn),除了存储答案的数组以外,我们需要 O(logn) 的栈空间进行排序
- 基本的语法
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int i = 0;
vector<int> ans;
while (i < nums.size()){
ans.push_back(nums[i] * nums[i]);
i++;
}
sort(ans.begin(), ans.end());
return ans;
}
};
- 双指针的解法
- 非递减数组,元素当中存在负数
- 第一个指针指向找到第一个大于等于0的元素
- 如果第一个指针为0,则不需要第二个指针
- 反之,第二个指针指向第一个元素左侧的元素
- 比较左右指针两个元素的大小,逐个加入
- 这里需要用到三个循环
- 同时移动两个指针
- 当一个指针已经移动完,则只移动单侧的指针
- 时间复杂度是O(n)。其中 n 是数组 nums 的长度。
- 空间复杂度是O(1)。除了存储答案的数组以外,我们只需要维护常量空间。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int i = 0;
vector<int> ans;
for (int num : nums) {
if (num < 0) {
i++;
} else {
break;
}
}
if (i == 0) {
for (int num : nums) {
ans.push_back(num * num);
}
return ans;
} else {
int j = i - 1;
while (i < nums.size() && j >= 0) {
if (nums[i] * nums[i] < nums[j] * nums[j]) {
ans.push_back(nums[i] * nums[i]);
i++;
} else {
ans.push_back(nums[j] * nums[j]);
j--;
}
}
while (i < nums.size()) {
ans.push_back(nums[i] * nums[i]);
i++;
}
while (j >= 0){
ans.push_back(nums[j] * nums[j]);
j--;
}
}
return ans;
}
- 双指针的解法二
- 一个指针指向第一个元素
- 另一个指针指向最后一个元素
- 从后往前加元素
- 时间复杂度是O(n)。其中 n 是数组 nums 的长度。
- 空间复杂度是O(1)。除了存储答案的数组以外,我们只需要维护常量空间。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int n = nums.size();
vector<int> ans(n);
for (int i = 0, j = n - 1, pos = n - 1; i <= j; pos--) {
if (nums[i] * nums[i] > nums[j] * nums[j]) {
ans[pos] = nums[i] * nums[i];
i++;
} else {
ans[pos] = nums[j] * nums[j];
j--;
}
}
return ans;
}
};
Day 2
🚩209.长度最小的子数组
- 双指针的思想
- 左右两个指针,当当前区间内的值小于target,则移动右指针;反之移动左指针
- 区间是左闭右开的
- 利用cnt来记录最短的长度;(right - left + 1)
- 这道题感觉是滑动窗口的思想
- 基本语法知识
- 三目运算符:
条件表达式?True : False
- INT的最大值:
int ans = INT_MAX
- 三目运算符:
// 有点冗余的写法
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int left = 0;
int right = left + 1;
int cnt = nums[0];
int n = nums.size();
int minlength = n + 1;
while(right < n) {
if (cnt < target) {
cnt += nums[right];
right++;
} else if (cnt >= target) {
if ((right - left) < minlength) {
minlength = right - left;
}
cnt -= nums[left];
left++;
}
}
// 当right已经走到最右端了,但cnt依旧大于0
while(left < n) {
if (cnt >= target) {
if ((right - left) < minlength) {
minlength = right - left;
}
cnt -= nums[left];
left++;
} else {
break;
}
}
return (minlength > n) ? 0 : minlength;
}
};
// 看完题解后的优化代码
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int left = 0;
int right = 0;
int cnt = 0;
int n = nums.size();
int minlength = n + 1;
while(right < n) {
cnt += nums[right];
while (cnt >= target) {
minlength = min(minlength, right - left + 1);
cnt -= nums[left];
left++;
}
right++;
}
return (minlength > n) ? 0 : minlength;
}
};
- 时间复杂度为:O(n)
- 空间复杂度为:O(1)
Day 3 栈与队列
239. 滑动窗口最大值
- 用一个队列来存储当前窗口内的元素,对队列中的元素进行排序,取出最大值
347.前K个高频元素
- 使用字典,key为对应的元素,value为对应的出现次数;遍历整个数组,然后按照value进行排序,然后输出前K个结果;
- 思想是上述的思想;但是如何根据value排序是一个难点
- 这里用到了堆的思想,也就是优先队列的思想
- 基本语法:
- priority_queue的基本函数:
push(), pop(), top(), empty(), size();
- 默认为大根堆
- 如果想要小元素放在队首,则可以使用以下的方式
priority_queue<int,vector<int>,greater<int>>q
- C++11中的STL 可以使用
emplace()
与emplace_back()
来代替insert()
与push_back()
- priority_queue的基本函数:
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int,int> ans;
for (int num : nums) {
ans[num]++;
}
// 排序
priority_queue<pair<int, int>> q;
for (auto item : ans) {
q.emplace(item.second, item.first);
}
//求解
vector<int> res;
while(k) {
res.emplace_back(q.top().second);
q.pop();
--k;
}
return res;
}
};
Day 4 二叉树
-
今天主要是一些理论学习
-
二叉树的分类
- 满二叉树
二叉树只有度为0和度为2的结点,并且度为0的节点都在同一层上(这个描述还是有点问题的)- 除最后一层无任何子结点外,每一层上的所有结点都有两个子结点的二叉树
- 一个二叉树如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。若层数为K,则结点总数为(2^k)-1
- 完全二叉树
- 除了最底层结点可能没有填满以外,其余每层的节点数都达到了最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第h层,则该层包含的节点数量为1~2^(k - 1)
- 二叉搜索树
- 二叉搜索树是一个有序树
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根节点的值;
- 它的左、右子树也分别为二叉排序树
- 平衡二叉搜索树 (AVL, Adelson-Velsky and Landis)
- 它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一颗平衡二叉树。
- C++中map,set, multimap, multiset的底层实现都是平衡二叉搜索树。
- 满二叉树
-
二叉树的存储方式
- 链式存储
- 顺序存储
-
如果父节点的数组下标为i,则左孩子为i * 2 + 1, 右孩子为i * 2+ 2数组可以用来表示二叉树
-
二叉树的遍历方法
- 深度优先搜索遍历:先往深走,遇到叶子节点再往回走。
- 前序遍历(递归法,迭代法)中左右
- 中序遍历(递归法,迭代法)左中右
- 后序遍历(递归法,迭代法)左右中
- 广度优先遍历:一层一层的去遍历层次遍历(迭代法)
- 深度优先搜索遍历:先往深走,遇到叶子节点再往回走。
-
二叉树的定义方法
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int X) : val(x), left(NULL), right(NULL) {}
};
- 二叉树的递归遍历递归三要素
- 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
- 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
- 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
Day 4 二叉树
二叉树的层序遍历
- 队列先进先出,符合一层一层便利的逻辑
- 栈先进后出适合模拟深度优先遍历也就是递归的逻辑
226.翻转二叉树
- 交换左右孩子
- 递归的思想
- 使用前序遍历和后序遍历都可以,但是不可以使用中序遍历,中序遍历会把某些节点的左右孩子翻转两次
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
swap(root->left, root->right);
invertTree(root->left);
invertTree(root->right);
return root;
}
};
101.对称二叉树
- 轴对称,比较内侧和外侧的节点
- 子树的左右中,和子树的右左中顺序进行比较
- 先比较根节点,若值相同,则进行左右节点的比较比较方式是
- 左子树的左孩子与右子树的右孩子
- 左子树的右孩子与右子树的左孩子
class Solution {
public:
bool compare(TreeNode* left, TreeNode* right) {
// 首先比较空节点的情况
if (left == NULL && right != NULL) return false;
else if (left != NULL && right == NULL) return false;
else if (left == NULL && right == NULL) return true;
// 比较数值不一致的情况
else if (left->val != right->val) return false;
// 比较左子树的左节点和右子树的右节点
bool outside = compare(left->left, right->right);
// 比较左子树的右节点和左子树的左节点
bool inside = compare(left->right, right->left);
bool isSame = outside && inside;
return isSame;
}
bool isSymmetric(TreeNode* root) {
if (root == nullptr) return true;
return compare(root->left, root->right);
}
};