-
- 3.1. 删除链表中的重复元素
- 3.1.1. 删除排序链表中的重复元素
- 3.1.2. 删除排序链表中的重复元素II
- 3.2. 合并链表
- 3.3. 🌈链表的反转/相交/重排
- 3.3.1. 反转链表
- 3.3.2. 🌟两个两两交换链表中的节点
- 3.3.3. K个一组翻转链表
- 3.4. 链表模拟
- 3.1. 删除链表中的重复元素
-
- 4.1. 二叉树递归
- 4.1.1. 对称二叉树
- 4.1.2. 将有序数组转换为二叉搜索树
- 4.2. 二叉树的深度
- 4.2.1. 🌈二叉树的直径
- 4.3. 二叉树的层序遍历
- 4.3.1. 二叉树的右视图
- 4.3.2. 二叉树的锯齿形层序遍历
- 4.4. 二叉搜索树
- 4.4.1. 二叉搜索树的第K小节点
- 4.5. LCA
- 4.5.1. 二叉树的最近公共祖先
- 4.6. 二叉树模拟
- 4.6.1. 二叉树展开为链表
- 4.1. 二叉树递归
-
- 5.1. 旋转数组
- 5.1.1. 🌈搜索旋转排序数组
- 5.1.2. 寻找旋转排序数组中的最小值I-无重复元素
- 5.1.3. 寻找旋转排序数组中的最小值II-有重复元素
- 5.2. 答案二分排除
- 5.3. 二分查找
- 5.1. 旋转数组
-
- 6.1. 范围指针(往里面缩单调趋势)
- 6.1.1. 🌈三数之和
- 6.2. 快慢指针类
- 6.2.1. 🌟移动零
- 6.3. 滑动窗口-窗口固定类
- 6.3.1. 🌟滑动窗口最大值
- 6.3.2. 找到字符串中所有字母异位词
- 6.4. 滑动窗口-窗口不固定类
- 6.4.1. 无重复字符的最长子串
- 6.4.2. 最小覆盖子串
- 6.1. 范围指针(往里面缩单调趋势)
-
- 9.1. 一维dp
- 9.2. 二维dp
- 9.2.1. 买卖股票最佳时机
- 9.2.2. 买卖股票的最佳时机II
- 9.2.3. 杨辉三角
- 9.3. 两个字符串匹配
- 9.3.1. 🌈编辑距离
- 9.4. 区间DP
- 9.4.1. 最长回文子串
- 9.5. 背包问题
- 9.5.1. 零钱兑换(完全背包最值问题)
- 9.5.2. 零钱兑换II(完全背包不考虑顺序的组合问题)
- 9.5.3. 完全平方数(完全背包的最值问题)
- 9.5.4. 目标和
- 9.5.5. 分割等和子集
🤠Code
1. 背过
1.1. 区间第K大
超时的双指针思路:
- 每次取区间rand的一个value,把比value大的数都放在value前面,这个可以通过双指针来实现(l指针把比value大的区域分开)
/**
* 超时版思路
*/
class Solution {
public:
int findK(vector<int>& nums, int l, int r, int k) {
int rd = l + rand() % (r - l + 1);
int val = nums[rd];
swap(nums[l], nums[rd]);
int ll = l + 1;
int rr = l + 1;
// 双指针维护比val大的区域
while(rr <= r) {
if(nums[rr] > val) {
swap(nums[ll], nums[rr]);
ll++;
}
rr++;
}
// ll - 1的位置替换为val,并让当前位置与k比较
swap(nums[ll - 1], nums[l]);
int idx = ll - 1;
if(idx == k) return val;
else if(idx < k) return findK(nums, idx + 1, r, k);
else{
return findK(nums, l, idx - 1, k);
}
}
int findKthLargest(vector<int>& nums, int k) {
return findK(nums, 0, nums.size() - 1, k - 1);
}
};
小根堆思路:维护一个小根堆,固定长度为k,遍历元素,每次只有比堆顶元素大才会加入堆中,最后堆顶元素为第k大
/**
* 小根堆思路
*/
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int, vector<int>, greater<int>> queue;
for(int &i : nums) {
if(queue.size() < k) {
queue.push(i);
} else {
if(i > queue.top()) {
queue.pop();
queue.push(i);
}
}
}
return queue.top();
}
};
1.2. LRU Cache
思路:
- Map快速定位 + 双向链表头插法维护最近使用 + size维护当前和capacity的容量
- 写两个函数addToHead()和remove(),注意这两个函数要维护size和Map
- get()判断如果Map中有则remove() + addToHead()
- put()判断如果Map中没有则直接addToHead(),加入以后如果size大于capacity了则remove(tail->prev)。如果Map中没有则remove() + addToHead()
struct Node {
Node* prev;
Node* next;
int key;
int val;
Node(int key, int val) : key(key), val(val){}
};
class LRUCache {
public:
int capacity;
int size;
Node* head;
Node* tail;
unordered_map<int, Node*> map;
LRUCache(int capacity) : capacity(capacity), size(0), head(new Node(-1, -1)), tail(new Node(-1, - 1)) {
head->next = tail;
tail->prev = head;
}
int get(int key) {
if(map.count(key)) {
Node* node = map[key];
remove(node);
addToHead(node);
return node->val;
}else {
return -1;
}
}
void put(int key, int value) {
if(!map.count(key)) {
Node* node = new Node(key, value);
addToHead(node);
if(size > capacity) {
remove(tail->prev);
}
} else {
Node* node = map[key];
node->val = value;
remove(node);
addToHead(node);
}
}
void addToHead(Node* node) {
Node* nn = head->next;
nn->prev = node;
node->next = nn;
head->next = node;
node->prev = head;
map[node->key] = node;
size++;
}
void remove(Node* node) {
Node* nn = node->next;
Node* pp = node->prev;
pp->next = nn;
nn->prev = pp;
map.erase(node->key);
size--;
}
};
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/
1.3. K个一组反转链表
思路:
- 先背一个反转链表
- [ pre [start end] nxt ]
- end是控制遍历的,每次循环k次向后移动都到end处
/**
* 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* reverseKGroup(ListNode* head, int k) {
// [ pre [start end] nxt ]
ListNode* H = new ListNode(-1);
H->next = head;
ListNode* end = H;
ListNode* pre = H;
while(end->next) {
for(int i = 0; i < k && end; ++i) {
end = end->next;
}
if(!end) break;
ListNode* start = pre->next;
ListNode* nxt = end->next;
end->next = nullptr;
pre->next = reverse(start);
start->next = nxt;
end = start;
pre = end;
}
return H->next;
}
ListNode* reverse(ListNode* head) {
ListNode* prev = nullptr;
ListNode* cur = head;
ListNode* nextp = nullptr;
while(cur) {
nextp = cur->next;
cur->next = prev;
prev = cur;
cur = nextp;
}
return prev;
}
};
2. 常考算法法
2.1. 排序
2.1.1. 快速排序
思路:控制区间一个rand的val保证其在正确的位置(val前面的一定小于它,后面的一定大于它)
class Solution {
public:
void Qsort(vector<int> &nums, int l, int r) {
if(l >= r) return;
int ll = l;
int rr = r;
int val = nums[l + rand() % (r - l + 1)];
while(ll <= rr) {
while(ll <= rr && nums[ll] < val) ll++;
while(ll <= rr && nums[rr] > val) rr--;
if(ll <= rr) swap(nums[ll++], nums[rr--]);
}
Qsort(nums, l, rr);
Qsort(nums, ll, r);
}
vector<int> sortArray(vector<int>& nums) {
Qsort(nums, 0, nums.size() - 1);
return nums;
}
};
2.1.2. 堆排序
思路:
- insert():插入的时候先插入数组最后面,然后逐个跟父节点比较并交换以得到它正确的位置
- pop():弹出heap[0](堆顶),即让heap数组最后一个替换heap[0],然后对0位置开始调用heapify[0]
- heapify():从该位置开始,比较左孩子和右孩子,如果比它大则交换,递归。
class Solution {
public:
class MaxHeap {
public:
vector<int> heap;
void insert(int val) {
heap.push_back(val);
int cur = heap.size() - 1;
while(cur > 0 && heap[cur] > heap[(cur - 1) / 2]) {
swap(heap[cur], heap[(cur - 1) / 2]);
cur = (cur - 1) / 2;
}
}
int pop() {
int tmp = heap[0];
heap[0] = heap.back();
heap.pop_back(); // 居然有这个函数嘛?
heapify(0);
return tmp;
}
void heapify(int x) {
int xl = x * 2 + 1;
int xr = x * 2 + 2;
int xx = x;
if(xl < heap.size() && heap[xl] > heap[xx]) xx = xl;
if(xr < heap.size() && heap[xr] > heap[xx]) xx = xr;
if(xx != x) {
swap(heap[xx], heap[x]);
heapify(xx);
}
}
};
vector<int> sortArray(vector<int>& nums) {
MaxHeap *mx = new MaxHeap();
for(auto &x : nums) mx->insert(x);
for(int i = 0; i < nums.size(); ++i) nums[i] = mx->pop();
reverse(nums.begin(), nums.end());
return nums;
}
};
2.1.3. 归并排序
思路:mid,左边右边排好,然后归并
class Solution {
void mergeSort(vector<int>& nums, int l, int r) {
if(l >= r) return;
int mid = (l + r) >> 1;
mergeSort(nums, l, mid);
mergeSort(nums, mid + 1, r);
int ll = l;
int rr = mid + 1;
int cur = l;
while(ll < mid && rr < r) {
if(nums[ll] < nums[rr]) st[cur++] = nums[ll++];
else st[cur++] = nums[rr++];
}
while(ll < mid) st[cur++] = nums[ll++];
while(rr < mid) st[cur++] = nums[rr++];
}
public:
vector<int> st;
vector<int> sortArray(vector<int> nums) {
st.resize((int)nums.size(), 0);
mergeSort(nums, 0, nums.size() - 1);
return st;
}
};
3. 链表
3.1. 删除链表中的重复元素
3.1.1. 删除排序链表中的重复元素
用双指针会改变结构,双指针用在数组中会比较好
这题需要在循环中控制cur
/**
* 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* deleteDuplicates(ListNode* head) {
ListNode* cur = head;
while(cur && cur->next) {
if(cur->val == cur->next->val) {
cur->next = cur->next->next;
} else {
cur = cur->next;
}
}
return head;
}
};
3.1.2. 删除排序链表中的重复元素II
控制的还是cur,不管I还是II有一点很关键,如果不需要更改cur后面的,就让cur = cur->next。如果需要更改,下一轮还是cur
p是当前的,判断是判断q(p->next)是否是一段重复的开头,是的话全部去掉
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode* dummy = new ListNode(-1);
dummy->next = head;
ListNode* p = dummy;
while(p && p->next) {
ListNode* q = p->next;
while(q->next && q->next->val == q->val) q = q->next;
if(p->next == q) p = p->next;
else p->next = q->next;
}
return dummy->next;
}
};
3.2. 合并链表
3.2.1. 合并K个升序链表
优先队列维护每个链表的最小的,其他跟合并两个就是一样的了
/**
* 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* mergeKLists(vector<ListNode*>& lists) {
priority_queue<pair<int, ListNode*>> q;
for(int i = 0; i < lists.size(); ++i) {
if(lists[i]) {
q.push(make_pair(-lists[i]->val, lists[i]));
}
}
ListNode* dummy = new ListNode(-1);
ListNode* cur = dummy;
while(!q.empty()) {
pair<int, ListNode*> pp = q.top();
ListNode* node = pp.second;
q.pop();
cur->next = node;
cur = cur->next;
if(node->next) q.push(make_pair(-node->next->val ,node->next));
}
return dummy->next;
}
};
3.2.2. 排序链表
需要注意两个点:什么情况下链表会出现无限递归导致的栈溢出,根据写的双指针移动来确定
这里我是把这种情况单独处理了
/**
* 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* sortList(ListNode* head) {
if(!head || !head->next) return head;
ListNode* p1 = head, *p2 = head;
if(!head->next->next) {
p2 = head->next;
} else {
while(p2) {
p2 = p2->next;
if(!p2) break;
p2 = p2->next;
p1 = p1->next;
}
}
ListNode* head1 = p1->next;
p1->next = nullptr;
ListNode* h1 = sortList(head);
ListNode* h2 = sortList(head1);
ListNode* H = new ListNode(-1);
ListNode* cur = H;
while(h1 && h2) {
if(h1->val < h2->val) {
cur->next = h1;
h1 = h1->next;
cur = cur->next;
} else {
cur->next = h2;
h2 = h2->next;
cur = cur->next;
}
}
while(h1) {
cur->next = h1;
h1 = h1->next;
cur = cur->next;
}
while(h2) {
cur->next = h2;
h2 = h2->next;
cur = cur->next;
}
return H->next;
}
};
3.3. 🌈链表的反转/相交/重排
3.3.1. 反转链表
(迭代)背出来的
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = nullptr, *cur = head, *nxt = nullptr;
while(cur) {
nxt = cur->next;
cur->next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
};
(递归):
3.3.2. 🌟两个两两交换链表中的节点
背出来,易错在第一句 if(!head || !head->next)
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(!head || !head->next) return head;
ListNode* nxt = head->next;
ListNode* nnxt = nxt->next;
nxt->next = head;
head->next = swapPairs(nnxt);
return nxt;
}
};
3.3.3. K个一组翻转链表
[ pre [start end] nxt ]
记得几个点:1. while(end->next) 2. 如果不够k个的特殊处理
/**
* 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* reverse(ListNode* head) {
ListNode* pre = nullptr, *cur = head, *nxt = nullptr;
while(cur) {
nxt = cur->next;
cur->next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
ListNode* reverseKGroup(ListNode* head, int k) {
int sum = 0;
ListNode* cur = head;
while(cur) {
cur = cur->next;
sum++;
}
ListNode* H = new ListNode(-1);
H->next = head;
ListNode* end = H;
ListNode* pre = H;
while(end->next) {
for(int i = 0; i < k && end; ++i) {
end = end->next;
}
if(!end) break;
ListNode* nxt = end->next;
ListNode* start = pre->next;
end->next = nullptr;
ListNode* head1 = reverse(start);
pre->next = head1;
start->next = nxt;
pre = end = start;
}
return H->next;
}
};
3.4. 链表模拟
不需要虚拟头节点
/*
// 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:
unordered_map<Node*, Node*> hash;
Node* copyRandomList(Node* head) {
if(!head) return nullptr;
Node* cur = head;
while(cur) {
Node* node = new Node(cur->val);
hash[cur] = node;
cur = cur->next;
}
cur = head;
while(cur) {
hash[cur]->next = hash[cur->next];
hash[cur]->random = hash[cur->random];
cur = cur->next;
}
return hash[head];
}
};
4. 二叉树
4.1. 二叉树递归
4.1.1. 对称二叉树
process(root, root)
递归处理比较特殊,传入的是两个
class Solution {
public:
bool isSymmetric(TreeNode* root) {
return process(root, root);
}
bool process(TreeNode* root1, TreeNode* root2) {
if(!root1 && !root2) return true;
else if(root1 && root2) {
if(root1->val != root2->val) return false;
bool l = process(root1->left, root2->right);
bool r = process(root1->right, root2->left);
return l && r;
} else return false;
}
};
4.1.2. 将有序数组转换为二叉搜索树
108. 将有序数组转换为二叉搜索树 - 力扣(LeetCode)
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
return process(nums, 0, nums.size() - 1);
}
TreeNode* process(vector<int>& nums, int l, int r) {
if(l > r) return nullptr;
int m = l + (r - l) / 2;
TreeNode* ll = process(nums, l, m - 1);
TreeNode* rr = process(nums, m + 1, r);
TreeNode* root = new TreeNode(nums[m]);
root->left = ll;
root->right = rr;
return root;
}
};
4.2. 二叉树的深度
4.2.1. 🌈二叉树的直径
边数 = 节点数 - 1
class Solution {
public:
int ans;
int func(TreeNode* node) {
if(!node) return 0;
int left = func(node->left);
int right = func(node->right);
int d = left + right;
ans = max(ans, d);
return max(left, right) + 1;
}
int diameterOfBinaryTree(TreeNode* root) {
ans = 0;
func(root);
return ans;
}
};
4.3. 二叉树的层序遍历
4.3.1. 二叉树的右视图
/**
* 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:
vector<int> rightSideView(TreeNode* root) {
if(!root) return {};
queue<TreeNode*> q;
q.push(root);
vector<int> res;
while(!q.empty()) {
int qsize = q.size();
for(int i = 0 ; i < qsize; ++i) {
auto n = q.front();
if(i == qsize - 1) {
res.push_back(n->val);
}
q.pop();
if(n->left) q.push(n->left);
if(n->right) q.push(n->right);
}
}
return res;
}
};
4.3.2. 二叉树的锯齿形层序遍历
相比于层序遍历,只是vector换成了deque,可以push_back也可以push_front,最后把deque转化为vector可以用vector
class Solution {
public:
vector<vector<int>> ans;
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
if(!root) return {};
queue<TreeNode*> q;
q.push(root);
int idx = 1;
while(!q.empty()) {
int size = q.size();
deque<int> arr;
for(int i = 0; i < size; ++i) {
auto n = q.front();
q.pop();
if(n->left) q.push(n->left);
if(n->right) q.push(n->right);
if(idx % 2) arr.push_back(n->val);
else arr.push_front(n->val);
}
ans.push_back(vector<int>(arr.begin(), arr.end()));
idx++;
}
return ans;
}
};
4.4. 二叉搜索树
4.4.1. 二叉搜索树的第K小节点
二叉搜索树->中序遍历
如果找的是第K小只需要先右后左
class Solution {
public:
int cnt;
int ans;
void dfs(TreeNode* root, int k) {
if(root->left) dfs(root->left, k);
if(cnt++ == k) ans = root->val;
if(root->right) dfs(root->right, k);
}
int kthSmallest(TreeNode* root, int k) {
cnt = 1;
ans = -1;
dfs(root, k);
return ans;
}
};
4.5. LCA
4.5.1. 二叉树的最近公共祖先
套路
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(!root || root == p || root == q) {
return root;
}
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if(left && right) return root;
else return left ? left : right;
}
};
4.6. 二叉树模拟
4.6.1. 二叉树展开为链表
记得先把root左节点保存下来就好了
class Solution {
public:
TreeNode* cur;
void flatten(TreeNode* root) {
cur = new TreeNode(-1);
dfs(root);
}
void dfs(TreeNode* root) {
if(!root) return;
TreeNode* r = root->right;
TreeNode* l = root->left;
cur->right = root;
root->left = nullptr;
cur = root;
dfs(l);
dfs(r);
}
};
5. 二分
5.1. 旋转数组
5.1.1. 🌈搜索旋转排序数组
套路还是先找分界点。
用红蓝染色法需要注意的一点是r == n的特殊判断
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = nums.size();
int l = -1;
int r = n;
while(l + 1 < r) {
int mid = (l + r) >> 1;
if(nums[mid] >= nums[0]) {
l = mid;
} else {
r = mid;
}
}
int ll,rr;
if(target >= nums[0]) {
ll = -1;
rr = r;
} else {
ll = l;
rr = n;
}
if(r == n) {
ll = -1;
rr = n;
}
while(ll + 1 < rr) {
int m = (ll + rr) >> 1;
if(nums[m] < target) {
ll = m;
} else {
rr = m;
}
}
if(rr == n) return -1;
return nums[rr] == target ? rr : -1;
}
};
5.1.2. 寻找旋转排序数组中的最小值I-无重复元素
上一题的简化版,easy
class Solution {
public:
int findMin(vector<int>& nums) {
int n = nums.size();
int l = -1;
int r = n;
while(l + 1 < r) {
int mid = (l + r) >> 1;
if(nums[mid] >= nums[0]) {
l = mid;
} else {
r = mid;
}
}
if(r == n) {
return nums[0];
} else {
return nums[r];
}
}
};
5.1.3. 寻找旋转排序数组中的最小值II-有重复元素
首尾的重复元素会破坏二段性,中间的重复元素
所以只需要一开始把首位重复的元素去掉,就跟第一个题是一样的了
class Solution {
public:
int findMin(vector<int>& nums) {
int n = nums.size();
int l = -1;
int r = n - 1;
while(r > 0 && nums[0] == nums[r]) r--;
r++;
n = r;
while(l + 1 < r) {
int mid = (l + r) >> 1;
if(nums[mid] >= nums[0]) {
l = mid;
} else {
r = mid;
}
}
if(r == n) {
return nums[0];
} else {
return nums[r];
}
}
};
5.2. 答案二分排除
5.3. 二分查找
6. 双指针
6.1. 范围指针(往里面缩单调趋势)
6.1.1. 🌈三数之和
注意去重,更要注意的是每个while都要有l < r
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
sort(nums.begin(), nums.end());
int n = nums.size();
for(int i = 0; i < n - 2; ++i) {
if(i > 0 && nums[i] == nums[i - 1]) continue;
int l = i + 1;
int r = n - 1;
while(l < r) {
int target = nums[l] + nums[r] + nums[i];
if(target > 0) {
r--;
while(l < r && nums[r] == nums[r + 1]) r--;
} else if(target < 0) {
l++;
while(l < r && nums[l] == nums[l - 1]) l++;
} else {
ans.push_back({nums[i], nums[l], nums[r]});
r--;
while(l < r && nums[r] == nums[r + 1]) r--;
l++;
while(l < r && nums[l] == nums[l - 1]) l++;
}
}
}
return ans;
}
};
6.2. 快慢指针类
6.2.1. 🌟移动零
l指针分割两个区域,r指针指向的满足l指针区域则交换
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int l = 0;
int r = 0;
while(r < nums.size()) {
if(nums[r] != 0) {
swap(nums[l], nums[r]);
l++;
}
r++;
}
}
};
6.3. 滑动窗口-窗口固定类
6.3.1. 🌟滑动窗口最大值
deque的API: front() back() push_back() push_front() pop_back() pop_front()
每一个新元素加进来就把它前面比它矮的都踢走
在这之前如果deque队首不在窗口中了(nums[i - k] == deque.front()),踢走
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
vector<int> res(n - k + 1, 0);
deque<int> dq;
for(int i = 0; i < k; ++i) {
while(!dq.empty() && dq.back() < nums[i]) {
dq.pop_back();
}
dq.push_back(nums[i]);
}
res[0] = dq.front();
for(int i = k; i < n; ++i) {
if(nums[i - k] == dq.front()) {
dq.pop_front();
}
while(!dq.empty() && dq.back() < nums[i]) {
dq.pop_back();
}
dq.push_back(nums[i]);
res[i - k + 1] = dq.front();
}
return res;
}
};
6.3.2. 找到字符串中所有字母异位词
438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
easy
class Solution {
public:
bool match(int* cnts, int* cntp) {
for(int i = 0; i < 26; ++i) {
if(cntp[i] != cnts[i]) return false;
}
return true;
}
vector<int> findAnagrams(string s, string p) {
if(s.length() < p.length()) return {};
vector<int> ans;
int cnts[26];
int cntp[26];
for(int i = 0 ; i < p.length(); ++i) cntp[p[i] - 'a']++;
int j = 0;
for(j = 0; j < p.length(); ++j) cnts[s[j] - 'a']++;
if(match(cnts, cntp)) ans.push_back(0);
int l = 0;
for(; j < s.length(); ++j) {
cnts[s[j] - 'a']++;
cnts[s[l++] - 'a']--;
if(match(cnts, cntp)) ans.push_back(l);
}
return ans;
}
};
6.4. 滑动窗口-窗口不固定类
6.4.1. 无重复字符的最长子串
class Solution {
public:
unordered_map<char, int> map;
int ans;
int lengthOfLongestSubstring(string s) {
int l = 0;
for(int i = 0; i < s.length(); ++i) {
char c = s[i];
map[c]++;
while(map[c] > 1) map[s[l++]]--;
ans = max(ans, i - l + 1);
}
return ans;
}
};
6.4.2. 最小覆盖子串
LCR 017. 最小覆盖子串 - 力扣(LeetCode)
小技巧,在不满足的时候cnt++,直到cnt = t的长度说明覆盖了
while l右移的时候保持仍保持覆盖条件
class Solution {
public:
string minWindow(string s, string t) {
unordered_map<char, int> cnts, cntt;
string ans;
for(int i = 0; i < t.length(); ++i) cntt[t[i]]++;
int l = 0;
int cnt = 0;
for(int i = 0; i < s.length(); ++i) {
cnts[s[i]]++;
if(cnts[s[i]] <= cntt[s[i]]) cnt++;
while(cnts[s[l]] > cntt[s[l]]) cnts[s[l++]]--;
if(cnts[s[i]] >= cntt[s[i]] && cnt == t.length()) {
if(ans.empty() || ans.length() > i - l + 1) {
ans = s.substr(l, i - l + 1);
}
}
}
return ans;
}
};
7. 数据结构-栈/单调栈
7.1. 单调栈
7.1.1. 🌈柱状图中的最大矩形
注意前后补零化简
注意别忘了往栈里加元素
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int maxS = 0;
heights.insert(heights.begin(), 0);
heights.push_back(0);
stack<int> st;
for(int i = 0; i < heights.size(); ++i) {
while(!st.empty() && heights[st.top()] > heights[i]) {
int pp = st.top();
st.pop();
maxS = max(maxS, (i - st.top() - 1) * heights[pp]);
}
st.push(i);
}
return maxS;
}
};
7.1.2. 接雨水
与上一个题的边界的区别
class Solution {
public:
int trap(vector<int>& height) {
stack<int> st;
int ans = 0;
for(int i = 0; i < height.size(); ++i) {
while(!st.empty() && height[st.top()] <= height[i]) {
int t = st.top();
st.pop();
if(st.empty()) break;
ans += (i - st.top() - 1) * (min(height[i], height[st.top()]) - height[t]);
}
st.push(i);
}
return ans;
}
};
7.2. 栈模拟
7.2.1. 有效的括号
class Solution {
public:
bool isValid(string s) {
stack<char> st;
int n = s.length();
for(int i = 0; i < n; i++) {
char c = s[i];
switch(c) {
case ')': {
if(st.empty() || !(st.top() == '(')) return false;
st.pop();
break;
}
case '}': {
if(st.empty() || !(st.top() == '{')) return false;
st.pop();
break;
}
case ']': {
if(st.empty() || !(st.top() == '[')) return false;
st.pop();
break;
}
default: {
st.push(c);
}
}
}
return st.empty();
}
};
7.2.2. 字符串解码
倒着遍历
class Solution {
public:
string decodeString(string s) {
stack<string> st;
int n = s.length();
for(int i = n - 1; i > -1; ) {
if(s[i] == '[') {
string ss = "";
while(!st.empty()) {
string c = st.top();
if(c == "]") break;
st.pop();
ss += c;
}
st.pop();
i--;
int cnt = 1;
int num = 0;
while(i > -1 && s[i] >= '0' && s[i] <= '9') {
num += (s[i] - '0') * cnt;
cnt *= 10;
i--;
}
string sss = "";
for(int j = 0; j < num; ++j) {
sss += ss;
}
st.push(sss);
} else {
st.push(string(1, s[i]));
i--;
}
}
string res = "";
while(!st.empty()) {
res += st.top();
st.pop();
}
return res;
}
};
7.3. 栈维护性质
7.3.1. 最小栈
最小值的栈记得先push进去一个INT_MAX
class MinStack {
public:
stack<int> minst;
stack<int> curst;
MinStack() {
minst.push(INT_MAX);
}
void push(int val) {
minst.push(min(val, minst.top()));
curst.push(val);
}
void pop() {
if(!curst.empty()) {
curst.pop();
minst.pop();
}
}
int top() {
return curst.top();
}
int getMin() {
return minst.top();
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(val);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
8. 数据结构-堆
8.1. 堆维护第K
8.1.1. 前K个高频元素
参考第K大元素,只不过用pair多了一个频率
class Solution {
public:
unordered_map<int, int> map;
priority_queue<pair<int, int>> q;
vector<int> topKFrequent(vector<int>& nums, int k) {
for(int i = 0; i < nums.size(); ++i) {
map[nums[i]]++;
}
for(auto& p : map) {
if(q.size() < k) {
q.push(make_pair(-p.second, p.first));
} else {
if(q.top().first > -p.second) {
q.pop();
q.push(make_pair(-p.second, p.first));
}
}
}
vector<int> arr;
while(!q.empty()) {
arr.push_back(q.top().second);
q.pop();
}
return arr;
}
};
9. 动态规划
9.1. 一维dp
9.1.1. 🌈最长递增子序列
dp[i]代表以i结尾的最长的递增子序列
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
vector<int> dp(n, 1);
int ans = 1;
for(int i = 1; i < nums.size(); ++i) {
for(int j = 0; j < i; ++j) {
if(nums[i] > nums[j]) {
dp[i] = max(dp[j] + 1, dp[i]);
}
}
ans = max(ans, dp[i]);
}
return ans;
}
};
9.1.2. 打家劫舍
不要用二维
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
vector<int> dp(n, 0);
dp[0] = nums[0];
if(n == 1) return dp[0];
dp[1] = max(nums[0], nums[1]);
if(n == 2) return dp[1];
for(int i = 2; i < n; ++i) {
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
}
return max(dp[n - 1], dp[n - 2]);
}
};
9.1.3. 单词拆分
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
int n = s.length();
vector<bool> dp(n + 1, false);
dp[0] = true;
for(int i = 1; i < n + 1; ++i) {
for(string& str : wordDict) {
int l = str.length();
if(i >= l && s.substr(i - l, l) == str) {
dp[i] = dp[i] || dp[i - l];
}
}
}
return dp[n];
}
};
9.1.4. 乘积最大子数组
一个dp维护最小,一个dp维护最大
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n = nums.size();
vector<int> dpmax(n, 1);
vector<int> dpmin(n, 1);
dpmax[0] = dpmin[0] = nums[0];
int ans = dpmax[0];
for(int i = 1; i < n; ++i) {
dpmax[i] = max(max(dpmax[i - 1] * nums[i], dpmin[i - 1] * nums[i]), nums[i]);
dpmin[i] = min(min(dpmax[i - 1] * nums[i], dpmin[i - 1] * nums[i]), nums[i]);
ans = max(ans, dpmax[i]);
}
return ans;
}
};
9.2. 二维dp
9.2.1. 买卖股票最佳时机
遍历一遍,边更新当前最小值边更新结果(非dp)
class Solution {
public:
int maxProfit(vector<int>& prices) {
int maxV = 0;
int minV = 1e5;
for(int i = 0; i < prices.size(); ++i) {
maxV = max(maxV, prices[i] - minV);
minV = min(minV, prices[i]);
}
return maxV;
}
};
9.2.2. 买卖股票的最佳时机II
122. 买卖股票的最佳时机 II - 力扣(LeetCode)
dp[i][1]表示持有第i天股票,dp[i][0]表示第i天不持有股票
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(2, 0));
dp[0][0] = 0;
dp[0][1] = -prices[0];
for(int i = 1; i < n; ++i) {
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return max(dp[n - 1][0], dp[n - 1][1]);
}
};
9.2.3. 杨辉三角
感觉也不算是dp
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> res;
res.push_back({1});
for(int i = 1; i < numRows; ++i) {
vector<int> ans(i + 1, 0);
ans[0] = 1;
ans[i] = 1;
for(int j = 1; j < i; ++j) {
ans[j] = res[i - 1][j - 1] + res[i - 1][j];
}
res.push_back(ans);
}
return res;
}
};
9.3. 两个字符串匹配
9.3.1. 🌈编辑距离
dp两个维度都是长度 + 1
class Solution {
public:
int minDistance(string word1, string word2) {
int len1 = word1.length();
int len2 = word2.length();
vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));
dp[0][0] = 0;
for(int i = 0; i < len1 + 1; i++) {
for(int j = 0; j < len2 + 1; j++) {
if(i == 0 && j == 0) continue;
else if(i == 0) dp[i][j] = dp[i][j - 1] + 1;
else if(j == 0) dp[i][j] = dp[i - 1][j] + 1;
else if(word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
else {
dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
}
}
}
return dp[len1][len2];
}
};
9.4. 区间DP
9.4.1. 最长回文子串
先枚举区间从小到大,注意从for l 2->n
注意区间往里转移的时候考虑区间不合法的情况
class Solution {
public:
string longestPalindrome(string s) {
int n = s.length();
int ans = 1;
string res = string(1, s[0]);
vector<vector<bool>> dp(n, vector<bool>(n, false));
for(int i = 0; i < n; ++i) dp[i][i] = true;
for(int l = 2; l <= n; ++l) {
for(int i = 0; i <= n - l; ++i) {
if(i + 1 > i + l - 2) dp[i][i + l - 1] = (s[i] == s[i + l - 1]) ? true : false;
else dp[i][i + l - 1] = (s[i] == s[i + l - 1]) ? dp[i + 1][i + l - 2] : false;
if(dp[i][i + l - 1]) {
if(l > ans) {
res = s.substr(i, l);
ans = l;
}
}
}
}
return res;
}
};
9.5. 背包问题
按背包类型分类:
- 0/1背包问题:每个元素最多选取一次
- 完全背包问题:每个元素可以重复选择
- 组合背包问题:背包中的物品要考虑顺序
- 分组背包问题:不止一个背包,需要遍历每个背包
按背包问题分类:
- 最值问题:要求最大值/最小值
- 存在问题:是否存在满足某条件
- 组合问题:求所有满足某条件的排列组合
解题模板代码
下面是求最值问题的模板:
void resolve()
{
vector<int> dp(bagWeight + 1, 0);
for (int i = 0; i < weight.size(); i++)// 遍历物品
for (int j = bagWeight; j >= weight[i]; j--)// 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); //不取或者取第i个
}
背包类型分类解题模板
- 0/1背包:外循环nums,内循环target,target倒序且target>=nums[i]
- 完全背包:外循环nums,内循环target,target正序且target>=nums[i]
- 组合背包:外循环target,内循环nums,target正序且target>=nums[i]
- 分组背包:这个比较特殊,需要三重循环:外循环背包bags,内部两层循环根据题目的要求转化为1,2,3三种背包类型的模板
问题类型分类题解
- 最值问题: dp[i] = max/min(dp[i], dp[i-nums]+1)或dp[i] = max/min(dp[i], dp[i-num]+nums);
- 存在问题(bool):dp[i]=dp[i]||dp[i-num];
- 组合问题:dp[i]+=dp[i-num];
9.5.1. 零钱兑换(完全背包最值问题)
给定amount,求用任意数量不同面值的零钱换到amount所用的最少数量
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<long> dp(amount + 1, 1e7);
long ans = 1e7;
dp[0] = 0;
for(int coin : coins) {
for(int j = 0; j < amount + 1; ++j) {
if(j >= coin) {
dp[j] = min(dp[j], dp[j - coin] + 1);
}
}
}
return dp[amount] < 1e7 ? dp[amount] : -1;
}
};
9.5.2. 零钱兑换II(完全背包不考虑顺序的组合问题)
任选硬币凑成指定金额,求组合总数
class Solution {
public:
int change(int amount, vector<int>& coins) {
vector<long> dp(amount + 1, 0);
dp[0] = 1;
for(int coin : coins) {
for(int i = 0; i < amount + 1; ++i) {
if(i >= coin) {
dp[i] += dp[i - coin];
}
}
}
return dp[amount];
}
};
9.5.3. 完全平方数(完全背包的最值问题)
对于一个正整数n,找出若干个完全平方数使其和为n,返回完全平方数最少数量
class Solution {
public:
int numSquares(int n) {
long ans = 1e7;
vector<long> dp(n + 1, 1e7);
dp[0] = 0;
for(int i = 1; i <= sqrt(n); ++i) {
for(int j = 1; j < n + 1; ++j) {
if(j >= i*i) dp[j] = min(dp[j], dp[j - i*i] + 1);
}
}
return dp[n];
}
};
9.5.4. 目标和
为了转化为一般01背包问题(选和不选):让它只选正数
正数x,负数y
要满足x + y = sum, x - y = target
则x = (sum + target) / 2
转化为只选出正数的那些,01背包target = x
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum = accumulate(nums.begin(), nums.end(), 0);
if((sum + target) % 2 || abs(sum) < abs(target)) return 0;
int s = (sum + target) >> 1;
vector<int> dp(s + 1, 0);
dp[0] = 1;
for(int n : nums) {
for(int i = s; i >= 0; --i) {
if(i >= n) {
dp[i] += dp[i - n];
}
}
}
return dp[s];
}
};
9.5.5. 分割等和子集
01背包只能外层for遍历物品
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = accumulate(nums.begin(), nums.end(), 0);
if(sum % 2) return false;
sum /= 2;
vector<bool> dp(sum + 1, false);
dp[0] = true;
for(int j = 0; j < nums.size(); ++j) {
for(int i = sum; i > -1; --i) {
if(i >= nums[j]) {
dp[i] = dp[i] || dp[i - nums[j]];
}
}
}
return dp[sum];
}
};
10. 搜索
10.1. 组合/子集/排列
10.1.1. 🌈子集
抽象为树搜索的形状,可以想到每次都是在树的最底下(同一层叶子节点)统计答案
所以在idx == nums.size()统计答案
class Solution {
public:
vector<vector<int>> ans;
void dfs(int idx, vector<int>& tmp, vector<int>& nums) {
if(idx == nums.size()) {
ans.push_back(tmp);
return;
}
tmp.push_back(nums[idx]);
dfs(idx + 1, tmp, nums);
tmp.pop_back();
dfs(idx + 1, tmp, nums);
}
vector<vector<int>> subsets(vector<int>& nums) {
vector<int> tmp;
dfs(0, tmp, nums);
return ans;
}
};
10.1.2. 子集II
子集II含有重复的元素
区分一下什么是在同一层去重,什么是在不同层去重
同一层去重的话先排序 nums[i] == nums[i - 1] 并且 vis[i - 1] == false
注意控制减枝尺度(出现重复元素那个分枝)
class Solution {
public:
vector<vector<int>> ans;
void dfs(int idx, vector<bool>& vis, vector<int>& tmp, vector<int>& nums) {
if(nums.size() == idx) {
ans.push_back(tmp);
return;
}
dfs(idx + 1, vis, tmp, nums);
if(idx > 0 && nums[idx] == nums[idx - 1] && vis[idx - 1] == false) {
return;
}
tmp.push_back(nums[idx]);
vis[idx] = true;
dfs(idx + 1, vis, tmp, nums);
vis[idx] = false;
tmp.pop_back();
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<bool> vis(nums.size(), false);
vector<int> tmp;
dfs(0, vis, tmp, nums);
return ans;
}
};
10.1.3. 🌈组合
在搜索树中表示的是通过idx来进行层序的(右伸树)
class Solution {
public:
vector<vector<int>> ans;
int nn;
int kk;
void dfs(int idx, int size, vector<int>& tmp) {
if(size == kk) {
ans.push_back(tmp);
return;
}
for(int i = idx + 1; i < nn; ++i) {
tmp.push_back(i + 1);
dfs(i, size + 1, tmp);
tmp.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
nn = n;
kk = k;
vector<int> tmp;
dfs(-1, 0, tmp);
return ans;
}
};
10.1.4. 🌈全排列
每层节点数相同的搜索树
class Solution {
public:
vector<vector<int>> ans;
void dfs(vector<int>& nums, vector<bool>& vis, vector<int>& tmp) {
if(tmp.size() == nums.size()) {
ans.push_back(tmp);
return;
}
for(int i = 0; i < nums.size(); ++i) {
if(!vis[i]) {
vis[i] = true;
tmp.push_back(nums[i]);
dfs(nums, vis, tmp);
tmp.pop_back();
vis[i] = false;
}
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<int> tmp;
vector<bool> vis(nums.size(), 0);
dfs(nums, vis, tmp);
return ans;
}
};
10.1.5. 全排列II
相比于全排列I只是加了同一层的去重
class Solution {
public:
vector<vector<int>> ans;
void dfs(vector<int>& nums, vector<bool>& vis, vector<int>& tmp) {
if(nums.size() == tmp.size()) {
ans.push_back(tmp);
return;
}
for(int i = 0; i < nums.size(); ++i) {
if(i > 0 && nums[i] == nums[i - 1] && vis[i - 1] == false) {
continue;
}
if(!vis[i]) {
vis[i] = true;
tmp.push_back(nums[i]);
dfs(nums, vis, tmp);
tmp.pop_back();
vis[i] = false;
}
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<int> tmp;
vector<bool> vis(nums.size(), false);
dfs(nums, vis, tmp);
return ans;
}
};
10.2. 图DFS/BFS
10.2.1. 岛屿数量 图DFS
class Solution {
public:
int coor[5] = {0, 1, 0, -1, 0};
void dfs(int i, int j, vector<vector<char>>& grid, vector<vector<bool>>& vis) {
vis[i][j] = true;
for(int k = 0; k < 4; ++k) {
int ii = i + coor[k];
int jj = j + coor[k + 1];
if(ii < 0 || ii >= grid.size() || jj < 0 || jj >= grid[0].size() || vis[ii][jj] || grid[ii][jj] == '0') continue;
dfs(ii, jj, grid, vis);
}
}
int numIslands(vector<vector<char>>& grid) {
vector<vector<bool>> vis(grid.size(), vector<bool>(grid[0].size(), false));
int cnt = 0;
for(int i = 0; i < grid.size(); ++i) {
for(int j = 0; j < grid[0].size(); ++j) {
if(!vis[i][j] && grid[i][j] == '1') {
dfs(i, j, grid, vis);
cnt++;
}
}
}
return cnt;
}
};
10.2.2. 单词搜索 图DFS
class Solution {
public:
bool f = false;
int coor[5] = {0, 1, 0, -1, 0};
void dfs(vector<vector<char>>& board, string word, int i, int j, vector<vector<bool>>& vis, int idx) {
if(idx == word.length()) {
f = true;
return;
}
for(int k = 0; k < 4; ++k) {
int ii = i + coor[k];
int jj = j + coor[k + 1];
if(ii < 0 || ii >= board.size() || jj < 0 || jj >= board[0].size() || vis[ii][jj] || board[ii][jj] != word[idx]) continue;
vis[ii][jj] = true;
dfs(board, word, ii, jj, vis, idx + 1);
vis[ii][jj] = false;
}
}
bool exist(vector<vector<char>>& board, string word) {
vector<vector<bool>> vis(board.size(), vector<bool>(board[0].size(), false));
for(int i = 0; i < board.size(); ++i) {
for(int j = 0; j < board[0].size(); ++j) {
if(board[i][j] == word[0]) {
vis[i][j] = true;
dfs(board, word, i, j, vis, 1);
vis[i][j] = false;
}
}
}
return f;
}
};
10.2.3. 岛屿的最大面积 图DFS
这道题主要学到如何dfs存结果,用返回值
class Solution {
public:
int coor[5] = {0, 1, 0, -1, 0};
int dfs(vector<vector<int>>& grid, vector<vector<bool>> &vis, int i, int j) {
int ans = 1;
for(int k = 0; k < 4; ++k) {
int ii = i + coor[k];
int jj = j + coor[k + 1];
if(ii < 0 || ii >= grid.size() || jj < 0 || jj >= grid[0].size() || vis[ii][jj] || grid[ii][jj] == 0) continue;
vis[ii][jj] = true;
ans += dfs(grid, vis, ii, jj);
}
return ans;
}
int maxAreaOfIsland(vector<vector<int>>& grid) {
int ans = 0;
vector<vector<bool>> vis(grid.size(), vector<bool>(grid[0].size(), false));
for(int i = 0 ;i < grid.size(); ++i) {
for(int j = 0;j < grid[0].size(); ++j) {
if(vis[i][j] || grid[i][j] == 0) continue;
vis[i][j] = true;
ans = max(ans, dfs(grid, vis, i, j));
}
}
return ans;
}
};
10.2.4. 腐烂的橘子 图BFS
如何判断是否最后还有没有新鲜的:fresh变量—,是否最后减到0
cnt一开始初始化的是-1
如果fresh一开始就是0,直接return 0
class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
int coor[5] = {0, 1, 0, -1, 0};
queue<pair<int, int>> q;
int fresh = 0;
for(int i = 0; i < grid.size(); ++i) {
for(int j = 0; j < grid[0].size(); ++j) {
if(grid[i][j] == 2) {
q.push({i, j});
} else if(grid[i][j] == 1) {
fresh++;
}
}
}
if(fresh == 0) return 0;
int cnt = -1;
while(!q.empty()) {
int qsize = q.size();
for(int k = 0; k < qsize; ++k) {
auto pp = q.front();
q.pop();
int i = pp.first;
int j = pp.second;
for(int t = 0; t < 4; ++t) {
int ii = i + coor[t];
int jj = j + coor[t + 1];
if(ii < 0 || ii >= grid.size() || jj < 0 || jj >= grid[0].size() || grid[ii][jj] != 1) continue;
grid[ii][jj] = 2;
fresh--;
q.push({ii, jj});
}
}
cnt++;
}
return (fresh > 0) ? -1 : cnt;
}
};
10.3. 图的存储
10.3.1. 课程表表
一个map记录指向某个节点入度的链表,一个cnt数组代表每个节点的入度
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
unordered_map<int, vector<int>> hash;
vector<int> cnt(numCourses, 0);
for(auto p : prerequisites) {
hash[p[1]].push_back(p[0]);
cnt[p[0]]++;
}
queue<int> q;
for(int i = 0; i < numCourses; ++i) {
if(cnt[i] == 0) {
q.push(i);
}
}
while(!q.empty()) {
int qsize = q.size();
for(int i = 0; i < qsize; ++i) {
auto p = q.front();
q.pop();
for(auto pp : hash[p]) {
if(--cnt[pp] == 0) {
q.push(pp);
}
}
}
}
bool f = true;
for(int i = 0; i < numCourses; ++i) {
if(cnt[i] != 0) f = false;
}
return f;
}
};
11. 贪心
11.1. 贪心算法
11.1.1. 跳跃游戏
注意:if(ans < i) return false;
class Solution {
public:
bool canJump(vector<int>& nums) {
int ans = 0;
for(int i = 0; i < nums.size(); ++i) {
if(ans < i) return false;
ans = max(ans, i + nums[i]);
}
return ans >= nums.size() - 1;
}
};
11.1.2. 跳跃游戏II
其实是dp
class Solution {
public:
int jump(vector<int>& nums) {
int n = nums.size();
vector<int> dp(nums.size(), 1e4);
dp[0] = 0;
for(int i = 1; i < n; i++) {
for(int j = 0; j < i; ++j) {
if(j + nums[j] >= i) {
dp[i] = min(dp[j] + 1, dp[i]);
}
}
}
return dp[n - 1];
}
};
11.1.3. 划分字母区间
遍历的过程不断更新nextIdx即可
class Solution {
public:
vector<int> partitionLabels(string s) {
int n = s.length();
unordered_map<char, int> hash;
for(int i = 0; i < n; ++i) {
char c = s[i];
hash[c] = i;
}
vector<int> ans;
int len = 0;
int nextIdx = 0;
for(int i = 0 ; i < n; ++i) {
nextIdx = max(nextIdx, hash[s[i]]);
len++;
if(i == nextIdx) {
ans.push_back(len);
len = 0;
}
}
return ans;
}
};
12. 模拟
12.1. 二维矩阵模拟
12.1.1. 矩阵置零
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
vector<bool> col(m, false);
vector<bool> row(n, false);
for(int i = 0; i < m; ++i) {
for(int j = 0; j < n; ++j) {
if(matrix[i][j] == 0) {
col[i] = true;
row[j] = true;
}
}
}
for(int i = 0; i < m; ++i) {
for(int j = 0; j < n; ++j) {
if(col[i] || row[j]) matrix[i][j] = 0;
}
}
}
};
12.1.2. 螺旋矩阵
上下左右边界限制
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
int t = 0;
int b = m - 1;
int l = 0;
int r = n - 1;
vector<int> res;
while(l <= r && t <= b) {
for(int i = l; i <= r; ++i) {
res.push_back(matrix[t][i]);
}
if(++t > b) break;
for(int i = t; i <= b; ++i) {
res.push_back(matrix[i][r]);
}
if(--r < l) break;
for(int i = r; i >= l; --i) {
res.push_back(matrix[b][i]);
}
if(--b < t) break;
for(int i = b; i >= t; --i) {
res.push_back(matrix[i][l]);
}
if(++l > r) break;
}
return res;
}
};
12.1.3. 旋转图像
注意边界
第二个循环是for(int j = i; i + j < n - 1; ++j)
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
for(int i = 0; i < n / 2; ++i) {
for(int j = i; i + j < n - 1; ++j) {
int t = matrix[i][j];
matrix[i][j] = matrix[n - j - 1][i];
matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1];
matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1];
matrix[j][n - i - 1] = t;
}
}
}
};
12.1.4. 搜索二维矩阵II
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m = matrix.size();
int n = matrix[0].size();
int i = m - 1;
int j = 0;
bool f = false;
while(i > -1 && j < n) {
if(matrix[i][j] > target) {
i--;
} else if(matrix[i][j] < target) {
j++;
} else {
f = true;
break;
}
}
return f;
}
};