代码随想录
数组
二分查找
左右边界,取中间位置比较,小了右边界等于中间位置大了左边届等于中间位置加一
移除元素:双指针
同时从前向后,一个for循环快指针找不符合目标值的赋予慢指针,慢指针++,返回慢指针大小
有序数组的平方:双指针
一左一右因为有序所以左指针和右指针比较,大的放最后,++或--后继续比较,从后往前放
长度最小的子数组:双指针滑窗
同时从前往后,一个for循环快指针边走边累加直到超过目标值,while循环判断大等于目标值计算两者之间序列长度,与之前的取最小值,慢指针++的同时sum减去看是否还满足
螺旋矩阵
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> results;
// 检查矩阵是否为空
if (matrix.size() == 0 || matrix[0].size() == 0) {
return results;
}
int rows = matrix.size();
int columns = matrix[0].size();
int startx = 0, starty = 0;
int loop = min(rows, columns) / 2;
int i, j;
int offset = 1;
while (loop--) {
i = startx;
j = starty;
// 上侧:从左到右
for (j = starty; j < columns - offset; j++) {
results.push_back(matrix[i][j]);
}
// 右侧:从上到下
for (i = startx; i < rows - offset; i++) {
results.push_back(matrix[i][j]);
}
// 下侧:从右到左
for (; j > starty; j--) {
results.push_back(matrix[i][j]);
}
// 左侧:从下到上
for (; i > startx; i--) {
results.push_back(matrix[i][j]);
}
startx++;
starty++;
offset++;
}
// 处理中心元素(如果需要)
if (min(rows, columns) % 2 == 1) {
int mid = min(rows, columns) / 2;
// 对于奇数大小的正方形矩阵
if (rows == columns) {
results.push_back(matrix[mid][mid]);
}
// 对于长方形矩阵
else if (rows > columns) {
for (i = mid; i <= rows - mid - 1; i++) {
results.push_back(matrix[i][mid]);
}
} else {
for (j = mid; j <= columns - mid - 1; j++) {
results.push_back(matrix[mid][j]);
}
}
}
return results;
}
};
螺旋矩阵II
二维数组,循环圈数和中心点坐标等于n / 2,每次循环右边界缩减值加一,左闭右开,while循环次数,顺时针绕圈赋值,n为奇数中心点单独赋值
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0));
int startx = 0, starty = 0; // 循环起始位置
int loop = n / 2; // 循环圈数
int mid = n / 2; // 中心位置
int count = 1; // 赋值
int offset = 1; // 控制每条边遍历长度
int i, j;
while (loop--){
i = startx;
j = starty;
// 填充上行 左闭右开
for(j; j < n - offset; j++){
res[i][j] = count++;
}
// 填充右行 左闭右开
for(i; i < n - offset; i++){
res[i][j] = count++;
}
// 填充下行 左闭右开
for(j; j > starty; j--){
res[i][j] = count++;
}
//填充左行 左闭右开
for(i; i > startx; i--){
res[i][j] = count++;
}
startx++;
starty++;
offset++;
}
if (n % 2) res[mid][mid] = count;
return res;
}
};
区间和:前缀和
另外定义一个前缀和数组,要求的区间和就是两个前缀和数组相减,前面的前缀和要往前一位
开发商购买土地:类前缀和
类似前缀和,分别统计每行/每列结束的时候,当前前缀和与剩余和之间的差值,找到最小值
移动零:双指针
两个指针一起从0往右,遇到非零保存给左指针,最后再从左指针开始给零
盛最多水的容器:双指针
一左一右,高度用最小值,长乘高取最大值,左边高度小左边++,否则右边--
接雨水:双指针
一个记录左边最大值,一个记录右边最大值
class Solution {
public:
int trap(vector<int>& height) {
int result = 0;
int left = 0, right = height.size() - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = max(leftMax, height[left]);
rightMax = max(rightMax, height[right]);
if (leftMax < rightMax) {
result += leftMax - height[left];
left++;
} else {
result += rightMax - height[right];
right--;
}
}
return result;
}
};
链表
构建虚拟头节点
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
移除链表元素
用一个cur遍历,遇到cur->next->val符合条件就跨过next建立节点,最后要返回虚拟头节点的下点
设计链表
struct LinkedNode;MyLinkedList
翻转链表:双指针
while循环cur,cur(head,准备接到pre,变成temp)、pre(Null,准备变成cur)、temp(原cur的next)
旋转链表:双指针
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* left = dummy;
ListNode* right = dummy;
if (head == NULL)
return head;
// 先计算链表长度
int n = 0;
for (ListNode* cur = head; cur != NULL; cur = cur->next)
n++;
k %= n;
while (k-- > 0 && right != NULL)
right = right->next;
if (right == NULL)
return dummy->next;
// 相当于把后k个元素翻转到前面
while (right->next != NULL) {
left = left->next;
right = right->next;
}
right->next = dummy->next;
dummy->next = left->next;
left->next = NULL;
return dummy->next;
}
};
两两交换链表中的节点
要预留tmp、tmp1,while, cur每次移动两next
删除链表的倒数第N个节点:双指针
快指针先走n+1步作为与慢指针之间的距离,然后同时移动直到快指针为空则慢指针就到目标位置的前一位,然后跨到下下位
链表相交:双指针
链表长度作差,用差值先把curA移动,再开始一起移动并比较,遇到相等就是要返回的点
ListNode* curA = headA;
ListNode* curB = headB;
int lenA = 0, lenB = 0;
//求长度
while (curA != NULL) {
lenA++;
curA = curA->next;
}
while (curB != NULL) {
lenB++;
curB = curB->next;
}
curA = headA;
curB = headB;
if (lenB > lenA) {
swap (curA,curB);
swap (lenA,lenB);
}
//求长度差
int gap = lenA - lenB;
//到同一起点
while (gap--) {
curA = curA->next;
}
//遍历 curA==curB 不止是值相等 地址也相等 因为是指针
while (curA != NULL) {
if (curA == curB){
return curA;
}
curA = curA->next;
curB = curB->next;
}
return NULL;
环形链表II:双指针
快慢指针,快指针一次走两步,慢指针一次走一步,肯定会在环内相遇(入环后相对速度追赶),记录相遇位置作为快指针点,然后慢指针返回起始点,同时一步移动相遇点就是入环点。
哈希表
LRU缓存
核心思路:
- 使用哈希表和双向链表组合设计
- 哈希表:O(1)时间查找节点
- 双向链表:维护使用顺序
- 主要操作流程:
- 初始化:设置容量,创建虚拟头尾节点
- get操作:查找键,若存在则移至链表头部并返回值
- put操作:若键不存在则创建节点放入头部,若缓存满则删除尾部;若键存在则更新值并移至头部
-
辅助方法
addToHead:将节点添加到链表头部removeNode:从链表中移除指定节点moveToHead:将节点移到链表头部removeTail:移除并返回链表尾部节点
struct DLinkedNode {
int key, value; // 存储键值对
DLinkedNode* prev; // 指向前一个节点的指针
DLinkedNode* next; // 指向后一个节点的指针
DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {} // 默认构造函数
DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {} // 带参数的构造函数
};
class LRUCache {
private:
unordered_map<int, DLinkedNode*> cache; // 哈希表,用于O(1)时间复杂度查找节点
DLinkedNode* head; // 双向链表的虚拟头节点
DLinkedNode* tail; // 双向链表的虚拟尾节点
int size; // 当前缓存中元素个数
int capacity; // 缓存的最大容量
public:
// 构造函数:初始化缓存容量和双向链表
LRUCache(int _capacity): capacity(_capacity), size(0) {
// 使用伪头部和伪尾部节点,简化边界情况处理
head = new DLinkedNode();
tail = new DLinkedNode();
head->next = tail;
tail->prev = head;
}
// 获取键对应的值,如果不存在返回-1
int get(int key) {
if (!cache.count(key)) {
return -1; // 键不存在,返回-1
}
// 如果键存在,先通过哈希表定位节点
DLinkedNode* node = cache[key];
moveToHead(node); // 将节点移到链表头部(表示最近使用)
return node->value; // 返回值
}
// 插入或更新键值对
void put(int key, int value) {
if (!cache.count(key)) {
// 键不存在,创建新节点
DLinkedNode* node = new DLinkedNode(key, value);
cache[key] = node; // 添加到哈希表
addToHead(node); // 添加到链表头部
++size; // 缓存大小增加
if (size > capacity) {
// 超过容量,删除最久未使用的节点(链表尾部)
DLinkedNode* removed = removeTail();
cache.erase(removed->key); // 从哈希表中删除
delete removed; // 释放内存
--size; // 缓存大小减少
}
}
else {
// 键已存在,更新值并移到链表头部
DLinkedNode* node = cache[key];
node->value = value;
moveToHead(node); // 表示最近使用
}
}
// 将节点添加到链表头部(表示最近使用)
void addToHead(DLinkedNode* node) {
node->prev = head;
node->next = head->next;
head->next->prev = node;
head->next = node;
}
// 从链表中移除指定节点(不释放内存)
void removeNode(DLinkedNode* node) {
node->prev->next = node->next;
node->next->prev = node->prev;
}
// 将节点移到链表头部,表示最近使用
void moveToHead(DLinkedNode* node) {
removeNode(node); // 先从当前位置移除
addToHead(node); // 再添加到链表头部
}
// 移除并返回链表尾部节点(最久未使用的节点)
DLinkedNode* removeTail() {
DLinkedNode* node = tail->prev; // 真正的尾部节点是tail的前一个
removeNode(node); // 从链表中移除
return node; // 返回节点供调用者处理
}
};
有效的字母异位词:哈希法数组
用数组,记录字符串1中字母出现频率,再减去字符串2中的频率,如果全为0就是异位词
int record[26] = {0};
...
record[s[i] - 'a']++;
字母异位词分组:哈希法map
unordered_map<string, vector<string>> mp;
//把每个词排序前后添加到哈希表,后->前(key->value),key相同的会追加
//mp["aet"] = ["eat", "tea"]
for (string& str : strs) {
string key = str;
sort(key.begin(), key.end());
mp[key].push_back(str);
}
vector<vector<string>> ans;
//把value添加到结果中
for (auto it = mp.begin(); it != mp.end(); it++) {
ans.push_back(it->second);
}
return ans;
两个数组的交集:哈希法set
用unordered_set<int> 存放结果,再定义一个存放值数组,然后遍历第一个数组给存放值数组对应位置赋值为1,再遍历另一个数组如果对应位置为1就insert到结果集
快乐数:哈希法set
判断是否循环:判断是否出现过,哈希法,先定义一个求各位数上平方和的函数。主函数定义一个unordered_set<int>,while(1) 求和、判断是否为1、是否出现过、没出现过就存进去,直到出现过或者和为1;
if (set.find(sum) != set.end()) {
return false;
}
两数之和:哈希法map
<key, value>
遍历数组,用目标值减去当前值找有没有在数组中
unordered_map<int,int> //<key, value>
for..
if(map.find(target - nums[i]) != map.end()) {
return {map[target - nums[i]], i};
}
// 将当前元素和下标加入到map中
map[nums[i]] = i;
四数相加II:哈希法map
两数之和升级版,两两看成一对两个for循环就变成了两数之和。
for for
umap[a + b]++;、
...
for for
if (umap.find(0 - (c + d)) != umap.end()){
count += umap[0 - (c + d)]
}
赎金信:哈希表数组
遍历第一个数组记录,遍历第二个数组减,再遍历看有负值就错,否则就对
record[magazine[i]-'a'] ++;
...
record[ransomNote[j]-'a']--;
if(record[ransomNote[j]-'a'] < 0) {
return false;
}
三数之和:双指针
ACM中实现sort的话#include <algorithm>
比哈希法简单,先sort排序,其实是三指针了,遍历i剪枝去重,左指针从i+1开始,右指针最右边,while(right>left) 大了右指针左移小了左指针右移,找到了push_back三个下标并while去重
四数之和:双指针
排序后,三数之和上再一层for循环从k开始剪枝去重,for循环i从k+1开始剪枝去重
最长连续序列
字符串
反转字符串:双指针
输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]
一左一右边往中间走边swap换位
反转字符串II
输入: s = "abcdefg", k = 2
输出: "bacdfeg"
for循环以2k递增,有前k个reverse k个,没前k个reverse全部
for (int i = 0; i < s.size(); i += (2 * k)) {
if (i + k <= s.size()) {
reverse(s.begin() + i, s.begin() + i + k );
} else {
reverse(s.begin() + i, s.end())
替换数字:双指针
多扩充数字个数乘以number的大小,扩充后一左一右双指针,while左指针大等0,不是数字的直接填过去,是数字右边一直倒着填。
翻转字符串里的单词:双指针
输入: "the sky is blue"
输出: "blue is sky the"
先双指针(start和循环的 i )去除多余空格:遇到非空格再处理,处理之前对slow!=0的s[slow++]=' '补一个空格再开始while(遇到空格停止,表示结束当前单词)循环把非空格的都赋值过去,再reverse整段,再reverse单词 start 和空格位置的i - 1,左闭右闭。
reverse(s, start, i - 1);
右旋字符串
反转三次就行了
reverse(s.begin(), s.begin() + len - n);//左
reverse(s.begin() + len - n, s.end());//右
reverse(s.begin(), s.end()); //整段
实现 strStr():KMP
太难了不想看
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从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; // // 因为next数组里记录的起始位置为-1
for (int i = 0; i < haystack.size(); i++) { // 注意i就从0开始
while(j >= 0 && haystack[i] != needle[j + 1]) { // 不匹配
j = next[j]; // j 寻找之前匹配的位置
}
if (haystack[i] == needle[j + 1]) { // 匹配,j和i同时向后移动
j++; // i的增加在for循环里
}
if (j == (needle.size() - 1) ) { // 文本串s里出现了模式串t
return (i - needle.size() + 1);
}
}
return -1;
}
};
重复的子字符串:KMP
class Solution {
public:
void getNext (int* next, const string& s){
next[0] = -1;
int j = -1;
for(int i = 1;i < s.size(); i++){
while(j >= 0 && s[i] != s[j + 1]) {
j = next[j];
}
if(s[i] == s[j + 1]) {
j++;
}
next[i] = j;
}
}
bool repeatedSubstringPattern (string s) {
if (s.size() == 0) {
return false;
}
int next[s.size()];
getNext(next, s);
int len = s.size();
if (next[len - 1] != -1 && len % (len - (next[len - 1] + 1)) == 0) {
return true;
}
return false;
}
};
栈与队列
各自的语法
栈:push(x) -- 元素 x 入栈
- pop() -- 移除栈顶元素
- top() -- 获取栈顶元素
- empty() -- 返回栈是否为空
队列:push(x) -- 将一个元素放入队列的尾部。
pop() -- 从队列首部移除元素。
peek() -- 返回队列首部的元素。
empty() -- 返回队列是否为空。
用栈实现队列
两个栈,一入一出,在pop的时候,输出栈如果为空,就把进栈数据全部导入进来,如果进栈和出栈都为空的话,说明模拟的队列为空。
用队列实现栈
两个队列,一个用来备份,把que1最后面的元素以外的元素都备份到que2,然后弹出最后面的元素,再把其他元素从que2导回que1。
有效的括号:栈
遇到左括号存对应右括号,遇到右括号和栈顶比较,相等弹出,最后for循环结束且栈空说明有效
删除字符串中的所有相邻重复项:栈
存到栈,栈顶遇到相等的弹出,最后留下来的记得reverse再弹出
逆波兰表达式求值:栈
数字入栈,遇到运算符弹出栈顶两个,注意先后顺序,最后留下来的数字就是答案
滑动窗口求最大值:队列
自定义双端队列
class Solution {
private:
class MyQueue { //单调队列(从大到小)
public:
deque<int> que; // 使用deque来实现单调队列
// 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
// 同时pop之前判断队列当前是否为空。
void pop(int value) {
if (!que.empty() && value == que.front()) {
que.pop_front();
}
}
// 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
// 这样就保持了队列里的数值是单调从大到小的了。
void push(int value) {
while (!que.empty() && value > que.back()) {
que.pop_back();
}
que.push_back(value);
}
// 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
int front() {
return que.front();
}
};
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
MyQueue que;
vector<int> result;
for (int i = 0; i < k; i++) { // 先将前k的元素放进队列
que.push(nums[i]);
}
result.push_back(que.front()); // result 记录前k的元素的最大值
for (int i = k; i < nums.size(); i++) {
que.pop(nums[i - k]); // 滑动窗口移除最前面元素
que.push(nums[i]); // 滑动窗口前加入最后面的元素
result.push_back(que.front()); // 记录对应的最大值
}
return result;
}
};
前 K 个高频元素
小顶堆,每次将最小的元素弹出
class Solution {
public:
// 小顶堆
class mycomparison {
public:
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
// 要统计元素出现频率
unordered_map<int, int> map; // map<nums[i],对应出现的次数>
for (int i = 0; i < nums.size(); i++) {
map[nums[i]]++;
}
// 对频率排序
// 定义一个小顶堆,大小为k
priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
// 用固定大小为k的小顶堆,扫面所有频率的数值
for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
pri_que.push(*it);
if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
pri_que.pop();
}
}
// 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
vector<int> result(k);
for (int i = k - 1; i >= 0; i--) {
result[i] = pri_que.top().first;
pri_que.pop();
}
return result;
}
};
二叉树
递归三要素
-
确定递归函数的参数和返回值
-
确定终止条件
-
确定单层递归的逻辑
二叉树的递归遍历
就递归 中的时候是push_back进数组
二叉树的迭代遍历:栈迭代
用栈模拟,先放根节点入栈,然后开始while前序就要先中出栈,然后右左入栈
二叉树的层序遍历:队列迭代
层序遍历模板
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;
}
};
二叉树的最大最小深度
求深度就是层数->层序遍历模板
最大深度:正常遍历过程中while里面depth++
最小深度:当左右孩子都为空的时候,说明是最低点的一层了,退出
翻转二叉树
迭代中(swap left right)左右
对称二叉树
参数返回值:bool;左右node
终止条件:有空节点(3种)、没有空节点(比较数值)
单层递归逻辑:递归左左、右右;左右、右左;判断这两个同时为true
回溯算法
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
回溯模板
三步:
- 递归函数的返回值以及参数
- 回溯函数终止条件
- 单层搜索的过程
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
全排列
记得用一个used数组 回溯前后要true false。
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking (vector<int>& nums, vector<bool>& used) {
// 此时说明找到了一组
if (path.size() == nums.size()) {
result.push_back(path);
return;
}
for (int i = 0; i < nums.size(); i++) {
if (used[i] == true) continue; // path里已经收录的元素,直接跳过
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
vector<vector<int>> permute(vector<int>& nums) {
result.clear();
path.clear();
vector<bool> used(nums.size(), false);
backtracking(nums, used);
return result;
}
};
动态规划
五步:
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
最大子数组和
dp[i]以i为结尾的最大和, 还要定义一个result
class Solution {
public:
int maxSubArray(vector<int>& nums) {
vector<int> dp(nums.size());
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]);
result = max(dp[i], result);
}
return result;
}
};
其他
回文数
使用循环反转数字:
- 每次取 num 的最后一位(num%10)
- 将这一位添加到 sum 中(sum=sum*10+num%10)
- 去掉 num 的最后一位(num/=10)
- 循环直到 num 变为0
class Solution {
public:
bool isPalindrome(int x) {
long i,sum=0,num=x;
for(i=0;num>0;i++)
{
sum=sum*10+num%10;
num/=10;
}
if(sum==x)
return true;
else return false;
}
};
只出现一次的数字
异或:不同为1,相同为0
任何数异或0等于本身:a ^ 0 = a
,异或自身等于0。
ACM:
#include <iostream>
#include <vector>
using namespace std;
int singleNumber(vector<int>& nums) {
int ret = 0;
for (auto e: nums) {
ret ^= e;
}
return ret;
}
int main() {
int n;
cin >> n; // 读取数组大小
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i]; // 读取数组元素
}
int result = singleNumber(nums);
cout << result << endl; // 输出结果
return 0;
}
环形链表:双指针
class Solution {
public:
bool hasCycle(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return false;
}
ListNode* slow = head;
ListNode* fast = head->next;
while (slow != fast) {
if (fast == nullptr || fast->next == nullptr) {
return false;
}
slow = slow->next;
fast = fast->next->next;
}
return true;
}
};
最长公共前缀
看成一个行列形式
#include <iostream>
#include <vector>
#include <string>
using namespace std;
string longestCommonPrefix(vector<string>& strs) {
if (strs.empty()) {
return "";
}
string& s0 = strs[0];
for (int j = 0; j < s0.size(); j++) {
for (string& s : strs) {
if (j == s.size() || s[j] != s0[j]) {
return s0.substr(0, j);
}
}
}
return s0;
}
int main() {
int n;
cin >> n; // 读取字符串数量
vector<string> strs(n);
for (int i = 0; i < n; i++) {
cin >> strs[i]; // 读取每个字符串
}
string result = longestCommonPrefix(strs);
cout << result << endl; // 输出最长公共前缀
return 0;
}
合并两个有序链表:递归
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == nullptr) {
return l2;
} else if (l2 == nullptr) {
return l1;
} else if (l1->val < l2->val) {
l1->next = mergeTwoLists(l1->next, l2);
return l1;
} else {
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
};
删除有序数组中的重复项:双指针
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int slow = 1;
for (int fast = 1; fast < nums.size(); fast++) {
if (nums[fast] != nums[fast - 1]) { // nums[i] 不是重复项
nums[slow++] = nums[fast]; // 保留 nums[i]
}
}
return slow;
}
};
多数元素
- 在排序数组中,相同的元素会相邻排列
- 由于多数元素出现次数大于⌊n/2⌋,所以无论数组长度是奇数还是偶数,数组中间位置一定是多数元素
- 这是因为即使多数元素恰好占总数的一半以上,排序后它也会占据中间位置
class Solution {
public:
int majorityElement(vector<int>& nums) {
sort(nums.begin(), nums.end());
return nums[nums.size() / 2];
}
};
反转字符串中的单词
输入:s = "Let's take LeetCode contest" 输出:"s'teL ekat edoCteeL tsetnoc"
class Solution {
public:
string reverseWords(string s)
{
int begin1=0;
int end1=0;
for(int i=0;i<s.size();i++)
{
if(s[i]==' ')
{
end1=i;
reverse(s.begin()+begin1,s.begin()+end1);//reverse左闭右开
begin1=i+1;//下一次的begin
}
}
//最后一个单词后面没有空格了,所以单独处理
reverse(s.begin()+begin1,s.end());
return s;
}
};
爬楼梯
class Solution {
public:
int climbStairs(int n) {
vector<int> dp(n + 1);
if(n == 1) return 1;
if(n == 2) return 2;
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i < n + 1; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
};
反转链表
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* temp;
ListNode* pre = NULL;
ListNode* cur = head;
while (cur) {
temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
return pre;
}
};
合并两个有序数组
算法思路:
- 从后向前填充 nums1 数组
- 使用三个指针:
- p1:指向 nums1 有效元素的末尾
- p2:指向 nums2 有效元素的末尾
- p:指向 nums1 最终结果的末尾位置
算法步骤:
- 初始化三个指针:
p1 = m - 1, p2 = n - 1, p = m + n - 1
- 当 p2 >= 0 时(即 nums2 还有元素需要合并)循环执行:
- 比较 nums1[p1] 和 nums2[p2]
- 将较大者放入 nums1[p] 的位置
- 移动相应的指针
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int p1 = m - 1, p2 = n - 1, p = m + n - 1;
while (p2 >= 0) { // nums2 还有要合并的元素
// 如果 p1 < 0,那么走 else 分支,把 nums2 合并到 nums1 中
if (p1 >= 0 && nums1[p1] > nums2[p2]) {
nums1[p--] = nums1[p1--]; // 填入 nums1[p1]
} else {
nums1[p--] = nums2[p2--]; // 填入 nums2[p1]
}
}
}
};
存在重复元素
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
unordered_map<int,int> map;
for (int i = 0; i < nums.size(); i++){
map[nums[i]]++;
if (map[nums[i]] >= 2) return true;
}
return false;
}
};
2的幂
执行位运算(如 &, |, ^, ~)时,计算机会直接操作这些数字的二进制表示。
2的幂在二进制中只有一位是1(如4 = 100₂)
对于2的幂,n-1会把那个1变成0,并把右边所有位变成1(如3 = 011₂)
所以对2的幂,n & (n-1)一定等于0
class Solution {
public:
bool isPowerOfTwo(int n) {
return n > 0 && (n & (n - 1)) == 0;
}
};
买股票的最佳时期
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
vector<vector<int>> dp(len, vector<int>(2, 0));
dp[0][0] = -prices[0];
dp[0][1] = 0;
for (int i = 1; i < len; i++) {
//第i天持有股票,可能是前一天就持有(dp[i-1][0]),或者是第i天买入(-prices[i])
dp[i][0] = max(dp[i - 1][0], -prices[i]);
//第i天不持有股票,可能是前一天就不持有(dp[i-1][1])
//或者是第i天卖出(prices[i] + dp[i-1][0])
dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
}
return dp[len - 1][1];//最后一天不持有股票的最大利润
}
};
二叉树的最大深度
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == NULL) return 0;
int depth = 0;
queue<TreeNode*> que;
que.push(root);
while(!que.empty()) {
int size = que.size();
depth++; // 记录深度
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
}
return depth;
}
};
手撕加油啊啊啊!!!冲冲冲!!!!!!