hot100
分披萨m
#include <algorithm> // 引入算法库 #include <iostream> // 引入输入输出流库 #include <vector> // 引入向量库 using namespace std; // 使用标准命名空间 // 定义递归函数来计算最大值 long recursion(int l, int r, vector<int> pizza, vector<vector<long>>& cache){ // 如果左边的值小于右边的值,则右边指针向右移动,否则左边指针向左移动 if(pizza[l] < pizza[r]){ r = r + 1; if(r >= pizza.size()){ r = 0; // 如果右边指针超出范围,则回到起始位置 } } else{ l = l - 1; if(l < 0){ l = pizza.size() - 1; // 如果左边指针超出范围,则回到末尾 } } // 如果当前状态的结果已经被缓存,则直接返回 if(cache[l][r] > 0){ return cache[l][r]; } // 如果左右指针重合,说明只剩下一个元素 if(l == r){ cache[l][r] = pizza[l]; // 直接缓存这个值 } else{ // 更新新的左右指针位置 int newl = l - 1; int newr = r + 1; if(newl < 0){ newl = pizza.size() - 1; // 如果新的左边指针超出范围,则回到末尾 } if(newr >= pizza.size()){ newr = 0; // 如果新的右边指针超出范围,则回到起始位置 } // 选择两个方向中得到的最大值 cache[l][r] = max(recursion(newl, r, pizza, cache) + pizza[l], recursion(l, newr, pizza, cache) + pizza[r]); } return cache[l][r]; // 返回缓存中的结果 } int main(){ int n; // 定义整数 n cin >> n; // 读取输入的整数 n vector<int> pizza(n); // 创建一个大小为 n 的向量来存储每块披萨的值 vector<vector<long>> cache(n, vector<long>(n, 0)); // 创建一个大小为 n x n 的缓存矩阵,初始值为 0 // 读取每块披萨的值 for(int i = 0; i < n; ++i){ cin >> pizza[i]; } long res = 0; // 初始化结果变量 // 遍历每个披萨块,计算每种情况下的最大值 for(int i = 0; i < n; ++i){ int l = i - 1; // 计算左指针 if(l < 0){ l = pizza.size() - 1; // 如果左指针超出范围,则回到末尾 } int r = i + 1; // 计算右指针 if(r >= pizza.size()){ r = 0; // 如果右指针超出范围,则回到起始位置 } // 更新结果值为所有可能情况中的最大值 res = max(res, recursion(l, r, pizza, cache) + pizza[i]); } cout << res; // 输出最终结果 }
字符串变换最小字符串
#include <iostream> #include <string> #include <algorithm> using namespace std; #define MAX_SIZE 1000 string getResult(string s); // 比较函数,用于排序 bool cmp(char a, char b) { return a < b; } int main() { string s; getline(cin, s); // 从标准输入读取一行字符串,保存到 s 中 int len = s.length(); // 获取字符串长度 string minS = s; // 复制字符串 sort(minS.begin(), minS.end(), cmp); // 对字符串进行排序 // 如果排序后的字符串与原字符串相同,直接返回原字符串 if (minS == s) { cout << s; } else { for (int i = 0; i < len; i++) { if (s[i] != minS[i]) { // 找到第一个不同的字符位置 auto swapIdx = s.find_last_of(minS[i]); // 找到最后一个出现的位置 swap(s[i], s[swapIdx]); // 交换字符 break; } } cout << s; } return 0; }
智能驾驶
10,40,70,20
40,70,70,10
0,20,20,40
10,-1,30,40
#include <climits> // 引入 INT_MAX 常量定义 #include <cstdio> // 引入 C 风格的输入输出函数 #include <deque> // 引入双端队列(deque)库 #include <iostream> // 引入 C++ 风格的输入输出流 #include <vector> // 引入向量(vector)库 using namespace std; // 使用标准命名空间 int m, n; // 定义矩阵的行数和列数 vector<vector<int>> matrix; // 定义二维矩阵 // 定义一个结构体,用于存储每个节点的状态 struct node{ int x; // 节点的 x 坐标 int y; // 节点的 y 坐标 int init; // 从起点到当前节点需要的初始值 int remain; // 当前节点剩余的值 bool flag; // 标记是否经过特殊节点(-1) }; // 定义四个方向的移动偏移量 int offset[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; int bfs(){ deque<node> q; // 双端队列用于 BFS node start{0, 0}; // 起始节点 // 初始化起始节点的状态 if(matrix[0][0] == -1){ start.init = 0; // 如果起始点是 -1,初始化值设为 0 start.remain = 100; // 剩余值设为 100 start.flag = true; // 标记经过特殊节点 } else{ start.init = matrix[0][0]; // 起始点的初始化值 start.remain = 0; // 起始点的剩余值 start.flag = false; // 没有经过特殊节点 } q.push_back(start); // 将起始节点放入队列 // 定义存储到达每个节点的最小初始值和剩余值的矩阵 vector<vector<int>> dis_init(m, vector<int>(n, INT_MAX)); vector<vector<int>> dis_remain(m, vector<int>(n, 0)); // BFS 遍历节点 while (!q.empty()) { node temp = q.front(); // 取出队首节点 q.pop_front(); // 从队列中移除队首节点 // 遍历四个方向 for(int i = 0; i < 4; ++i){ int newx = temp.x + offset[i][0]; // 计算新的 x 坐标 int newy = temp.y + offset[i][1]; // 计算新的 y 坐标 // 判断新坐标是否合法以及是否是障碍物 if(newx < 0 || newx >= m || newy < 0 || newy >= n || matrix[newx][newy] == 0){ continue; } int init = temp.init; // 复制当前节点的初始化值 int remain = temp.remain; // 复制当前节点的剩余值 bool flag = temp.flag; // 复制当前节点的标记 // 更新新节点的剩余值和标记 if(matrix[newx][newy] == -1){ remain = 100; // 遇到特殊节点,剩余值设为 100 flag = true; // 标记经过特殊节点 } else{ remain -= matrix[newx][newy]; // 扣除当前节点的值 } // 如果剩余值小于 0,并且没有经过特殊节点,调整初始化值和剩余值 if(remain < 0){ if(flag){ continue; // 如果已经经过特殊节点,则不能继续 } else{ init -= remain; // 增加初始化值 remain = 0; // 剩余值设为 0 } } // 判断是否需要更新最小初始值和剩余值 if(init > 100 || init > dis_init[newx][newy]){ continue; // 如果初始化值不满足条件,则跳过 } // 更新最小初始值和剩余值,并将新节点放入队列 if(init < dis_init[newx][newy] || remain > dis_remain[newx][newy]){ dis_init[newx][newy] = init; dis_remain[newx][newy] = remain; q.push_back(node{newx, newy, init, remain, flag}); } } } // 如果最终节点的初始值仍然为 INT_MAX,说明无法到达终点 if(dis_init[m - 1][n - 1] == INT_MAX){ return -1; // 返回 -1 表示无法到达 } else{ return dis_init[m - 1][n - 1]; // 返回到达终点所需的最小初始值 } } int main(){ scanf("%d,%d", &m, &n); // 读取矩阵的行数和列数 matrix = vector<vector<int>>(m, vector<int>(n, 0)); // 初始化矩阵 // 读取矩阵的每个元素 for(int i = 0; i < m; ++i){ for(int j = 0; j < n; ++j){ scanf("%d", &matrix[i][j]); getchar(); // 读取并丢弃换行符 } } int res = bfs(); // 调用 BFS 函数计算结果 cout << res; // 输出结果 }
1
数对应一个下标。
每个值对应一个目标结果,如果已经有了这个结果,那么直接输出这两个数。
class Solution { public: vector<int> twoSum(vector<int>& nums, int target) { unordered_map<int,int> heap; for(int i = 0; i < nums.size(); i ++) { int r = target - nums[i]; if(heap.count(r)) return {heap[r],i}; //如果有目标值就输出下标,没有的话就把当前值存到哈希表中,键是当前值,值是下标 heap[nums[i]] = i; } return {}; } };
2
从头开始加,一直加到尾巴
只要有数,进位t += l1->val,l1 = la->next
cur = cur->next = new LisNode(t % 10)
t /= 10;
/** * 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* addTwoNumbers(ListNode* l1, ListNode* l2) { auto dummy = new ListNode(-1), cur = dummy; int t = 0; while(l1 || l2 || t){ if(l1) t += l1->val, l1 = l1->next; if(l2) t += l2->val, l2 = l2->next; cur = cur->next = new ListNode(t % 10);//存结果,虚拟头节点的下一个 t /= 10; } return dummy->next; } };
注意返回的是dummy->next
3
i先走遇到第一个重复的,j++
class Solution { public: int lengthOfLongestSubstring(string s) { unordered_map<char, int> hp; int res = 0; for(int i = 0, j = 0; i < s.size(); i++) { hp[s[i]] ++; while(hp[s[i]] > 1) hp[s[j++]] --; res = max(res, i - j + 1); } return res; } };
11
暴力做法:
两重循环,依次遍历
双指针优化:
一个在首一个在尾。如果头上的线先到达最优解,如果右边的线大于等于左边,那么一定就是最优解的位置。反之,如果右边没有到达最优解,那么一定是小于左边边界的。那么只需要--右边界,把每次结果存进去即可。
对于每一个头,右边只需要遍历到不小于左边的位置,这就是每一个头对应的最优秀的尾。也就是尾巴右边一定是比头小的。
对于下一次循环,如果头大于了前一次的头,那么尾巴也是单调的往左移动。因为前一个尾巴右边严格比前一个头小,也一定严格比当前头小。如果头小于前一次的头,则右边不动,左边++;
算法的复杂度为 2n
注意是if else不是while
注意高是最小的边,注意计算的不是长度,是空隙
if(nums[i] > nums[j]) j--;方向不要反
class Solution { public: int maxArea(vector<int>& nums) { int res = 0; for(int i = 0, j = nums.size() - 1; i < j; ){ res = max(res, min(nums[i],nums[j]) * (j - i)); if(nums[i] > nums[j]) j--; else i ++; } return res; } };
注意:字符的长度要+1.这里的长度其实是字符间的距离,不用加
15/
对每一个元素,后面的数进行双指针遍历,
对i和j跳过重复元素
if(i && nums[i] == nums[i - 1]) continue; // 跳过重复元素
if(j > i + 1 && nums[j] == nums[j - 1]) continue; // 跳过重复元素
用了短路原则,i = 0的时候不会执行
双指针循环中,只有结果》=0 k -- 否则 j ++
如果等于0,就加入结果
疑问:k对应的结果,有没有可能在 j的前面
注意 while(j < k - 1 j只循环到k的前一个
注意 while(j < k - 1 && nums[i] + nums[j] + nums[k - 1] >= 0)这里是k - 1
注意 for(int j = i + 1, k = nums.size() - 1; j < k; j++){ 这里是j < k
class Solution { public: vector<vector<int>> threeSum(vector<int>& nums) { vector<vector<int>> res; // 存储结果的二维向量 sort(nums.begin(), nums.end()); // 对输入数组进行排序 for(int i = 0; i < nums.size(); i++){ // 遍历数组中的每个元素 if(i && nums[i] == nums[i - 1]) continue; // 跳过重复元素 for(int j = i + 1, k = nums.size() - 1; j < k; j++){ // 使用双指针遍历剩余数组 if(j > i + 1 && nums[j] == nums[j - 1]) continue; // 跳过重复元素 while(j < k - 1 && nums[i] + nums[j] + nums[k - 1] >= 0) k--; // 调整右指针以确保三数之和小于零 if(nums[i] + nums[j] + nums[k] == 0) res.push_back({nums[i], nums[j], nums[k]}); // 如果找到三数之和为零的组合,加入结果 } } return res; // 返回所有满足条件的三元组 } };
17dfs
dfs:递归基,如果路径长度等于数字长度,追加结果
一般情况,遍历当前数字对应的所有字母,递归搜索下一种情况,dfs参数里记录结果
u记录当前路径长度,path记录递归到底的结果。遍历第一层的时候,路径长度为0。
注意 for(auto c : strs[nums[u] - '0']) 要减去0
注意 dfs(nums,u + 1, path + c);是u + 1
class Solution { public: vector<string> ans; // 用于存储最终结果的字符串组合 string strs[10] = { // 存储每个数字对应的字母组合 "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz", }; // 主函数:返回数字字符串对应的所有字母组合 vector<string> letterCombinations(string digits) { if (digits.empty()) return ans; // 如果输入为空,返回空结果 dfs(digits, 0, ""); // 开始深度优先搜索 return ans; // 返回结果 } // 深度优先搜索函数 void dfs(string& digits, int u, string path) { if (u == digits.size()) { // 如果当前路径长度等于输入数字的长度 ans.push_back(path); // 将当前路径添加到结果中 } else { for (auto c : strs[digits[u] - '0']) // 遍历当前数字对应的所有字母 dfs(digits, u + 1, path + c); // 递归搜索下一个数字 } } };
19
删除倒数第n个点,找到倒数第n+1个点。让倒数第n+1个点的next指向next-》next
n是链表长度,包含虚拟头节点。然后找到要删除的前一个节点,位置是n - (k - 1)
创建虚拟头节点,虚拟头节点的下一个指向头节点,通过从虚拟头节点开始遍历p-》next是不是存在计算链表总长度,要删除倒数第k个节点,找到倒数第k + 1个节点,让他的next指向 next 的next,返回新的头节点
注意是 for(auto p = dummy; p; p->next) n ++; p = p->next
不管加没加虚拟头节点的长度,
class Solution { public: ListNode* removeNthFromEnd(ListNode* head, int k) { // 创建一个虚拟头节点dummy,并将其指针指向head auto dummy = new ListNode(-1); dummy->next = head; // 计算链表的总长度n int n = 0; for (auto p = dummy; p; p = p->next) n++; // 定位到需要删除节点的前一个节点 auto p = dummy; for (int i = 0; i < n - k - 1; i++) p = p->next; // 删除倒数第k个节点 p->next = p->next->next; // 返回新链表的头节点 return dummy->next; } };
20
class Solution { public: bool isValid(string s) { stack<char> stk; for (auto c : s){ if (c == '(' || c == '[' || c == '{') stk.push(c); else{ if(stk.size() && abs(stk.top() - c) <= 2) stk.pop(); else return false; } } return stk.empty(); } };
abs(stk.top() - c) <= 2这个目前是最优解
21
先创建虚拟节点,从尾巴开始接,所以定义tail等于dummy最后返回dummy-》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* mergeTwoLists(ListNode* l1, ListNode* l2) { // 创建一个哨兵节点 dummy,值为 -1 auto dummy = new ListNode(-1); // 初始化尾节点 tail 指向 dummy 节点 auto tail = dummy; // 当 l1 和 l2 都不为空时,继续比较节点 while (l1 && l2) { // 如果 l1 的值小于 l2 的值 if (l1->val < l2->val) { // 将 l1 节点连接到结果链表上 tail = tail->next = l1; // 移动 l1 到下一个节点 l1 = l1->next; } else { // 否则,将 l2 节点连接到结果链表上 tail = tail->next = l2; // 移动 l2 到下一个节点 l2 = l2->next; } } // 如果 l1 不为空,连接剩余的 l1 到结果链表 if (l1) tail->next = l1; // 如果 l2 不为空,连接剩余的 l2 到结果链表 if (l2) tail->next = l2; // 返回哨兵节点的下一个节点,即合并后的链表头节点 return dummy->next; } };
22dfs
任意前缀中“(”数量大于等于“)”数量
左右括号数量相等
seq用于记录答案,先处理递归基,如果左右括号都为n,将记录加入答案数组
else处理平凡情况,然后两个if,只要左括号小于n,就加一个左括号
只要右括号小于n且小于左括号数量,加一个右括号
注意res要定义为public里的成员变量
class Solution { public: // 用于存储生成的括号组合 vector<string> ans; // 生成 n 对括号的所有合法组合 vector<string> generateParenthesis(int n) { // 开始深度优先搜索,初始左右括号数量均为0,初始序列为空 dfs(n, 0, 0, ""); // 返回所有生成的括号组合 return ans; } // 深度优先搜索生成括号组合 void dfs(int n, int lc, int rc, string seq) { // 如果左括号和右括号的数量都达到了 n,则将当前序列加入结果集 if (lc == n && rc == n) ans.push_back(seq); else { // 如果左括号数量小于 n,则可以继续添加左括号 if (lc < n) dfs(n, lc + 1, rc, seq + '('); // 如果右括号数量小于 n 且 左括号数量大于右括号数量,则可以继续添加右括号 if (rc < n && lc > rc) dfs(n, lc, rc + 1, seq + ')'); } } };
23(难
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: struct Cmp { bool operator() (ListNode* a, ListNode* b) { return a->val > b->val; } }; ListNode* mergeKLists(vector<ListNode*>& lists) { priority_queue<ListNode*, vector<ListNode*>, Cmp> heap; auto dummy = new ListNode(-1), tail = dummy; for (auto l : lists) if (l) heap.push(l); while (heap.size()) { auto t = heap.top(); heap.pop(); tail = tail->next = t; if (t->next) heap.push(t->next); } return dummy->next; } }; 作者:yxc 链接:https://www.acwing.com/activity/content/code/content/347836/ 来源:AcWing 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
24
0 -> 2 ; 2 - > 1 ; 1 -> 3
定义虚拟头节点,然后他的next指向head。存一下a, b为交换的两个数。然后根据图,1, 2,3顺着操作
注意 p = a;//因为a , b的位置已经交换了,所以p = a
注意不要忘了new auto dummy = new ListNode(-1);
/** * Definition for singly-linked list. * struct ListNode { * int val; // 节点的值 * ListNode *next; // 指向下一个节点的指针 * ListNode(int x) : val(x), next(NULL) {} // 构造函数初始化节点值和指针 * }; */ class Solution { public: // 函数:交换链表中的每两个相邻节点 ListNode* swapPairs(ListNode* head) { // 创建一个哑节点,并将其 next 指针指向链表头节点 auto dummy = new ListNode(-1); dummy->next = head; // 使用指针 p 遍历链表,交换每两个相邻节点 for (auto p = dummy; p->next && p->next->next;) { // a 指向第一个节点,b 指向第二个节点 auto a = p->next;//1 auto b = a->next;//2 // 将 b 节点插入到 a 节点之前 p->next = b;//p->2 a->next = b->next;//1->3 b->next = a;//2->1 // 移动指针 p 到下一个需要交换的节点 p = a;//因为a , b的位置已经交换了,所以p = a } // 返回新链表的头节点 return dummy->next; } };
25(难
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode* reverseKGroup(ListNode* head, int k) { auto dummy = new ListNode(-1); dummy->next = head; for (auto p = dummy;;) { auto q = p; for (int i = 0; i < k && q; i ++ ) q = q->next; if (!q) break; auto a = p->next, b = a->next; for (int i = 0; i < k - 1; i ++ ) { auto c = b->next; b->next = a; a = b, b = c; } auto c = p->next; p->next = a, c->next = b; p = c; } return dummy->next; } }; 作者:yxc 链接:https://www.acwing.com/activity/content/code/content/347863/ 来源:AcWing 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
31
不会
class Solution { public: // 函数:获取下一个排列 void nextPermutation(vector<int>& nums) { int k = nums.size() - 1; // 初始化 k 为数组最后一个元素的索引 // 从后向前找到第一个违反升序排列的元素 while (k > 0 && nums[k - 1] >= nums[k]) k--; // 如果整个数组是降序排列的,则直接翻转整个数组 if (k <= 0) { reverse(nums.begin(), nums.end()); } else { int t = k; // 初始化 t 为 k // 从 k 开始向后找到第一个比 nums[k - 1] 大的元素 while (t < nums.size() && nums[t] > nums[k - 1]) t++; // 交换 nums[k - 1] 和找到的元素 swap(nums[t - 1], nums[k - 1]); // 反转从 k 开始到数组末尾的部分,使其变为升序 reverse(nums.begin() + k, nums.end()); } } };
33
不会
先找到旋转点,然后查找目标值
class Solution { public: int search(vector<int>& nums, int target) { // 如果数组为空,返回 -1 表示未找到 if (nums.empty()) return -1; // 初始化左右指针 int l = 0, r = nums.size() - 1; // 第一次二分查找,找到旋转点(数组的最大值) while (l < r) { int mid = (l + r + 1) >> 1; // 中间位置,加 1 是为了保证 mid 向上取整 if (nums[mid] >= nums[0]) l = mid; // 如果中间元素大于等于第一个元素,旋转点在右半部分 else r = mid - 1; // 否则在左半部分 } // 根据目标值与第一个元素的比较,确定查找范围 if (target >= nums[0]) l = 0; // 如果目标值大于等于第一个元素,从数组开头查找 else l = r + 1, r = nums.size() - 1; // 否则从旋转点后面开始查找 // 第二次二分查找,查找目标值 while (l < r) { int mid = (l + r) >> 1; // 中间位置,不加 1 是为了保证 mid 向下取整 if (nums[mid] >= target) r = mid; // 如果中间元素大于等于目标值,移动右指针 else l = mid + 1; // 否则移动左指针 } // 检查找到的元素是否等于目标值 if (nums[r] == target) return r; // 未找到目标值,返回 -1 return -1; } };
34
二分
class Solution { public: vector<int> searchRange(vector<int>& nums, int target) { // 如果数组为空,返回 {-1, -1} 表示未找到 if (nums.empty()) return {-1, -1}; int l = 0, r = nums.size() - 1; // 寻找第一个等于 target 的位置 while (l < r) { int mid = (l + r) >> 1; // 中间位置,不加 1 是为了保证 mid 向下取整 if (nums[mid] >= target) r = mid; // 如果中间元素大于等于目标值,移动右指针 else l = mid + 1; // 否则移动左指针 } // 检查找到的元素是否等于目标值 if (nums[r] != target) return {-1, -1}; int L = r; // 记录第一个等于 target 的位置 l = 0, r = nums.size() - 1; // 寻找最后一个等于 target 的位置 while (l < r) { int mid = (l + r + 1) >> 1; // 中间位置,加 1 是为了保证 mid 向上取整 if (nums[mid] <= target) l = mid; // 如果中间元素小于等于目标值,移动左指针 else r = mid - 1; // 否则移动右指针 } return {L, r}; // 返回第一个和最后一个等于 target 的位置 } };
35
二分
注意循环是 l < r
mid要写在循环里,因为每次要更新
r = nums.size();
if (nums[mid] >= target)
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; } };
39dfs
dfs(c, 0, target) 从0开始,表明第1层,第0个元素开始遍历
第u + 1层,第u个数
处理递归基,如果目标减为0,将路径加入结果。如果递归到c.size()层,遍历完所有元素,返回。
平凡情况,当前元素可取的个数,只要加起来小于等于目标值,就可以取,取完每一个元素,递归下去,更新层数和目标值,将当前元素加到path中。
回溯,从path中移除元素。
class Solution { public: vector<vector<int>> ans; // 保存所有组合结果 vector<int> path; // 当前组合路径 // 主函数,调用 dfs 函数开始搜索 vector<vector<int>> combinationSum(vector<int>& c, int target) { dfs(c, 0, target); // 从第 0 个元素开始搜索 return ans; // 返回所有组合结果 } // 深度优先搜索函数 void dfs(vector<int>& c, int u, int target) { // 如果目标为 0,说明找到一个满足条件的组合,添加到结果中 if (target == 0) { ans.push_back(path); return; } // 如果已经遍历完所有元素,返回 if (u == c.size()) return; // 遍历当前元素 u 可以取的次数 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(); // 移除当前元素 } } };
41(难
class Solution { public: int firstMissingPositive(vector<int>& nums) { int n = nums.size(); if (!n) return 1; for (auto& x : nums) if (x != INT_MIN) x -- ; for (int i = 0; i < n; i ++ ) { while (nums[i] >= 0 && nums[i] < n && nums[i] != i && nums[i] != nums[nums[i]]) swap(nums[i], nums[nums[i]]); } for (int i = 0; i < n; i ++ ) if (nums[i] != i) return i + 1; return n + 1; } }; 作者:yxc 链接:https://www.acwing.com/activity/content/code/content/356160/ 来源:AcWing 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
42难
class Solution { public: int trap(vector<int>& height) { stack<int> stk; int res = 0; for (int i = 0; i < height.size(); i ++ ) { int last = 0; while (stk.size() && height[stk.top()] <= height[i]) { res += (height[stk.top()] - last) * (i - stk.top() - 1); last = height[stk.top()]; stk.pop(); } if (stk.size()) res += (i - stk.top() - 1) * (height[i] - last); stk.push(i); } return res; } }; 作者:yxc 链接:https://www.acwing.com/activity/content/code/content/356196/ 来源:AcWing 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
45难
class Solution { public: int jump(vector<int>& nums) { int n = nums.size(); vector<int> f(n); for (int i = 1, j = 0; i < n; i ++ ) { while (j + nums[j] < i) j ++ ; f[i] = f[j] + 1; } return f[n - 1]; } };
46dfs
class Solution { public: vector<vector<int>> ans; // 存储所有排列结果 vector<int> path; // 当前排列路径 vector<bool> st; // 标记元素是否已被使用 // 主函数,调用 dfs 函数开始搜索 vector<vector<int>> permute(vector<int>& nums) { path = vector<int>(nums.size()); // 初始化 path 大小与 nums 相同 st = vector<bool>(nums.size()); // 初始化 st 大小与 nums 相同,默认值为 false dfs(nums, 0); // 从第 0 个元素开始搜索 return ans; // 返回所有排列结果 } // 深度优先搜索函数 void dfs(vector<int>& nums, int u) { // 如果当前排列长度等于 nums 的大小,说明找到一个排列,添加到结果中 if (u == nums.size()) { ans.push_back(path); return; } // 遍历 nums 中的每个元素 for (int i = 0; i < nums.size(); i++) { // 如果当前元素未被使用 if (!st[i]) { path[u] = nums[i]; // 将当前元素添加到当前排列路径中 st[i] = true; // 标记当前元素为已使用 dfs(nums, u + 1); // 递归调用,搜索下一个位置 st[i] = false; // 回溯,恢复当前元素为未使用状态 } } } };
48
获取矩阵大小,然后
先沿着对角线反转,
然后左右反转
class Solution { public: // 主函数,用于将给定的 n x n 矩阵顺时针旋转 90 度 void rotate(vector<vector<int>>& matrix) { int n = matrix.size(); // 行列数 // 进行矩阵的转置操作 for (int i = 0; i < n; i++) for (int j = 0; j < i; j++) swap(matrix[i][j], matrix[j][i]); // 交换 matrix[i][j] 和 matrix[j][i] 的值 // 进行每行元素的反转操作 for (int i = 0; i < n; i++) for (int j = 0, k = n - 1; j < k; j++, k--) swap(matrix[i][j], matrix[i][k]); // 交换 matrix[i][j] 和 matrix[i][k] 的值 } };
49
对每个字符串,复制字符串,然后将他排序,将排序后的字符串作为键,将原始字符放到值数组里
遍历完所有的字符串之后,定义结果数组,遍历hash,将所有的答案存到结果数组
class Solution { public: // 主函数,用于将给定的字符串数组分组为字母异位词的集合 vector<vector<string>> groupAnagrams(vector<string>& strs) { unordered_map<string, vector<string>> hash; // 定义哈希表,用于存储排序后的字符串和对应的字母异位词组 // 遍历每一个字符串 for (auto& str : strs) { string nstr = str; // 复制字符串 sort(nstr.begin(), nstr.end()); // 将字符串排序 hash[nstr].push_back(str); // 将原始字符串添加到排序后的字符串对应的组中 } vector<vector<string>> res; // 定义结果二维数组 // 遍历哈希表,将每个组添加到结果数组中 for (auto& item : hash) res.push_back(item.second); return res; // 返回结果数组 } };
51dfs难
class Solution { public: int n; // 棋盘的大小 (n x n) vector<bool> col, dg, udg; // 列、对角线、反对角线的占用标志 vector<vector<string>> ans; // 存储所有可能的解法 vector<string> path; // 当前的棋盘布局 // 主函数,解决 n 皇后问题 vector<vector<string>> solveNQueens(int _n) { n = _n; col = vector<bool>(n); // 初始化列占用标志 dg = udg = vector<bool>(n * 2); // 初始化对角线和反对角线占用标志 path = vector<string>(n, string(n, '.')); // 初始化棋盘,每个位置为空 ('.') dfs(0); // 开始深度优先搜索,从第 0 行开始 return ans; // 返回所有解法 } // 深度优先搜索函数,u 表示当前处理的行 void dfs(int u) { if (u == n) { ans.push_back(path); // 如果所有行都处理完,保存当前解法 return; } // 尝试在当前行的每一列放置皇后 for (int i = 0; i < n; i++) { // 检查当前列、对角线、反对角线是否被占用 if (!col[i] && !dg[u - i + n] && !udg[u + i]) { col[i] = dg[u - i + n] = udg[u + i] = true; // 标记占用 path[u][i] = 'Q'; // 在 (u, i) 位置放置皇后 dfs(u + 1); // 继续处理下一行 path[u][i] = '.'; // 回溯,移除 (u, i) 位置的皇后 col[i] = dg[u - i + n] = udg[u + i] = false; // 取消占用标志 } } } };
53
子数组是负的就不加了,然后更新结果为结果和last最大的
class Solution { public: // 最大子数组和函数 int maxSubArray(vector<int>& nums) { int res = INT_MIN; // 初始化结果为最小整数 int last = 0; // 记录前一个位置的最大子数组和 // 遍历数组 for (int i = 0; i < nums.size(); i++) { // 计算当前子数组的最大和 last = nums[i] + max(last, 0); // 更新结果 res = max(res, last); } return res; // 返回结果 } };
class Solution { public: // 定义节点结构体,包含四个属性 struct Node { int sum; // 子数组的总和 int s; // 子数组的最大和 int ls; // 从左边界开始的最大子数组和 int rs; // 到右边界结束的最大子数组和 }; // 构建线段树 Node build(vector<int>& nums, int l, int r) { if (l == r) { // 叶子节点 int v = max(nums[l], 0); // 子数组的最大和不能小于0 return {nums[l], v, v, v}; // 初始化节点 } int mid = l + r >> 1; // 计算中点 auto L = build(nums, l, mid); // 递归构建左子树 auto R = build(nums, mid + 1, r); // 递归构建右子树 Node res; // 初始化当前节点 res.sum = L.sum + R.sum; // 当前区间的总和 res.s = max(max(L.s, R.s), L.rs + R.ls); // 当前区间的最大子数组和 res.ls = max(L.ls, L.sum + R.ls); // 左边界开始的最大子数组和 res.rs = max(R.rs, R.sum + L.rs); // 右边界结束的最大子数组和 return res; } // 主函数,计算最大子数组和 int maxSubArray(vector<int>& nums) { int res = INT_MIN; // 初始化结果为最小整数 for (auto x: nums) res = max(res, x); // 找到数组中的最大值 if (res < 0) return res; // 如果最大值小于0,直接返回 auto t = build(nums, 0, nums.size() - 1); // 构建线段树 return t.s; // 返回最大子数组和 } };
54
注意不能随便定义方向,要按照螺旋的方向来
class Solution { public: vector<int> spiralOrder(vector<vector<int>>& matrix) { vector<int> res; // 存储螺旋顺序的结果 int n = matrix.size(); // 矩阵的行数 if (!n) return res; // 如果矩阵为空,直接返回空结果 int m = matrix[0].size(); // 矩阵的列数 // 方向数组,表示右、下、左、上的四个方向 int dx[] = {0, 1, 0, -1}; // x方向的移动 int dy[] = {1, 0, -1, 0}; // y方向的移动 vector<vector<bool>> st(n, vector<bool>(m)); // 记录每个位置是否已经访问过 for (int i = 0, x = 0, y = 0, d = 0; i < n * m; i++) { res.push_back(matrix[x][y]); // 将当前元素添加到结果中 st[x][y] = true; // 标记当前位置为已访问 // 计算下一个位置 int a = x + dx[d], b = y + dy[d]; // 如果下一个位置超出边界或已经访问过,改变方向 if (a < 0 || a >= n || b < 0 || b >= m || st[a][b]) { d = (d + 1) % 4; // 更新方向,右→下→左→上 a = x + dx[d], b = y + dy[d]; // 计算新的位置 } x = a, y = b; // 移动到新位置 } return res; // 返回结果 } };
55
判断能否到达最后一个下标,能跳到的位置一定是连续的一段
每一次都将j更新为max(j, i + nums[i])
只要j < i,说明跳不到位置i了,返回false,
否则就最后返回true
class Solution { public: bool canJump(vector<int>& nums) { // 变量 j 表示到达当前位置 i 能够达到的最远位置 // 变量 i 用于遍历 nums 数组中的每个位置 for (int i = 0, j = 0; i < nums.size(); i++) { // 如果当前位置 i 超出了 j 能够到达的最远位置,则返回 false if (j < i) return false; // 更新最远能够到达的位置 j j = max(j, i + nums[i]); } // 如果循环结束仍未返回 false,说明可以到达最后一个位置,返回 true return true; } };
56
模板
按左端点从小到大排序
只要当前起始位置大于右端点,则没交集保存当前区间
否则,有交集更新右端点
**注意循环要从1开始 **
res.push_back({l, r}); // 将合并后的区间添加到结果中
l = a[i][0]; // 更新新的区间的左端点
r = a[i][1]; // 更新新的区间的右端点
注意,有交集,更新的是最大的右端点
注意添加最后一个合并的区间,
class Solution { public: vector<vector<int>> merge(vector<vector<int>>& a) { vector<vector<int>> res; // 存储合并后的区间 if (a.empty()) return res; // 如果输入为空,则返回空结果 // 按照每个区间的起始位置排序 sort(a.begin(), a.end()); // 初始化第一个区间的左端点和右端点 int l = a[0][0], r = a[0][1]; // 遍历剩下的区间 for (int i = 1; i < a.size(); i++) { // 如果当前区间的起始位置大于之前区间的右端点,说明没有重叠 if (a[i][0] > r) { res.push_back({l, r}); // 将合并后的区间添加到结果中 l = a[i][0]; // 更新新的区间的左端点 r = a[i][1]; // 更新新的区间的右端点 } else { // 如果有重叠,则更新当前区间的右端点 r = max(r, a[i][1]); } } // 添加最后一个合并后的区间 res.push_back({l, r}); return res; } };
73
利用原数组第一行的数表示每一列有没有零,第一列的数表示每一行有没有零
先判断是不是空 注意判断为空的 方法if (matrix.empty() || matrix[0].empty()) return; // 如果矩阵为空,直接返回
先判断第0行 ,0列有没有零最后处理
先判断每一行有没有0,for(int j = 0; j < m; j ++) {//注意是从0开始
class Solution { public: void setZeroes(vector<vector<int>>& matrix) { if (matrix.empty() || matrix[0].empty()) return; // 如果矩阵为空,直接返回 int n = matrix.size(), m = matrix[0].size(); // 行数和列数 int r0 = 1, c0 = 1; // 标记第0行和第0列是否需要置零 for (int i = 0; i < m; i ++ ) if (!matrix[0][i]) r0 = 0; // 检查第0行是否有0 for (int i = 0; i < n; i ++ ) if (!matrix[i][0]) c0 = 0; // 检查第0列是否有0 for (int i = 1; i < m; i ++ ) // 遍历第1列到第m列 for (int j = 0; j < n; j ++ ) // 遍历第0行到第n行 if (!matrix[j][i]) // 如果当前元素为0 matrix[0][i] = 0; // 将第0行第i列元素置为0 for (int i = 1; i < n; i ++ ) // 遍历第1行到第n行 for (int j = 0; j < m; j ++ ) // 遍历第0列到第m列 if (!matrix[i][j]) // 如果当前元素为0 matrix[i][0] = 0; // 将第i行第0列元素置为0 for (int i = 1; i < m; i ++ ) // 遍历第1列到第m列 if (!matrix[0][i]) // 如果第0行第i列元素为0 for (int j = 0; j < n; j ++ ) // 遍历第0行到第n行 matrix[j][i] = 0; // 将第j行第i列元素置为0 for (int i = 1; i < n; i ++ ) // 遍历第1行到第n行 if (!matrix[i][0]) // 如果第i行第0列元素为0 for (int j = 0; j < m; j ++ ) // 遍历第0列到第m列 matrix[i][j] = 0; // 将第i行第j列元素置为0 if (!r0) for (int i = 0; i < m; i ++ ) matrix[0][i] = 0; // 如果第0行需要置零,将第0行所有元素置为0 if (!c0) for (int i = 0; i < n; i ++ ) matrix[i][0] = 0; // 如果第0列需要置零,将第0列所有元素置为0 } };
74
展成1维情况,然后二分
将一维数组下标转成二维下标
class Solution { public: bool searchMatrix(vector<vector<int>>& matrix, int target) { if (matrix.empty() || matrix[0].empty()) return false; // 如果矩阵为空或矩阵的第一行为空,返回 false int n = matrix.size(), m = matrix[0].size(); // 获取矩阵的行数 n 和列数 m int l = 0, r = n * m - 1; // 设置搜索范围的左右边界,左边界 l 为 0,右边界 r 为矩阵元素总数减 1 while (l < r) { // 当左边界小于右边界时,继续搜索 int mid = l + r >> 1; // 计算中间位置 mid if (matrix[mid / m][mid % m] >= target) // 如果中间位置的元素大于等于目标值 r = mid; // 将右边界移动到 mid else // 否则 l = mid + 1; // 将左边界移动到 mid 的右侧 } return matrix[r / m][r % m] == target; // 检查最终位置的元素是否等于目标值,返回结果 } };
75难
class Solution { public: void sortColors(vector<int>& nums) { for (int i = 0, j = 0, k = nums.size() - 1; i <= k;) { if (nums[i] == 0) swap(nums[i ++ ], nums[j ++ ]); else if (nums[i] == 2) swap(nums[i], nums[k -- ]); else i ++ ; } } };
76难没共性
class Solution { public: string minWindow(string s, string t) { unordered_map<char, int> hs, ht; for (auto c: t) ht[c] ++ ; string res; int cnt = 0; for (int i = 0, j = 0; i < s.size(); i ++ ) { hs[s[i]] ++ ; if (hs[s[i]] <= ht[s[i]]) cnt ++ ; while (hs[s[j]] > ht[s[j]]) hs[s[j ++ ]] -- ; if (cnt == t.size()) { if (res.empty() || i - j + 1 < res.size()) res = s.substr(j, i - j + 1); } } return res; } }; 作者:yxc 链接:https://www.acwing.com/activity/content/code/content/370411/ 来源:AcWing 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
78
每一种二进制组合对应一种状态
有2^n个组合,从0开始遍历到每个数,就是一种组合,对于每个组合,记录他的二进制形式。
因为有n位,只需要遍历n次,每次又移动 j 次,然后将最低为存入路径。
每遍历完一个路径将其存入结果矩阵
if (i >> j & 1)
path.push_back(nums[j]); // 只将位置是1的存汝路径,右移动0位是1就是第0个元素加入路径
class Solution { public: vector<vector<int>> subsets(vector<int>& nums) { vector<vector<int>> res; // 存储所有子集的结果 int n = nums.size(); // 数组 nums 的大小 // 遍历所有可能的子集组合,1 << n 是 2^n for (int i = 0; i < 1 << n; i++) {//有这么些组合 vector<int> path; // 存储当前子集 // 遍历数组 nums 中的每个元素 for (int j = 0; j < n; j++) { // 检查第 j 位是否在子集中(i >> j & 1 判断第 j 位是否为 1) if (i >> j & 1) path.push_back(nums[j]); // 只将位置是1的存汝路径,右移动0位是1就是第0个元素加入路径 } // 将当前子集添加到结果中 res.push_back(path); } return res; // 返回所有子集 } };
79dfs
class Solution { public: // 主函数,检查单词是否存在于字符网格中 bool exist(vector<vector<char>>& board, string word) { // 遍历网格的每一个位置 for (int i = 0; i < board.size(); i++) { for (int j = 0; j < board[i].size(); j++) { // 从每一个位置开始深度优先搜索(DFS) if (dfs(board, word, 0, i, j)) return true; } } return false; // 如果所有位置都没有找到单词,返回 false } // 定义四个方向的移动(上、右、下、左) int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; // 深度优先搜索(DFS)函数 bool dfs(vector<vector<char>>& board, string& word, int u, int x, int y) { // 如果当前位置的字符不匹配单词的当前字符,返回 false if (board[x][y] != word[u]) return false; // 如果已经匹配到单词的最后一个字符,返回 true if (u == word.size() - 1) return true; char t = board[x][y]; // 保存当前字符 board[x][y] = '.'; // 将当前位置标记为访问过,避免重复访问 // 遍历四个方向(上、右、下、左) for (int i = 0; i < 4; i++) { int a = x + dx[i], b = y + dy[i]; // 计算下一个位置 // 检查下一个位置是否在边界内且未被访问过 if (a < 0 || a >= board.size() || b < 0 || b >= board[0].size() || board[a][b] == '.') continue; // 递归搜索下一个字符 if (dfs(board, word, u + 1, a, b)) return true; } board[x][y] = t; // 恢复当前位置的字符,回溯 return false; // 没有找到匹配的路径,返回 false } };
84难
class Solution { public: int largestRectangleArea(vector<int>& h) { int n = h.size(); vector<int> left(n), right(n); stack<int> stk; for (int i = 0; i < n; i ++ ) { while (stk.size() && h[stk.top()] >= h[i]) stk.pop(); if (stk.empty()) left[i] = -1; else left[i] = stk.top(); stk.push(i); } stk = stack<int>(); for (int i = n - 1; i >= 0; i -- ) { while (stk.size() && h[stk.top()] >= h[i]) stk.pop(); if (stk.empty()) right[i] = n; else right[i] = stk.top(); stk.push(i); } int res = 0; for (int i = 0; i < n; i ++ ) { res = max(res, h[i] * (right[i] - left[i] - 1)); } return res; } }; 作者:yxc 链接:https://www.acwing.com/activity/content/code/content/375419/ 来源:AcWing 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
94
迭代就是用队列或者栈,蚕食进行
一般用while,只要还是节点或者栈不会空,如果是节点,就一路走到黑,把所有的左子树节点push到栈,不是节点了,就访问栈顶元素,说明到达了中序遍历最左端的位置,也即没有左孩子,把值存入,然后遍历右孩子,就是把右孩子赋值为根。
stk.push(root); // 将当前节点压入栈中
root = root->left;
注意用的是栈,所以遇到就把root push进去,最后遍历到的就是root,不是队列
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: // 返回二叉树的中序遍历结果 vector<int> inorderTraversal(TreeNode* root) { vector<int> res; // 存储中序遍历的结果 stack<TreeNode*> stk; // 用于模拟递归的栈 // 遍历二叉树,直到所有节点都被访问 while (root || !stk.empty()) { // 访问当前节点的所有左子节点 while (root) { stk.push(root); // 将当前节点压入栈中 root = root->left; // 继续遍历左子树 } // 当前节点为最左侧节点 root = stk.top(); // 取出栈顶元素(即最左侧节点) stk.pop(); // 从栈中移除该节点 res.push_back(root->val); // 访问当前节点(将其值添加到结果中) // 继续遍历右子树 root = root->right; // 转到右子树 } return res; // 返回中序遍历的结果 } };
98dfs
如果节点为空,返回true。
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: // 判断二叉树是否为有效的二叉搜索树 bool isValidBST(TreeNode* root) { if (!root) return true; // 空树被认为是有效的二叉搜索树 return dfs(root)[0]; // 执行递归检查,并返回结果 } // 返回一个 vector,其中包含三个值: // 0: 当前子树是否为有效的二叉搜索树 // 1: 当前子树中节点的最小值 // 2: 当前子树中节点的最大值 vector<int> dfs(TreeNode* root) { // 初始化当前节点的最小值和最大值为节点值自身 vector<int> res({1, root->val, root->val}); // 如果当前节点有左子树 if (root->left) { auto t = dfs(root->left); // 递归检查左子树 if (!t[0] || t[2] >= root->val) res[0] = 0; // 左子树无效或左子树最大值不小于当前节点值 res[1] = min(res[1], t[1]); // 更新当前子树的最小值 res[2] = max(res[2], t[2]); // 更新当前子树的最大值 } // 如果当前节点有右子树 if (root->right) { auto t = dfs(root->right); // 递归检查右子树 if (!t[0] || t[1] <= root->val) res[0] = 0; // 右子树无效或右子树最小值不大于当前节点值 res[1] = min(res[1], t[1]); // 更新当前子树的最小值 res[2] = max(res[2], t[2]); // 更新当前子树的最大值 } return res; // 返回当前子树的结果 } };
101dfs
dfs函数,递归基没有左右指针,返回true
普通情况,只要一个没有,或者左边的值不等于右边的值,返回false
递归左右。注意,递归的是左的左,右的右
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: bool isSymmetric(TreeNode* root) { if (!root) return true; // 如果树为空,则它是对称的 return dfs(root->left, root->right); // 调用辅助函数检查左右子树是否对称 } bool dfs(TreeNode* p, TreeNode* q) { if (!p && !q) return true; // 如果两个节点都为空,则对称 if (!p || !q || p->val != q->val) return false; // 如果一个为空或值不相等,则不对称 // 递归检查左子树的左子树与右子树的右子树是否对称,左子树的右子树与右子树的左子树是否对称 return dfs(p->left, q->right) && dfs(p->right, q->left); } };
102
如果根不为空,入队。
只要队不为空,获取当前层节点数量,定义存当前层结果数组,遍历当前层所有节点,每次遍历获取队头节点,访问其数值,存到层结果数组,将左右节点入队。直到当前层所有节点遍历完毕,将结果数组存到结果矩阵
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: vector<vector<int>> levelOrder(TreeNode* root) { vector<vector<int>> res; // 用于存储每一层的节点值 queue<TreeNode*> q; // 用于层次遍历的队列 if (root) q.push(root); // 如果根节点不为空,将其入队 while (!q.empty()) { // 当队列不为空时进行循环 vector<int> level; // 用于存储当前层的节点值 int len = q.size(); // 当前层的节点数量 while (len--) { // 遍历当前层的所有节点 TreeNode* t = q.front(); // 取出队首节点 q.pop(); // 将队首节点出队 level.push_back(t->val); // 将队首节点的值加入当前层的结果中 if (t->left) q.push(t->left); // 如果左子节点存在,将其入队 if (t->right) q.push(t->right); // 如果右子节点存在,将其入队 } res.push_back(level); // 将当前层的结果加入最终结果中 } return res; // 返回最终的层次遍历结果 } };
104
递归
递归基,如果不是根返回0
只要不是一个根节点,那么递归左右的最大值加1
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: int maxDepth(TreeNode* root) { if (!root) return 0; // 如果根节点为空,树的深度为0 return max(maxDepth(root->left), maxDepth(root->right)) + 1; // 递归计算左右子树的最大深度,取其最大值,加上根节点本身的深度1 } };
105
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: unordered_map<int, int> pos; // 用于记录中序遍历中每个值的索引位置 TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) { // 填充pos映射,中序遍历中的值映射到它们的索引 for (int i = 0; i < inorder.size(); i++) { pos[inorder[i]] = i; } // 构建二叉树 return build(preorder, inorder, 0, preorder.size() - 1, 0, inorder.size() - 1); } TreeNode* build(vector<int>& preorder, vector<int>& inorder, int pl, int pr, int il, int ir) { if (pl > pr) return NULL; // 如果前序区间为空,返回NULL auto root = new TreeNode(preorder[pl]); // 前序遍历的第一个元素是当前子树的根节点 int k = pos[root->val]; // 找到根节点在中序遍历中的位置 // 递归构建左子树 root->left = build(preorder, inorder, pl + 1, pl + 1 + k - 1 - il, il, k - 1); // 递归构建右子树 root->right = build(preorder, inorder, pl + 1 + k - 1 - il + 1, pr, k + 1, ir); return root; // 返回构建好的子树 } };
108
没看懂。背一下思路
递归基,l > r
计算中间位置,创建当前节点,值为中间位置的元素
左子树等于 l, mid - 1
右子树 mid + 1,r
return root;
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: // 将排序数组转换为平衡二叉搜索树 TreeNode* sortedArrayToBST(vector<int>& nums) { return build(nums, 0, nums.size() - 1); } // 递归构建平衡二叉搜索树 TreeNode* build(vector<int>& nums, int l, int r) { if (l > r) return NULL; // 递归终止条件:区间无效,返回空指针 int mid = l + r >> 1; // 计算当前区间的中间位置 auto root = new TreeNode(nums[mid]); // 创建当前节点,值为中间位置的元素 root->left = build(nums, l, mid - 1); // 递归构建左子树 root->right = build(nums, mid + 1, r); // 递归构建右子树 return root; // 返回当前节点作为子树的根节点 } };
114
只要当前节点存在,只要有左子树,只要有左子树的右子树,就指向这里
p->right = root->right;
root->right = root->left;
root->left = null;
最后递归处理
root = root->right;
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: // 将二叉树展平为链表 void flatten(TreeNode* root) { while (root) { // 如果当前节点有左子树 auto p = root->left; if (p) { // 找到左子树中最右的节点 while (p->right) p = p->right; // 将最右节点的右指针指向当前节点的右子树 p->right = root->right; // 将当前节点的右子树更新为左子树 root->right = root->left; // 清空当前节点的左子树 root->left = NULL; } // 移动到右子树,继续处理下一个节点 root = root->right; } } };
121
每遍历到一个价格计算他与之前最小价格的差,然后更新结果
class Solution { public: // 计算在给定的股票价格数组中,能够获得的最大利润 int maxProfit(vector<int>& prices) { int res = 0; // 初始化最大利润为0 for (int i = 0, minp = INT_MAX; i < prices.size(); i ++ ) { // 更新最大利润,当前价格减去之前遇到的最低价格 res = max(res, prices[i] - minp); // 更新最低价格 minp = min(minp, prices[i]); } return res; // 返回计算得到的最大利润 } };
124难
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: int ans; // 用于存储最大路径和 // 主函数,返回二叉树中的最大路径和 int maxPathSum(TreeNode* root) { ans = INT_MIN; // 初始化最大路径和为最小整数值 dfs(root); // 调用深度优先搜索函数计算最大路径和 return ans; // 返回最大路径和 } // 深度优先搜索函数,返回从当前节点开始的最大路径和 int dfs(TreeNode* u) { if (!u) return 0; // 如果当前节点为空,返回0 // 递归计算左子树和右子树的最大路径和,负数路径和置为0 int left = max(0, dfs(u->left)), right = max(0, dfs(u->right)); // 更新全局最大路径和 ans = max(ans, u->val + left + right); // 返回当前节点的值加上左子树和右子树中较大的路径和 return u->val + max(left, right); } };
128难
class Solution { public: int longestConsecutive(vector<int>& nums) { unordered_set<int> S; // 用于存储数组中的所有数字,方便查找 for (auto x: nums) S.insert(x); // 将数组中的每个数字插入集合中 int res = 0; // 存储最长连续序列的长度 for (auto x: nums) { // 遍历数组中的每个数字 if (S.count(x) && !S.count(x - 1)) { // 如果当前数字存在于集合中且前一个数字不存在 int y = x; // 初始化当前连续序列的起点 S.erase(x); // 从集合中移除当前数字 while (S.count(y + 1)) { // 检查后续连续的数字 y++; // 更新连续序列的终点 S.erase(y); // 从集合中移除后续数字 } res = max(res, y - x + 1); // 更新最长连续序列的长度 } } return res; // 返回最长连续序列的长度 } };
131dfs
class Solution { public: vector<vector<bool>> f; // 用于记录子字符串是否是回文的二维数组 vector<vector<string>> ans; // 存储所有可能的分割方案 vector<string> path; // 当前分割方案 // 主方法,返回字符串 s 的所有回文分割 vector<vector<string>> partition(string s) { int n = s.size();//元素个数 // 初始化 f 数组,记录所有子字符串是否是回文 f = vector<vector<bool>>(n, vector<bool>(n)); // 填充 f 数组 for (int j = 0; j < n; j++) { for (int i = 0; i <= j; i++) { // 单个字符是回文 if (i == j) f[i][j] = true; // 两个字符相等且是回文 else if (s[i] == s[j]) { if (i + 1 > j - 1 || f[i + 1][j - 1]) f[i][j] = true; } } } // 深度优先搜索生成所有回文分割方案 dfs(s, 0); return ans; } // 深度优先搜索方法 void dfs(string& s, int u) { // 如果当前下标到达字符串末尾,保存当前方案 if (u == s.size()) ans.push_back(path); else { // 遍历所有可能的分割点 for (int i = u; i < s.size(); i++) { // 如果从 u 到 i 的子字符串是回文 if (f[u][i]) { // 添加子字符串到当前方案 path.push_back(s.substr(u, i - u + 1)); // 递归调用继续处理下一个子字符串 dfs(s, i + 1); // 回溯,移除当前子字符串 path.pop_back(); } } } } };
136
异或运算有一个重要的性质:a ^ a = 0
和 a ^ 0 = a
三行代码,定义结果,遍历数组与结果异或,返回结果
class Solution { public: // 主方法,返回数组中只出现一次的数字 int singleNumber(vector<int>& nums) { int res = 0; // 初始化结果为0 // 遍历数组中的每个数字 for (auto x: nums) { res ^= x; // 使用异或运算更新结果 } // 返回只出现一次的数字 return res; } };
138链表长
/* // Definition for a Node. class Node { public: int val; Node* next; Node* random; Node(int _val) { val = _val; next = NULL; random = NULL; } }; */ class Solution { public: Node* copyRandomList(Node* head) { // 第一步:在每个节点后面插入一个新节点,形成链表 A -> A' -> B -> B' -> ... for (auto p = head; p; p = p->next->next) { auto q = new Node(p->val); // 复制当前节点 p q->next = p->next; // 将新节点的 next 指向 p 的下一个节点 p->next = q; // 将 p 的 next 指向新节点 q } // 第二步:复制 random 指针 for (auto p = head; p; p = p->next->next) { if (p->random) { p->next->random = p->random->next; // 将新节点的 random 指向 p 的 random 指向的节点的下一个节点 } } // 第三步:拆分两个链表,恢复原链表并提取新链表 auto dummy = new Node(-1); // 哑节点用于构建新链表 auto cur = dummy; // 用于构建新链表的指针 for (auto p = head; p; p = p->next) { auto q = p->next; // 新节点 cur->next = q; // 将新节点连接到新链表 cur = cur->next; // 移动新链表的指针 p->next = q->next; // 恢复原链表的连接 } // 返回新链表的头节点 return dummy->next; } };
141
如果链表为空或只有一个节点,返回false。将快慢指针初始化为头指针。如果下一步能走,慢指针走一步,快指针走两步,如果快指针走到末尾,返回false,如果 相遇 返回true
遍历完没相遇,返回true
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: bool hasCycle(ListNode *head) { // 如果链表为空或者只有一个节点,则不可能有环 if (!head || !head->next) return false; // 慢指针 s 和快指针 f auto s = head; auto f = head; // 使用快慢指针检查环 while (f->next) {//下一步能走 s = s->next; // 慢指针每次移动一步 f = f->next->next; // 快指针每次移动两步 if (!f) return false; // 快指针到达链表末尾,则没有环 if (s == f) return true; // 快慢指针相遇,则存在环 } return false; // 如果循环结束而没有相遇,则链表无环 } };
142
从头开始,只要接下来能走,慢指针走一步,快指针走两步,直到他们相遇。如果不能走了,就返回没环。
相遇后,慢指针指向头,快慢指针每次一起走1步,直到再次相遇,相遇的位置就是环入口的位置
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode *detectCycle(ListNode *head) { // 如果链表为空或者链表只有一个节点,则没有环 if (!head || !head->next) return NULL; // 初始化快慢指针 auto s = head; // 慢指针 auto f = head; // 快指针 // 检测是否存在环 while (f->next) {//如果没达到链表末尾 s = s->next; // 慢指针每次移动一步 f = f->next->next; // 快指针每次移动两步 if (!f) return NULL; // 如果没到末尾。再走一步 if (s == f) { // 快慢指针相遇,存在环 // 找到环的起点 s = head; // 重置慢指针为链表头 while (s != f) { // 当慢指针与快指针相遇时,即为环的起点 s = s->next; f = f->next; } return s; // 返回环的起点 } } // 没有环 return NULL; } };
146lru缓存算法
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode* sortList(ListNode* head) { // 计算链表的长度 int n = 0; for (auto p = head; p; p = p->next) n++; // 遍历链表,计算节点总数 // 使用自底向上的归并排序进行排序 for (int i = 1; i < n; i *= 2) { // i 是当前要归并的子链表的大小,从 1 开始,每次翻倍 auto dummy = new ListNode(-1), cur = dummy; // 创建一个虚拟头节点和一个指针用于合并结果 for (int j = 1; j <= n; j += i * 2) { // 每次处理一对子链表 auto p = head, q = p; for (int k = 0; k < i && q; k++) q = q->next; // 移动 q 指针到第二个子链表的起始位置 auto o = q; // 记录第二个子链表的起始位置 for (int k = 0; k < i && o; k++) o = o->next; // 移动 o 指针到第三个子链表的起始位置 int l = 0, r = 0; // l 和 r 分别记录已经处理的第一个和第二个子链表的长度 while (l < i && r < i && p && q) // 合并两个子链表 if (p->val <= q->val) { // 如果第一个子链表的当前节点值小于等于第二个子链表的当前节点值 cur = cur->next = p; // 将第一个子链表的当前节点加入结果链表 p = p->next; // 移动第一个子链表的指针 l++; // 更新第一个子链表已处理的长度 } else { // 否则 cur = cur->next = q; // 将第二个子链表的当前节点加入结果链表 q = q->next; // 移动第二个子链表的指针 r++; // 更新第二个子链表已处理的长度 } while (l < i && p) { // 处理第一个子链表中剩余的节点 cur = cur->next = p; // 将剩余节点加入结果链表 p = p->next; // 移动第一个子链表的指针 l++; // 更新第一个子链表已处理的长度 } while (r < i && q) { // 处理第二个子链表中剩余的节点 cur = cur->next = q; // 将剩余节点加入结果链表 q = q->next; // 移动第二个子链表的指针 r++; // 更新第二个子链表已处理的长度 } head = o; // 更新链表头,为下一次归并做准备 } cur->next = NULL; // 设置结果链表的尾部为 NULL head = dummy->next; // 更新链表头为新排序的链表 } return head; // 返回排序后的链表头 } };
148难长
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode* sortList(ListNode* head) { // 计算链表的长度 int n = 0; for (auto p = head; p; p = p->next) n++; // 遍历链表,计算节点总数 // 使用自底向上的归并排序进行排序 for (int i = 1; i < n; i *= 2) { // i 是当前要归并的子链表的大小,从 1 开始,每次翻倍 auto dummy = new ListNode(-1), cur = dummy; // 创建一个虚拟头节点和一个指针用于合并结果 for (int j = 1; j <= n; j += i * 2) { // 每次处理一对子链表 auto p = head, q = p; for (int k = 0; k < i && q; k++) q = q->next; // 移动 q 指针到第二个子链表的起始位置 auto o = q; // 记录第二个子链表的起始位置 for (int k = 0; k < i && o; k++) o = o->next; // 移动 o 指针到第三个子链表的起始位置 int l = 0, r = 0; // l 和 r 分别记录已经处理的第一个和第二个子链表的长度 while (l < i && r < i && p && q) // 合并两个子链表 if (p->val <= q->val) { // 如果第一个子链表的当前节点值小于等于第二个子链表的当前节点值 cur = cur->next = p; // 将第一个子链表的当前节点加入结果链表 p = p->next; // 移动第一个子链表的指针 l++; // 更新第一个子链表已处理的长度 } else { // 否则 cur = cur->next = q; // 将第二个子链表的当前节点加入结果链表 q = q->next; // 移动第二个子链表的指针 r++; // 更新第二个子链表已处理的长度 } while (l < i && p) { // 处理第一个子链表中剩余的节点 cur = cur->next = p; // 将剩余节点加入结果链表 p = p->next; // 移动第一个子链表的指针 l++; // 更新第一个子链表已处理的长度 } while (r < i && q) { // 处理第二个子链表中剩余的节点 cur = cur->next = q; // 将剩余节点加入结果链表 q = q->next; // 移动第二个子链表的指针 r++; // 更新第二个子链表已处理的长度 } head = o; // 更新链表头,为下一次归并做准备 } cur->next = NULL; // 设置结果链表的尾部为 NULL head = dummy->next; // 更新链表头为新排序的链表 } return head; // 返回排序后的链表头 } };
153
先 判断数组旋转没旋转,如果l 《= r 没旋转,返回开头的值
二分,右边界,
class Solution { public: // 主函数,找到旋转排序数组中的最小值 int findMin(vector<int>& nums) { int l = 0, r = nums.size() - 1; // 初始化左右指针 // 如果数组未被旋转,直接返回第一个元素 if (nums[r] >= nums[l]) return nums[0]; // 二分查找旋转点 while (l < r) { int mid = l + r >> 1; // 计算中间位置 if (nums[mid] < nums[0]) // 如果中间元素小于数组的第一个元素 r = mid; // 最小值在左半边 else l = mid + 1; // 最小值在右半边 } // 循环结束时 l == r,即最小值所在的位置 return nums[r]; } };
155
只是在标准栈加了获取其中最小元素的这一功能,其余功能同栈。
注意p中不只一个元素,里面的元素都是当前栈里在不同情况下最小的
注意
void push(int val) {
if(p.empty() || p.top() >= val) {
p.push(val);
}
stk.push(val);
}
这里是>=
只要是等于也要加入,不然以后pop时会出错
class MinStack { public: // 主栈:存储所有元素 stack<int> stk; // 辅助栈:存储当前栈中最小的元素 stack<int> f; /** 初始化数据结构 */ MinStack() { } /** 将元素 x 推入栈中 */ void push(int x) { stk.push(x); // 将元素 x 推入主栈 // 如果辅助栈为空,或者 x 小于等于辅助栈的栈顶元素 // 说明 x 是当前栈的最小元素之一,所以也将其推入辅助栈 if (f.empty() || f.top() >= x) { f.push(x); } } /** 移除栈顶元素 */ void pop() { // 如果主栈的栈顶元素等于辅助栈的栈顶元素 // 说明主栈中的最小元素也在辅助栈中 // 因此需要将辅助栈的栈顶元素也移除 if (stk.top() == f.top()) { f.pop(); } stk.pop(); // 移除主栈的栈顶元素 } /** 获取栈顶元素 */ int top() { return stk.top(); } /** 获取栈中的最小元素 */ int getMin() { return f.top(); // 辅助栈的栈顶元素即为当前栈的最小元素 } }; /** * Your MinStack object will be instantiated and called as such: * MinStack* obj = new MinStack(); * obj->push(x); * obj->pop(); * int param_3 = obj->top(); * int param_4 = obj->getMin(); */
160
从两个头开始走,只要不相等,且到了尾巴,就从另一个头开始走,如果最后相交,这就是交点
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: // 查找两个链表的交点 ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { auto p = headA; // 初始化指针 p 指向链表 A 的头部 auto q = headB; // 初始化指针 q 指向链表 B 的头部 // 当两个指针不相等时,继续遍历 while (p != q) { // 如果 p 不为空,则移动到下一个节点;否则,指向链表 B 的头部 p = p ? p->next : headB; // 如果 q 不为空,则移动到下一个节点;否则,指向链表 A 的头部 q = q ? q->next : headA; } // 返回交点节点(如果存在),否则返回 NULL return p; } };
169
一个记录当前的多数元素,一个记录他的个数
如果个数为0,多数元素就是当前的元素,个数为1
否则 等于 r 则 c ++
其余则 c --;
最后return r
class Solution { public: // 查找多数元素 int majorityElement(vector<int>& nums) { int r, c = 0; // r: 当前候选的多数元素, c: 当前候选元素的计数 // 遍历数组中的每个元素 for (auto x: nums) { // 当计数器 c 为 0 时,将当前元素 x 作为新的候选元素 r,并将计数器 c 设为 1 if (!c) { r = x; // 设置候选元素 c = 1; // 初始化计数器 } // 如果当前元素 x 与候选元素 r 相同,则计数器 c 增加 else if (r == x) { c++; // 增加计数 } // 如果当前元素 x 与候选元素 r 不同,则计数器 c 减少 else { c--; // 减少计数 } } // 返回最终的候选元素 r return r; } };
183
189
向右反转k个位置,先处理k %= n;处理大于n的情况
然后反转整个数组
反转前k个
反转后n - k 个
class Solution { public: void rotate(vector<int>& nums, int k) { int n = nums.size(); // 数组的长度 k %= n; // 处理 k 大于 n 的情况 // 反转整个数组 reverse(nums.begin(), nums.end()); // 反转前 k 个元素 reverse(nums.begin(), nums.begin() + k); // 反转后 n - k 个元素 reverse(nums.begin() + k, nums.end()); } };
199
层序遍历就是宽搜
初始化队,结果
如果根节点为空直接返回结果
否则将根节点放入队列开始迭代
只要q不为空,计算q的长度,遍历q的长度,取出队首,弹出队首,有左右节点就加入队列
如果遍历到了当前层最后一个元素,将他的值加入结果数组
注意:所有的迭代操作,都在小于len的范围内进行
注意queue没有top
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: vector<int> rightSideView(TreeNode* root) { queue<TreeNode*> q; // 定义一个队列用于层序遍历 vector<int> res; // 存放最终结果的向量 if (!root) return res; // 如果根节点为空,直接返回空向量 q.push(root); // 将根节点放入队列 while (q.size()) { // 队列不为空时循环 int len = q.size(); // 当前层的节点个数 for (int i = 0; i < len; i++) { auto t = q.front(); // 取出队首节点 q.pop(); // 弹出队首节点 if (t->left) q.push(t->left); // 如果有左子节点,将左子节点加入队列 if (t->right) q.push(t->right); // 如果有右子节点,将右子节点加入队列 if (i == len - 1) res.push_back(t->val); // 如果是当前层的最后一个节点,将其值加入结果向量 } } return res; // 返回右视图的结果向量 } };
200dfs
跟腐烂橘子类似,找到一个1,就把附近的全消掉
class Solution { public: vector<vector<char>> g; // 声明二维字符向量用于存储地图信息 int dx[4] = {-1, 0, 1, 0}; // 方向数组,表示四个方向的行偏移量 int dy[4] = {0, 1, 0, -1}; // 方向数组,表示四个方向的列偏移量 int numIslands(vector<vector<char>>& grid) { g = grid; // 将输入的地图复制给成员变量g int cnt = 0; // 记录岛屿数量的变量 // 遍历整个地图 for (int i = 0; i < g.size(); i++) { for (int j = 0; j < g[i].size(); j++) { if (g[i][j] == '1') { // 如果当前位置是陆地('1'表示陆地) dfs(i, j); // 对当前的岛屿进行深度优先搜索 cnt++; // 岛屿数量加一 } } } return cnt; // 返回岛屿的总数量 } void dfs(int x, int y) { g[x][y] = '0'; // 将当前位置标记为已访问过的水域('0'表示已访问过) // 遍历四个方向 for (int i = 0; i < 4; i++) { int a = x + dx[i], b = y + dy[i]; // 计算下一个位置的坐标 // 判断下一个位置是否在地图范围内,并且是陆地 if (a >= 0 && a < g.size() && b >= 0 && b < g[a].size() && g[a][b] == '1') { dfs(a, b); // 对下一个位置进行深度优先搜索 } } } };
206
迭代
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode* reverseList(ListNode* head) { if (!head) return NULL; // 如果链表为空,直接返回NULL auto a = head, b = a->next; // 定义两个指针a和b,分别指向头节点和头节点的下一个节点 while (b) { auto c = b->next; // 定义指针c,指向b的下一个节点,用于暂存b的下一个节点 b->next = a; // 将b的next指针指向a,实现局部反转 a = b; // 将a指针向后移动一位,指向b,实现向后移动 b = c; // 将b指针向后移动一位,指向c,实现向后移动 } head->next = NULL; // 将反转后的头节点的next指针设为NULL,结束反转链表 return a; // 返回反转后的头节点a } };
递归
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode* reverseList(ListNode* head) { // 如果链表为空或者只有一个节点,直接返回head if (!head || !head->next) return head; // 调用递归函数,返回反转后的链表的头节点 auto tail = reverseList(head->next); // 将当前节点的下一个节点的next指针指向当前节点,实现局部反转 head->next->next = head; // 将当前节点的next指针设为NULL,结束反转过程 head->next = NULL; // 返回反转后的链表的头节点 return tail; } };
207
class Solution { public: bool canFinish(int n, vector<vector<int>>& edges) { // 创建邻接表表示有向图 vector<vector<int>> g(n); // 存储图的邻接表 vector<int> d(n); // 存储每个节点的入度 // 根据输入的边列表构建邻接表和入度数组 for (auto& e : edges) { int b = e[0]; // 边的起点 int a = e[1]; // 边的终点 g[a].push_back(b); // 将边添加到邻接表中 d[b]++; // 终点节点入度加一 } // 使用队列存储入度为零的节点,作为拓扑排序的起点 queue<int> q; for (int i = 0; i < n; ++i) { if (d[i] == 0) { q.push(i); // 将入度为零的节点加入队列 } } int cnt = 0; // 记录能够拓扑排序的节点数 while (!q.empty()) { int t = q.front(); // 获取队首节点 q.pop(); // 出队 cnt++; // 能够拓扑排序的节点数加一 for (auto& i : g[t]) { // 遍历当前节点的所有出边 if (--d[i] == 0) { // 将相邻节点的入度减一,并且如果入度变为零,加入队列 q.push(i); } } } return cnt == n; // 如果能够拓扑排序的节点数等于总节点数n,说明可以完成课程任务 } };
208
结构体包括当前是不是字符末尾的标志,和存储26个英文字母的指针数组,每个元素都是一个结构体类型的指针。构造函数,就是初始化为所有的指针为NULL。 同时定义一个结构体的root指针。初始话root 用new和构造函数在堆上申请一块内存并返回地址。
每个成员函数,先存一下根地址到p,然后用p遍历操作。对单词中的每个字母,计算字符对应的索引,如果当前节点没有这个索引的子节点,要么创建后更新,要么直接更新到p,进入下一层遍历。
最后遍历完成,要么记录当前为结束,要么检查是否结束,要么检查前缀存在不
class Trie { public: // 定义 Trie 的节点结构 struct Node { bool is_end; // 标记该节点是否为一个单词的结束 Node *son[26]; // 存储所有子节点(26 个小写字母) Node() { is_end = false; // 初始化时该节点不是单词的结束 for (int i = 0; i < 26; i++) son[i] = NULL; // 初始化时所有子节点指针为 NULL } }*root; // Trie 的根节点 /** Initialize your data structure here. */ Trie() { root = new Node(); // 初始化 Trie 时创建一个根节点 } /** Inserts a word into the trie. */ void insert(string word) { auto p = root; // 从根节点开始插入单词 for (auto c : word) { // 遍历单词中的每个字符 int u = c - 'a'; // 计算字符对应的索引 if (!p->son[u]) // 如果当前字符的子节点不存在 p->son[u] = new Node(); // 创建一个新节点 p = p->son[u]; // 移动到下一个节点 } p->is_end = true; // 标记该节点为单词的结束 } /** Returns if the word is in the trie. */ bool search(string word) { auto p = root; // 从根节点开始搜索单词 for (auto c : word) { // 遍历单词中的每个字符 int u = c - 'a'; // 计算字符对应的索引 if (!p->son[u]) // 如果当前字符的子节点不存在 return false; // 单词不在 Trie 中 p = p->son[u]; // 移动到下一个节点 } return p->is_end; // 检查当前节点是否标记为单词的结束 } /** Returns if there is any word in the trie that starts with the given prefix. */ bool startsWith(string prefix) { auto p = root; // 从根节点开始检查前缀 for (auto c : prefix) { // 遍历前缀中的每个字符 int u = c - 'a'; // 计算字符对应的索引 if (!p->son[u]) // 如果当前字符的子节点不存在 return false; // 没有单词以该前缀开始 p = p->son[u]; // 移动到下一个节点 } return true; // 前缀存在于 Trie 中 } }; /** * Your Trie object will be instantiated and called as such: * Trie* obj = new Trie(); * obj->insert(word); * bool param_2 = obj->search(word); * bool param_3 = obj->startsWith(prefix); */
215
对nums中的数据排好序,然后输出第k个元素,用快排
class Solution { public: // 使用快速排序来找出第 k 大的元素 int quick_sort(vector<int>& nums, int l, int r, int k) { // 递归基例:当左边界和右边界相同,说明子数组只有一个元素 if (l == r) return nums[k]; // 选择枢轴元素,这里选择了当前子数组的第一个元素作为枢轴 int x = nums[l]; int i = l - 1; // 左指针初始化为 l - 1 int j = r + 1; // 右指针初始化为 r + 1 // 分区操作:将小于枢轴的元素移动到左边,大于枢轴的元素移动到右边 while (i < j) { do i++; while (nums[i] > x); // 找到第一个不大于枢轴的元素 do j--; while (nums[j] < x); // 找到第一个不小于枢轴的元素 if (i < j) swap(nums[i], nums[j]); // 交换这两个元素 } // 递归查找第 k 大的元素 // 如果 k 在左边子数组中,递归左边 if (k <= j) return quick_sort(nums, l, j, k); // 否则,递归右边 else return quick_sort(nums, j + 1, r, k); } // 对外接口:找出数组中第 k 大的元素 int findKthLargest(vector<int>& nums, int k) { // 将 k 转换为 0 基索引,因此传递 k - 1 return quick_sort(nums, 0, nums.size() - 1, k - 1); } };
216
226
处理递归基,如果不是根节点返回空
一般情况,交换当前节点左右子树,递归左子树,右子树,返回根节点
注意C++ 中的 swap
函数用于交换两个变量的值
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: // 翻转二叉树 TreeNode* invertTree(TreeNode* root) { // 如果当前节点为空,直接返回空 if (!root) return NULL; // 交换当前节点的左子树和右子树 swap(root->left, root->right); // 递归翻转左子树 invertTree(root->left); // 递归翻转右子树 invertTree(root->right); // 返回翻转后的树的根节点 return root; } };
230dfs
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: int k, ans; // 用于存储第 k 小元素和当前的 k 值 // 查找二叉搜索树中第 k 小的元素 int kthSmallest(TreeNode* root, int _k) { k = _k; // 初始化 k 值 dfs(root); // 执行深度优先搜索 return ans; // 返回第 k 小的元素 } // 深度优先搜索函数,返回是否找到第 k 小的元素 bool dfs(TreeNode* root) { if (!root) return false; // 如果当前节点为空,返回 false // 递归访问左子树 if (dfs(root->left)) return true; // 当前节点的值是我们需要的第 k 小元素 if (--k == 0) { ans = root->val; // 记录当前节点的值 return true; // 找到第 k 小元素,返回 true } // 递归访问右子树 return dfs(root->right); } };
234
先计算链表长度,如果是0直接返回true。计算链表长度的一半,将头节点存到a。
将a移动到链表中间的位置,将中间位置存到b。反转链表的后半部分,比较前半部分和后半部分,如果不相同则不是回文串,相同则是回文串
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: bool isPalindrome(ListNode* head) { int n = 0; // 计算链表的长度 n for (auto p = head; p; p = p->next) n++; // 如果链表长度为0或1,直接返回 true(因为0或1个元素的链表都是回文的) if (n <= 1) return true; int half = n / 2; // 计算链表长度的一半 auto a = head; // 移动指针 a 到链表的中间位置 for (int i = 0; i < n - half; i++) a = a->next; // b 指向链表的后半部分的第一个节点 auto b = a->next; // 反转链表的后半部分 for (int i = 0; i < half - 1; i++) { auto c = b->next; // 保存下一个节点 b->next = a; // 反转当前节点的指针 a = b; // 更新 a 为当前节点 b = c; // 移动 b 到下一个节点 } // 比较前半部分和反转后的后半部分 auto p = head; // p 指向链表的开头 auto q = a; // q 指向反转后的链表的开头 bool success = true; for (int i = 0; i < half; i++) { // 如果两个节点的值不相等,链表不是回文 if (p->val != q->val) { success = false; break; } p = p->next; // 移动 p 到下一个节点 q = q->next; // 移动 q 到下一个节点 } // 恢复链表的原状(反转链表的后半部分) auto tail = a; // 保存反转后的链表的开头 b = a->next; // b 指向反转链表的第二个节点 // 反转链表的后半部分,恢复原链表 for (int i = 0; i < half - 1; i++) { auto c = b->next; // 保存下一个节点 b->next = a; // 反转当前节点的指针 a = b; // 更新 a 为当前节点 b = c; // 移动 b 到下一个节点 } // 断开链表尾部的连接 tail->next = NULL; return success; // 返回是否为回文 } };
236dfs
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: TreeNode* ans = NULL; // 用于存储最终的最近公共祖先节点 // 主函数:找到节点 p 和 q 的最近公共祖先 TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { dfs(root, p, q); // 执行深度优先搜索 return ans; // 返回找到的最近公共祖先 } // 深度优先搜索函数,返回状态码 int dfs(TreeNode* root, TreeNode* p, TreeNode* q) { if (!root) return 0; // 如果当前节点为空,返回状态码0 int state = dfs(root->left, p, q); // 递归搜索左子树 if (root == p) state |= 1; // 如果当前节点是 p,将状态码标记为1 else if (root == q) state |= 2; // 如果当前节点是 q,将状态码标记为2 state |= dfs(root->right, p, q); // 递归搜索右子树 // 如果当前节点是 p 和 q 的公共祖先,并且还没有找到最近公共祖先 if (state == 3 && !ans) ans = root; return state; // 返回当前节点的状态码 } };
238
等于前缀乘积 * 后缀乘积
当前nums[i]的前缀乘积p[i] = 前一个的前缀乘积 * 前一个数
后缀乘积 = 后一个的后缀乘积 * 后一个数
计算前缀和。
从后面开始计算后缀和顺便用前缀和数组存答案
class Solution { public: vector<int> productExceptSelf(vector<int>& nums) { int n = nums.size(); // 获取数组的长度 vector<int> p(n, 1); // 创建一个与输入数组相同长度的结果数组,并初始化为1 // 第一步:计算每个位置左侧所有元素的乘积 for (int i = 1; i < n; i ++ ) p[i] = p[i - 1] * nums[i - 1]; // 第二步:计算每个位置右侧所有元素的乘积,并将其乘到结果数组的对应位置 for (int i = n - 1, s = 1; i >= 0; i -- ) { p[i] *= s; // 将当前位置的乘积乘以右侧所有元素的乘积 s *= nums[i]; // 更新右侧所有元素的乘积 } return p; // 返回最终结果数组 } };
239
元素的长度 计算的时候+ 1
结果数组存的滑窗位置里的最大值。因为滑窗占k个元素大小。所以滑窗的起始位置是数组的起始位置加上滑窗大小。计算位置的时候要 - 1。a是开头,长度k,末尾位置是
b = a + k - 1。k是长度
计算长度是 k = b - a + 1。
滑动窗口有边界开始记录答案。窗口左边会过期,每次判断。窗口中后来的,只要比尾部的大,就pop,只要小于窗口尾的能插进来。
注意:判断的时候要判断q.size 否则不能pop_front(),用双端队列定义滑动窗口,没思路就直接开始遍历
注意;q存的是索引,查看队列末尾元素要用 nums[q.back()]
注意
要用大于等于,判断加不加入队列的时候也要用大于等于。
class Solution { public: vector<int> maxSlidingWindow(vector<int>& nums, int k) { deque<int> q; // 双端队列,用于存储窗口内元素的索引 vector<int> res; // 存储结果的数组 // 遍历数组中的每个元素 for (int i = 0; i < nums.size(); i ++ ) { // 移除窗口左边界外的元素 if (q.size() && i - q.front() + 1 > k) q.pop_front(); // 移除队列中所有小于当前元素的元素,因为它们不可能是窗口最大值 while (q.size() && nums[i] >= nums[q.back()]) q.pop_back(); // 将当前元素的索引加入队列 q.push_back(i); // 当遍历到的元素数大于等于窗口大小时,将队列头部元素(最大值)加入结果数组 if (i >= k - 1) res.push_back(nums[q.front()]); } return res; // 返回结果数组 } };
240
从右上角开始搜索。找到就返回,遍历完没找到就返回false。
注意:不用for,while判断是否出界
注意:如果全是else if会漏最后一步,if的最后一个一定是else,在3分支的情况下
class Solution { public: bool searchMatrix(vector<vector<int>>& matrix, int target) { // 如果矩阵为空或第一个行为空,返回 false if (matrix.empty() || matrix[0].empty()) return false; // 获取矩阵的行数和列数 int n = matrix.size(), m = matrix[0].size(); // 从矩阵的右上角开始搜索 int i = 0, j = m - 1; // 当行数在矩阵范围内且列数在矩阵范围内时 while (i < n && j >= 0) { int t = matrix[i][j]; if (t == target) return true; // 找到目标值,返回 true else if (t > target) j -- ; // 如果当前值大于目标值,向左移动 else i ++ ; // 如果当前值小于目标值,向下移动 } // 搜索完毕仍未找到目标值,返回 false return false; } };
287
有点抽象,把数组看成链表。如果有环的话,他们一定会相遇。然后参考链表的做法即可
注意:遍历下一个位置的 时候,不是按照地址,而是按照数组中的值
注意:两次判断,先是判断相等,然后判断不相等
注意:return a 的位置,第二层while之后return
class Solution { public: int findDuplicate(vector<int>& nums) { int a = 0, b = 0; while (true) { // 快指针每次移动一步,慢指针每次移动两步 a = nums[a]; b = nums[nums[b]]; // 当快指针和慢指针相遇时,表示存在环 if (a == b) { // 重新初始化慢指针 a = 0; // 找到环的入口 while (a != b) { a = nums[a]; b = nums[b]; } // 返回环的入口,即重复的数字 return a; } } // 代码实际上不会到达这里 return -1; } };
295难长
进阶:用最顶堆维护一个桶?
class MedianFinder { public: // 最小堆,存放较大的那一部分的元素 priority_queue<int, vector<int>, greater<int>> up; // 最大堆,存放较小的那一部分的元素 priority_queue<int> down; /** Initialize your data structure here. */ MedianFinder() { // 构造函数无需额外操作 } /** Adds a number into the data structure. */ void addNum(int num) { // 将 num 添加到最大堆(down) if (down.empty() || num <= down.top()) { down.push(num); // 确保最大堆(down)的大小不大于最小堆(up)+ 1 if (down.size() > up.size() + 1) { // 从最大堆(down)移动元素到最小堆(up) up.push(down.top()); down.pop(); } } else { // 将 num 添加到最小堆(up) up.push(num); // 确保最小堆(up)的大小不大于最大堆(down) if (up.size() > down.size()) { // 从最小堆(up)移动元素到最大堆(down) down.push(up.top()); up.pop(); } } } /** Returns the median of all numbers in the data structure. */ double findMedian() { // 当总数为奇数时,中位数是最大堆(down)堆顶元素 if ((down.size() + up.size()) % 2) { return down.top(); } // 当总数为偶数时,中位数是最大堆(down)和最小堆(up)堆顶元素的平均值 return (down.top() + up.top()) / 2.0; } }; /** * Your MedianFinder object will be instantiated and called as such: * MedianFinder* obj = new MedianFinder(); * obj->addNum(num); * double param_2 = obj->findMedian(); */
347
计数排序
hash的好处是,存的键值对,可以根据键找值,可以分别遍历键和值
用hash记录每个元素出现的次数,用数组记录出现次数的次数,用数组的下标表示出现次数的次数,然后从后往前遍历,找出最大的k个出现次数,然后从hash中找出出现这些次数的元素,加入结果
注意 for(auto [x, y] : hp) a[y] ++; //出现次数的元素种类 ++
while(t < k) t += a[i --]; //t每次加上最高的出现的次数的种类。如果还没到要求,就继续加。最后i表示前k个高品元素后面的数的个数
vector
for(auto [x, y] : hp) {
if(y > i) res.push_back(x); //只要个数大于 i,就把这个元素加入结果
}
class Solution { public: vector<int> topKFrequent(vector<int>& nums, int k) { // 记录每个元素的出现次数 unordered_map<int, int> cnt; for (auto x: nums) cnt[x] ++ ; // 遍历数组,填充计数器 int n = nums.size(); // 创建一个大小为 n + 1 的数组,用来存储频率的出现次数 vector<int> s(n + 1); // 遍历计数器,将每个出现次数填入数组 s for (auto [x, c]: cnt) s[c] ++ ; // c 是出现次数,s[c] 表示有多少个元素出现次数为 c int i = n, t = 0; // 从高到低遍历频率数组,累计出现次数,直到累计的次数 >= k while (t < k) t += s[i -- ]; vector<int> res; // 遍历计数器,将出现次数大于 i 的元素加入结果 for (auto [x, c]: cnt) if (c > i) res.push_back(x); return res; } };
394dfs
class Solution { public: string decodeString(string s) { int u = 0; // `u` 是字符串的指针,用于在递归过程中跟踪当前的位置 return dfs(s, u); // 从字符串开始位置调用递归函数 } // 递归解码函数 string dfs(string& s, int& u) { string res; // 用于保存当前解析的结果 while (u < s.size() && s[u] != ']') { // 遍历字符串直到遇到 ']' if (s[u] >= 'a' && s[u] <= 'z' || s[u] >= 'A' && s[u] <= 'Z') { // 如果当前字符是字母,直接添加到结果中 res += s[u++]; } else if (s[u] >= '0' && s[u] <= '9') { // 如果当前字符是数字,表示重复次数 int k = u; // 找到完整的数字 while (s[k] >= '0' && s[k] <= '9') k++; int x = stoi(s.substr(u, k - u)); // 提取数字并转换为整数 u = k + 1; // 跳过数字和左括号 '[' string y = dfs(s, u); // 递归解码括号内的部分 u++; // 跳过右括号 ']' while (x--) res += y; // 将解码后的字符串重复 x 次并添加到结果中 } } return res; // 返回当前结果 } };
437dfs
和第一题类似
每遍历到一个元素用hp查目标减当前是否存在,存在,则将数量追加到结果
递归处理递归基,
然后朴素情况,更新当前路径和,
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: unordered_map<int, int> cnt; // 用于记录前缀和的出现次数 int res = 0; // 记录路径和等于目标值的路径数量 int pathSum(TreeNode* root, int sum) { cnt[0]++; // 初始时考虑路径和为0的情况 dfs(root, sum, 0); // 从根节点开始深度优先遍历 return res; // 返回结果 } void dfs(TreeNode* root, int sum, int cur) { if (!root) return; // 如果节点为空,直接返回 cur += root->val; // 更新当前路径和 res += cnt[cur - sum]; // 如果当前路径和减去目标值的差值在cnt中存在,则找到了一条符合条件的路径 cnt[cur]++; // 记录当前路径和出现的次数 // 递归遍历左右子树 dfs(root->left, sum, cur); dfs(root->right, sum, cur); cnt[cur]--; // 回溯时,移除当前路径和的记录,保持状态一致 } };
438
滑动窗口 + 哈希
思路是,先将目标中每个元素的个数存到hash,然后记录共有几种元素。
从头开始遍历文本串,统计窗口中某个元素出现的 个数,如果与hash中对应元素的个数相等,那么已经达到要求,mark ++ 。如果超出了窗口范围,那么前边移动的时候,判断要剔除的元素已经达到目标个数,达到的话,剔除后,mark --;
最后判断,如果mark等于 hash中元素的数量,也就是hash的大小,那么就添加一个结果,进入下一个遍历。
注意,第一步hash中存的是目标串的元素个数
不要偷懒,第一步把几种元素的数量记录下来,因为后续再遍历的过程中会加入不是目标串的元素,导致hp长度变化
class Solution { public: vector<int> findAnagrams(string s, string p) { unordered_map<char, int> cnt; // 用于记录字符串 p 中每个字符的出现次数 for (auto c: p) cnt[c]++; // 统计 p 中每个字符的频次 vector<int> res; // 存储结果,即所有异位词的起始索引 int tot = cnt.size(); // 异位词中不同字符的总数 for (int i = 0, j = 0, satisfy = 0; i < s.size(); i++) { // 更新窗口的右端字符 if (--cnt[s[i]] == 0) satisfy++; // 记录字符 s[i] 的频次变化 // 如果当前窗口的大小大于 p 的长度,则移动左端 while (i - j + 1 > p.size()) { if (cnt[s[j]] == 0) satisfy--; // 记录字符 s[j] 的频次变化 cnt[s[j++]]++; // 窗口左移 } // 检查当前窗口是否是异位词 if (satisfy == tot) res.push_back(j); // 如果满足条件,记录起始索引 } return res; // 返回结果 } };
543
写递归代码先写递归基
递归到最后不是根节点,返回0,递归计算左右子树高度,一般节点,返回左右子树高度的最大值 加 1
然后递归处理左右子树,将返回值存下。
递归函数返回的是当前节点的深度,即左右子树深度最大值加1
在dfs里更新结果,
dfs返回的是高度,不考虑一个节点的情况,也就是dfs传入的只要不是
如果dfs传入的是原树根节点,这个返回没有意义。
传入的只要不是原树根节点,那么返回的就是上一个节点加上当前节点树的高度。
注意:int left = dfs(root->left);//上一个节点加上当前节点树的高度上
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: int ans = 0; // 用于存储当前找到的最大直径 int diameterOfBinaryTree(TreeNode* root) { dfs(root); // 启动深度优先搜索(DFS)来计算直径 return ans; // 返回最终的直径值 } int dfs(TreeNode* root) { if (!root) return 0; // 如果当前节点为空,返回深度0 // 递归计算左子树和右子树的深度 int left = dfs(root->left);//上一个节点加上当前节点树的高度上 int right = dfs(root->right); // 更新直径,直径是通过当前节点的左右子树深度之和 ans = max(ans, left + right); // 返回当前节点的深度,即左右子树最大深度+1(当前节点) return max(left, right) + 1; } };
560
只要有 当前前缀和减去目标值等于的前缀和 出现次数追加到结果
如果当前前缀和等于k,则追加前缀和为0 的次数,所以初始化为1
从零开始遍历,有的话就更新结果,没有的话就更新hash。
类似题:力扣1
注意:hash不是初始化为0,而是未键入的键对应的值都是0
注意:
i要从1开始 n结束。而且要初始话hp[0] = 1
class Solution { public: int subarraySum(vector<int>& nums, int k) { int n = nums.size(); // 获取数组长度 vector<int> s(n + 1); // 创建前缀和数组 for (int i = 1; i <= n; i ++ ) s[i] = s[i - 1] + nums[i - 1]; // 计算前缀和 unordered_map<int, int> hash; // 用于存储前缀和及其出现次数 hash[0] = 1; // 初始化前缀和为0的出现次数为1 int res = 0; // 记录满足条件的子数组的数量 for (int i = 1; i <= n; i ++ ) { // 检查当前前缀和减去目标值k的前缀和是否出现过 res += hash[s[i] - k]; // 更新当前前缀和在哈希表中的出现次数 hash[s[i]] ++ ; } return res; // 返回满足条件的子数组的数量 } };
503
单调栈,右边就从右开始数,左边就从左开始数
本题有循环,所以长度加倍,并且从右开始数,只要栈内元素小于等于当前元素,就出栈
只要一倍的长度遍历完,并且栈为空,那么当前元素的结果就是0;
否则就是栈顶元素,并把当前结果加入栈
class Solution { public: vector<int> nextGreaterElements(vector<int>& nums) { int n = nums.size(); nums.insert(nums.end(), nums.begin(), nums.end()); stack<int> stk; vector<int> res(n); for(int i = n * 2 - 1;i >= 0; i --) { int x = nums[i]; while(stk.size() && x >= stk.top()) stk.pop(); if(i < n) { if(!stk.size()) res[i] = -1; else { res[i] = stk.top(); } } stk.push(nums[i]); } return res; } };
735
739
单调栈模板,数右边离他最近且比他大的位置在哪模板
想求左边离i最近比目标值小的数。如果右边的比栈里的小,且更靠近目标值,所以永无出头之日。单调栈是一个单调递增的栈。
表示多久之后,元素直接相减
思路,从右往左遍历,如果小于当前元素,说明当前元素比栈顶元素大还离目标近,栈顶无出头之日,pop。如果栈不空,就一直pop。最后如果栈不空,将结果加入到res,多久之后,元素索引是相减的。最后将元素入栈。
stack
class Solution { public: vector<int> dailyTemperatures(vector<int>& T) { stack<int> stk; // 单调递减栈 vector<int> res(T.size()); // 结果数组 // 从后向前遍历温度数组 for (int i = T.size() - 1; i >= 0; i -- ) { // 保持栈中元素的单调性,移除栈中所有不符合条件的元素 while (stk.size() && T[i] >= T[stk.top()]) stk.pop(); // 如果栈非空,说明栈顶元素的温度高于 T[i] if (stk.size()) res[i] = stk.top() - i; // 将当前索引入栈 stk.push(i); } return res; // 返回结果数组 } };
763
元素的长度加一,元素的 位置减1
从头开始遍历,如果当前片段的末尾小于元素最后出现的位置,那么末尾更新为元素最后出现的 位置。
如果已经遍历到了片段末尾,则将这个片段的长度加入结果数组。
class Solution { public: vector<int> partitionLabels(string S) { unordered_map<int, int> last; // 记录每个字符最后出现的位置 for (int i = 0; i < S.size(); i ++ ) last[S[i]] = i; vector<int> res; int start = 0, end = 0; // 遍历字符串 for (int i = 0; i < S.size(); i ++ ) { // 更新当前部分的结束位置 end = max(end, last[S[i]]); // 当遍历到当前部分的结束位置时,记录部分长度 if (i == end) { res.push_back(end - start + 1); // 更新开始位置和结束位置 start = end = i + 1; } } return res; } };
994
每个新鲜橘子到腐烂橘子的最短路径,就是新鲜橘子腐烂的时间。
队列记录这一次循环(这一个时间单位)变腐烂的新鲜句子。如果队列为空,则上一次没有将新鲜橘子变腐烂。路径不可达。输出结果。
每次进入队列后时间加1,表示当前时间已经腐烂的橘子。所以初始化时间为-1。
如果队列不空,用队列里的橘子遍历下一个时刻腐烂的橘子。存到队列。
初始化队列为当前已经腐烂的橘子。每一次队列不空,把当前队列能污染到的所有新鲜橘子加入队列。然后进入下次循环。
注意判断出界没有,注意队列中存的是pair,注意res初始化为0,不是-1,注意define反过来,原先有的放在后面,重命名的放在前面
#define x first // 将 `x` 定义为 `pair` 的第一个元素 #define y second // 将 `y` 定义为 `pair` 的第二个元素 typedef pair<int, int> PII; // 定义 `PII` 为 `pair<int, int>` class Solution { public: int orangesRotting(vector<vector<int>>& g) { int n = g.size(); // 获取网格的行数 int m = g[0].size(); // 获取网格的列数 queue<PII> q; // 创建一个队列用于存储腐烂橘子的坐标 // 遍历网格,将所有腐烂的橘子坐标入队 for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (g[i][j] == 2) { // 如果当前橘子是腐烂的 q.push({i, j}); // 将其坐标加入队列 } } } // 定义四个方向的偏移量:上、右、下、左 int dx[] = {-1, 0, 1, 0}; int dy[] = {0, 1, 0, -1}; int res = 0; // 记录需要的时间 // 如果队列中有腐烂橘子,则时间减一(这是为了处理初始状态) if (q.size()) res--; // 开始 BFS 过程 while (!q.empty()) { // 当队列非空时继续处理 res++; // 每次 BFS 扩展一层,时间加一 int sz = q.size(); // 当前层的腐烂橘子数量 while (sz--) { // 遍历当前层的所有腐烂橘子 auto t = q.front(); // 获取队列的前端元素 q.pop(); // 移除队列的前端元素 for (int i = 0; i < 4; i++) { // 遍历四个方向 int x = t.x + dx[i]; // 计算新位置的行坐标 int y = t.y + dy[i]; // 计算新位置的列坐标 if (x < 0 || x >= n || y < 0 || y >= m || g[x][y] != 1) { // 如果新位置超出边界或不是新鲜橘子,则跳过 continue; } g[x][y] = 2; // 将新鲜橘子变为腐烂橘子 q.push({x, y}); // 将新腐烂橘子的坐标加入队列 } } } // 遍历网格检查是否还有新鲜橘子 for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (g[i][j] == 1) { // 如果有新鲜橘子 return -1; // 返回 -1,表示无法使所有橘子变腐烂 } } } return res; // 返回所需的最短时间 } };
1282
hs模拟
class Solution { public: vector<vector<int>> res; unordered_map<int, vector<int>> hp; vector<vector<int>> groupThePeople(vector<int>& nums) { for(int i = 0; i < nums.size(); i ++) { int x = nums[i]; hp[x].push_back(i); if(hp[x].size() == x) { res.push_back(hp[x]); hp[x].clear(); } } return res; } };
14
class Solution { public: string longestCommonPrefix(vector<string>& strs) { string res; if(strs.empty()) return res; for(int i = 0;;i ++) { if(i >= strs[0].size()) return res;//i大于等于第一个字符串的长度,该返回结果了 char c = strs[0][i];//否则把第一个字符串的第i个字母拿出来,依次判断一下所有字符串的第i个字符是不是和这个字符相同 for(auto& str: strs) { if(i >= str.size() || str[i] != c) return res; } res += c;//不相同则返回res否则前缀中加上c } return res; } };
724
class Solution { public: int pivotIndex(vector<int>& nums) { int sum = accumulate(nums.begin(), nums.end(), 0); for(int i = 0, s = 0; i < nums.size(); i ++) { if(s == sum - s - nums[i]) return i;//s是i前面的和,sum - s -nums[i]是i后面的和 s += nums[i]; } return -1; } };
365水壶问题
只能倒满或者倒空,
根据题目,发现两个杯子不能同时既不空也不满,
所以最优解的操作就四种情况,+a , - a, +b, -b。
所以等价于ax + by =c
裴蜀定理(a,b)最大公约数能整除c,上面的式子有解
class Solution { public: int gcd(int a, int b) { return b ? gcd(b, a % b) : a; } bool canMeasureWater(int a, int b, int c) { if(c > a + b) return false; return !c || c % gcd(a, b) == 0; } };
86
/** * 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* partition(ListNode* head, int x) { auto lh = new ListNode(-1), rh = new ListNode(-1);//虚拟头 auto lt = lh, rt = rh; for(auto p = head; p; p = p->next) { if(p->val < x) lt = lt->next = p; else rt = rt->next = p; } lt->next = rh->next; rt->next = NULL; return lh->next; } };
118
class Solution { public: vector<vector<int>> generate(int n) { vector<vector<int>> f; // 存储杨辉三角的所有行 for (int i = 0; i < n; i++) { // 从第0行开始生成,到第n-1行 vector<int> line(i + 1); // 创建一个包含i+1个元素的向量 line[0] = line[i] = 1; // 每行的第一个和最后一个元素都为1 for (int j = 1; j < i; j++) { // 遍历除了第一个和最后一个元素的其他元素 line[j] = f[i - 1][j - 1] + f[i - 1][j]; // 每个元素是前一行的两个元素之和 } f.push_back(line); // 将当前行添加到结果中 } return f; // 返回杨辉三角 } };
139
class Solution { public: // 主函数,判断字符串 s 是否可以拆分成字典中的单词 bool wordBreak(string s, vector<string>& wordDict) { set<string> word(wordDict.begin(), wordDict.end()); // vector<int> memo(s.size(), -1); return dfs(s, word, 0); } bool dfs(string s, set<string>& word, int u) { if (u == s.size()) return true; // if (memo[u] != -1) return memo[u]; for (int j = u + 1; j <= s.size(); ++j) { if (word.find(s.substr(u, j - u)) != word.end() && dfs(s, word, j)) { // memo[u] = 1; return true; } } // memo[u] = 0; return false; } };
150
class Solution { public: int evalRPN(vector<string>& tokens) { stack<int> stk; for(auto s : tokens) { if(s == "+" || s == "-" || s == "*" || s == "/") { auto b = stk.top(); stk.pop(); auto a = stk.top(); stk.pop(); if(s == "+") a += b; else if(s == "-") a -= b; else if(s == "*") a *= b; else a /= b; stk.push(a); } else stk.push(stoi(s)); } return stk.top(); } };
1156
class Solution { public: int maxRepOpt1(string s) { vector<int> p[26]; // 创建一个数组,数组中的每个元素都是一个 vector<int>,用于存储每个字母在字符串中的出现位置 // 遍历字符串 s,将每个字母的出现位置记录在对应的 vector<int> 中 for (int i = 0; i < s.size(); i++) { p[s[i] - 'a'].push_back(i); } int res = 0; // 初始化结果变量 // 遍历每个字母出现位置的向量 for (auto q : p) { // 第一次遍历,计算最长子序列长度 for (int i = 0, j = 0; i < q.size(); i++) { while (q[i] - q[j] > i - j + 1) j++; // 检查当前子序列是否超过一个字符可以替换的限制,如果超过则移动 j if (i + 1 < q.size() || j) // 如果还有剩余的元素或者 j 不为 0 res = max(res, q[i] - q[j] + 1); // 更新最大长度 } // 第二次遍历,计算允许多一个字符替换后的最长子序列长度 for (int i = 0, j = 0; i < q.size(); i++) { while (q[i] - q[j] > i - j) j++; // 检查当前子序列是否超过允许替换的字符限制,如果超过则移动 j int t = q[i] - q[j] + 1; // 计算当前子序列长度 if (i + 1 < q.size() || j) t++; // 如果还有剩余的元素或者 j 不为 0,则允许多一个字符替换 res = max(res, t); // 更新最大长度 } } return res; // 返回结果 } };
本文作者:tommiemie
本文链接:https://www.cnblogs.com/wswtc/p/18342516
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步