子串
560.和为K的子数组
使用前缀和预处理一下题目给的数组, 然后用二重循环遍历一遍就可以了。
239.滑动窗口最大值
看题面比较容易想到的是用优先级队列来解决, 但是STL中的priority_queue
不支持随机删除, 如果要用优先级队列来解决这道题的话比较复杂。这道题的一种正确解法是用单调队列来处理, 单调队列专门用来处理类似滑动窗口的区间最值问题。
接下来来看针对这道题, 单调队列是如何处理元素的入队和出队呢?
- 入队: 从队列的后方入队, 弹出所有比当前元素(即待入队元素)小的元素, 因为这些元素都比当前元素小, 而且入队时间更早, 如果队列中有当前元素, 那么这些较小的元素永远不可能成为答案
- 出队: 从队列的前方出队, 先弹出所有已经在区间外的元素, 然后得到的队头即是当前窗口的最大值
class Solution {
public:vector<int> maxSlidingWindow(vector<int>& nums, int k) {vector<int> res;deque<int> que; // 出队从前面, 入队从后面for (int i = 0; i < k; ++i) {while(!que.empty() && nums[i] > nums[que.back()]) que.pop_back();que.push_back(i);}res.push_back(nums[que.front()]);for (int i = k; i < nums.size(); ++i) { // 枚举区间右端点while(!que.empty() && nums[i] > nums[que.back()]) que.pop_back();que.push_back(i);while(que.front() <= i-k) {que.pop_front();}res.push_back(nums[que.front()]);}return res;}
};
这类区间最值问题还可以使用ST表或线段树来解决, 不过针对滑动窗口的区间最值问题, 还是单调队列更简便一些。
普通数组
53.最大子数组和
从前往后遍历, 用一个变量来记录和, 如果这个变量记录到的和小于0, 就将它重置为0; 因为它不会给答案带来正面的收益, 算上它之后数组的和只会更小。注意要处理全是负数的数组的情况, 此时答案就为数组中最大的负数。
56. 合并区间
刚开始打算使用使用一个大小为1e4
的数组来标记区间, 但是发现这种方法不能处理[1, 4], [5, 7]
的例子, 这个例子实际没有产生重叠, 但依旧会在数组中产生一个连续的不为0的区间。正确处理方式是对所有区间按照左端点进行排序, 然后就可以方便的进行合并操作了。
189.轮转数组
使用区间拷贝的库函数, 几行就搞定了。空间复杂度为O(1)
的方法可以通过旋转数组来实现。
class Solution {
public:void rotate(vector<int>& nums, int k) {k %= nums.size();vector<int> tmp(nums.rbegin(), nums.rbegin()+k);copy(nums.begin(), nums.end()-k, nums.begin()+k);copy(tmp.rbegin(), tmp.rend(), nums.begin());}
};
238.除自身以外数组的乘积
预处理前缀乘积和后缀乘积即可, 其中一个预处理数组可以使用一个变量来简化, 另一个预处理数组可以使用返回结果数组来代替。
class Solution {
public:vector<int> productExceptSelf(vector<int>& nums) {vector<int> res(nums.size());res[0] = 1;for (int i = 0; i < nums.size()-1; ++i) {res[i+1] = res[i] * nums[i];}int t = 1;for (int i = nums.size()-1; i >= 0; --i) {res[i] *= t;t *= nums[i];}return res;}
};
矩阵
73.矩阵置零
直接说进阶方法,用第1行和第1列来记录第i
行/第j
列是否需要置零,第1行和第1列是否需要置0可以使用两个标志变量来记录。
54.螺旋矩阵
我采用了一种类似分治的想法,通过一个循环来不断输出数组,在循环的过程中,每输出一整行,剩下需要输出的矩阵的高就减1;同样每输出一整列,剩下需要输出的矩阵的宽就减1,直到剩余矩阵的宽或高变为0时,输出就结束了。通过一个变量来标记当前输出的方向。
class Solution {
public:vector<int> spiralOrder(vector<vector<int>>& matrix) {vector<int> res;int height = matrix.size();int width = matrix[0].size();pair<int, int> pos(0, 0);/*** 移动方向* 1 --- 横向增大* 2 --- 纵向增大** -1 --- 横向减小* -2 --- 纵向减小*/int dir = 1;while(height && width) {if (dir == 1) {for (int i = 0; i < width; ++i) {res.push_back(matrix[pos.first][pos.second+i]);}--height;pos.second += width-1;pos.first += 1;dir = 2;} else if (dir == 2) {for (int i = 0; i < height; ++i) {res.push_back(matrix[pos.first+i][pos.second]);}--width;pos.first += height-1;pos.second -= 1;dir = -1;} else if (dir == -1) {for (int i = 0; i < width; ++i) {res.push_back(matrix[pos.first][pos.second-i]);}--height;pos.second -= width-1;pos.first -= 1;dir = -2;} else if (dir == -2) {for (int i = 0; i < height; ++i) {res.push_back(matrix[pos.first-i][pos.second]);}--width;pos.first -= height-1;pos.second += 1;dir = 1;}}return res;}
};
48.旋转图像
矩阵旋转90度,只需要沿对角线翻转矩阵,然后把矩阵逐行逆序即可。
240.搜索二维矩阵
遍历所有列, 二分查找。看了三叶的评论, 发现这个矩阵可以抽象成根节点在矩阵右上角的二叉搜索树, 太妙了。
class Solution {
public:bool searchMatrix(vector<vector<int>>& matrix, int target) { // 抽象成二叉搜索树pair<int, int> pos{0, matrix[0].size()-1};while(true) {if (target > matrix[pos.first][pos.second]) { // 往右子树查找if (pos.first == matrix.size()) return false;++pos.first;} else if (target < matrix[pos.first][pos.second]) {if (pos.second == 0) return false;--pos.second;} else {return true;}}return false;}
};
链表
160.相交链表
可以知道, 如果两个链表相交, 那么从链表的末尾到相交节点的距离一定是相等的, 我们在遍历两个链表的过程中, 可以先将指针移动到距离链表结尾距离相等的位置, 然后同时移动两个指针, 移动过程中查看两个指针是否指向了同一个节点。
206. 翻转链表
头插法即可。这里学习一下如何使用递归来完成翻转, 这里的递归和以前用到的递归形式不太一样, 不太好理解
/*** 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* reverseList(ListNode* head) {if (!head || !head->next) return head;ListNode* p = head;ListNode* newHead = reverseList(head->next); // (1)假设head后的所有节点都完成了翻转head->next->next = head; // (2)翻转后head的下一个节点变成了head前面紧邻的节点head->next = nullptr; // (3)head->next置空return newHead; // 返回翻转后的新头节点}
};
我们现在取一个中间状态来讲解一下,假设现在递归调用到了处理节点2的情况,执行完(1)
后链表的情况是这样的
执行完(2)
之后链表情况是这样的
执行完(3)
之后是这样的
234.回文链表
判断回文串的方式就是找到中间节点, 使用两个指针分别向两边移动, 依次比较即可, 难点主要在链表不能简单的找到它的前驱节点, 我们可以利用递归函数的性质来比较
class Solution {
private:ListNode* p;int pos;
public:bool isPalindrome(ListNode* head) {ListNode* slow = head, *fast = head;pos = 0;while (fast && fast->next) {slow = slow->next;fast = fast->next->next;++pos;}if (fast) p = slow->next;else p = slow;return cmp(head, 0);}bool cmp(ListNode* head, int cnt) {if (cnt < pos) {if (cmp(head->next, cnt+1)) {if (head->val == p->val) {p = p->next;return true;} else {return false;}} else {return false;}}return true;}
};