-
区域和检索(题目编号:307,中等,link,2022.1.6)
- 一种想法,前缀和数组,再用map额外记录哪些位置的元素发生了变化以及变化前后的差值。测试阶段,遍历map中的key(位置下标),如果出现在求和区间,就对区间初始和作调整。结果正确,但是会超时,原因是需要多次遍历map,如果map元素变多,耗时将大幅增加。
- 使用线段树,把更新和查找区间和的耗时都降为log(N)。线段树数组的长度得是原数组长度的4倍,为什么呢?怎么计算?
class NumArray {
public:
vector<int> arr, tree;
int size;
NumArray(vector<int>& nums) {
size = nums.size();
arr = nums;
tree = vector<int>(nums.size()*4); //这里的最大容量容易出错
build_tree(arr, tree, 0, 0, size - 1);
}
void build_tree(vector<int>& arr, vector<int>& tree, int node, int start, int end) {
if(start == end){
tree[node] = arr[start];
}
else{
int mid = (start + end) / 2;
int left_node = 2 * node + 1;
int right_node = 2 * node + 2;
build_tree(arr, tree, left_node, start, mid);
build_tree(arr, tree, right_node, mid + 1, end);
tree[node] = tree[left_node] + tree[right_node];
}
}
void update(int index, int val) {
update_tree(arr, tree, 0, 0, size - 1, index, val);
}
void update_tree(vector<int>& arr, vector<int>& tree, int node, int start, int end, int index, int val){
if(start == end){
arr[index] = val;
tree[node] = val;
}
else{
int mid = (start + end) / 2;
int left_node = 2 * node + 1;
int right_node = 2 * node + 2;
if(start <= index && mid >= index){
update_tree(arr, tree, left_node, start, mid, index, val);
}
else{
update_tree(arr, tree, right_node, mid + 1, end, index, val);
}
tree[node] = tree[left_node] + tree[right_node];
}
}
int sumRange(int left, int right) {
return query_tree(arr, tree, 0, 0, size - 1, left, right);
}
int query_tree(vector<int>& arr, vector<int>& tree, int node, int start, int end, int L, int R){
if(L > end || R < start){
return 0;
}
else if(start == end){
return tree[node];
}
else if(L <= start && end <= R){
return tree[node];
}
else{
int mid = (start + end) / 2;
int left_node = 2 * node + 1;
int right_node = 2 * node + 2;
int sum_left = query_tree(arr, tree, left_node, start, mid, L, R);
int sum_right = query_tree(arr, tree, right_node, mid+1, end, L, R);
return sum_left + sum_right;
}
}
};
/**
* Your NumArray object will be instantiated and called as such:
* NumArray* obj = new NumArray(nums);
* obj->update(index,val);
* int param_2 = obj->sumRange(left,right);
*/
-
二叉搜索树中的插入(题目编号:701,中等link,2022.1.7)
- 如果当前节点的值小于要插入的值,就往右子树寻找合适的插入位置,否则往左子树找。如果对应子树是空的,那就直接构建一个新的节点,插入完成。
/**
* 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:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if(!root) return new TreeNode(val);
TreeNode* pos = root;
while(pos){
if(pos->val > val){
if(pos->left == nullptr){
pos->left = new TreeNode(val);
break;
}
else{
pos = pos->left;
}
}
else{
if(pos->right == nullptr){
pos->right = new TreeNode(val);
break;
}
else{
pos = pos->right;
}
}
}
return root;
}
};
-
合并K个升序链表(题目编号23:困难,link,2022.1.7)
- 可选择分治法,每次两两合并,每次链表数量减半,单个链表的长度加倍。
- 注意容易错的点,合并两个单链表时,先创建一个dummy节点(而不是节点指针,会导致超时错误),再用另一个指针指向该dummy节点。
/**
* 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) {
return mergeKListsFunc(lists, 0, lists.size() - 1);
}
ListNode* mergeKListsFunc(vector<ListNode*>& lists, int start, int end) {
if(start == end){
return lists[start];
}
if(start > end) return nullptr;
int mid = (start + end) >> 1;
return merge2Lists(mergeKListsFunc(lists, start, mid), mergeKListsFunc(lists, mid + 1, end));
}
ListNode* merge2Lists(ListNode* L1, ListNode* L2) {
if(!L1 && L2) return L2;
if(L1 && !L2) return L1;
ListNode dummy;
ListNode *node = &dummy;
while(L1 && L2){
if(L1->val < L2->val){
node->next = L1;
L1 = L1->next;
}
else{
node->next = L2;
L2 = L2->next;
}
node = node->next;
}
if(L1){
node->next = L1;
}
if(L2){
node->next = L2;
}
return 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:
struct Status {
int val;
ListNode *ptr;
bool operator<(const Status &rhs) const {
return val > rhs.val;
}
};
priority_queue<Status> q;
ListNode* mergeKLists(vector<ListNode*>& lists) {
for(auto node: lists){
if(node) q.push({node->val, node});
}
ListNode head, *tail = &head;
while(!q.empty()){
auto f = q.top();q.pop();
tail->next = f.ptr;
tail = tail->next;
if(f.ptr->next) q.push({f.ptr->next->val, f.ptr->next});
}
return head.next;
}
};
-
消灭怪物的最大数量(题目编号1921:中等,link,2022.1.10)
- 本来考虑使用map记录(到达城堡的时间,怪物的数量),思路应该是正确的,实现起来有点bug。题解是把到达城堡的时间放到vector里然后排序,如果耗时小于下标,就表示该怪物无法在到达城堡前消灭。
class Solution {
public:
int eliminateMaximum(vector<int>& dist, vector<int>& speed) {
vector<int> time;
int n = dist.size();
for(int i = 0; i < n; ++i){
time.push_back((dist[i] - 1) / speed[i]);
}
sort(time.begin(), time.end());
for(int i = 0; i < n; ++i){
if(time[i] < i){
return i;
}
}
return n;
}
};
-
搜索二维矩阵(题目编号:74,中等,link,2022.1.11)
- 两次二分搜索,第一次对第一列搜索,目标是找到小于target的最靠下的行,第二次对那一行进行搜索,目标是判断target是否在这一行。时间复杂度log(row)+log(col)。
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int r = matrix.size();
if(r == 0) return false;
int c = matrix[0].size();
if(c == 0) return false;
int lo = 0, hi = r - 1, row = 0;
while(lo <= hi) {
int mid = (lo + hi) / 2;
int cur = matrix[mid][0];
if(cur == target) {
return true;
}
else if(cur > target) {
hi = mid - 1;
}
else{
row = mid;
lo = mid + 1;
}
}
lo = 1, hi = c - 1;
while(lo <= hi){
int mid = (lo + hi) / 2;
if(matrix[row][mid] == target) {
return true;
}
else if(matrix[row][mid] < target) {
lo = mid + 1;
}
else {
hi = mid - 1;
}
}
return false;
}
};
-
任务调度器(题目编号621:中等,link,2022.1.16)
- 按填充的思路来做,首先找到出现任务最多的任务,均匀散开,间距n,接下来往n个空格填充,可能有多个任务的频次相同,依次填充出现频次最高,接着从倒数第二行开始,填充任务次多的。需要区分两种情况,填充时是否把n个空位填满了,如果填满了,那么相同任务之间的间距就大于n了,不需要填充空位,此时需要的总时间为总任务书,如果没有填满,n个格子剩余的位置就处于待机状态。官方题解讲的比较好,link。
class Solution {
public:
int leastInterval(vector<char>& tasks, int n) {
vector<int> taskCount(26);
for(char c: tasks) {
++taskCount[c - 'A'];
}
int maxCount = 0, numTask = 0;
for(int i = 0; i < 26; ++i) {
if(taskCount[i] > maxCount) {
maxCount = taskCount[i];
numTask = 1;
}
else if(taskCount[i] == maxCount) {
++numTask;
}
}
return max(int((maxCount - 1) * (n + 1) + numTask), int(tasks.size()));
}
};
-
二叉树最大宽度(题目编号662:link,中等,2022.1.18)
- 最开始想了一种笨方法,遍历所有的节点,把null也放进去,结果应该是对的,但是浪费了很多push,pop的时间,导致超时。
/**
* 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 widthOfBinaryTree(TreeNode* root) {
if(!root) return 0;
queue<TreeNode*> q;
q.push(root);
int ans = 1, cur_len = 1, tmp_ans = 1;
while(tmp_ans > 0) {
//tmp_len表示上一层次弹出队列的节点数
//tmp_ans表示当前层次计算得到的答案,从第一个非null节点到最后一个非null节点
//tmp_null记录从上一个非null节点到现在经历了多少了null
//tmp_node记录当前层次放入队列的节点数
//cur_len表示上一层次总共放入队列的节点数
tmp_ans = 0;
int tmp_len = 0, tmp_null = 0, tmp_node = 0;
while(tmp_len < cur_len) {
TreeNode* n = q.front();
q.pop();
if(tmp_ans == 0){
if(n){
tmp_ans = 1;
}
}
else{
if(n){
tmp_ans += tmp_null + 1;
tmp_null = 0;
}
else{
++tmp_null;
}
}
if(n){
q.push(n->left);
q.push(n->right);
}
else{
q.push(nullptr);
q.push(nullptr);
}
++tmp_len;
tmp_node += 2;
}
cur_len = tmp_node;
ans = max(ans, tmp_ans);
}
return ans;
}
};
- 其实可以在层次遍历的基础上,跟之前一样,只push非空节点,此外再把非空节点在所在层次的位置信息放到队列里,这样就不用通过遍历每个null和非null节点来统计最大宽度。最痛苦的是数值溢出问题,存放的位置坐标很快就超过了long存储的范围。策略:每行从最左边的非空节点开始,重新计算下标,忽略左边全null的元素。
- long全改成int也可以通过,因为题目保证答案不会超过int型。
/**
* 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 widthOfBinaryTree(TreeNode* root) {
if(!root) return 0;
queue<TreeNode*> q_node;
queue<long> q_pos;
q_node.push(root);
q_pos.push(1);
long ans = 1;
while(!q_node.empty()) {
int sz = q_node.size();
long left = q_pos.front();
while(sz--) {
TreeNode* n = q_node.front();
long cur_pos = q_pos.front();
q_node.pop();
q_pos.pop();
ans = max(ans, cur_pos - left + 1);
if(n->left){
q_node.push(n->left);
q_pos.push(2 * (cur_pos - left + 1) - 1);
}
if(n->right){
q_node.push(n->right);
q_pos.push(2 * (cur_pos - left + 1));
}
}
}
return ans;
}
};
-
实现前缀树(题目编号:剑指Offer II 062,link,中等,2022.1.19)
class Trie {
private:
vector<Trie*> children;
bool isEnd;
Trie* searchPrefix(string prefix) {
Trie* node = this;
for(char c: prefix) {
int pos = c - 'a';
if(node->children[pos] != nullptr) {
node = node->children[pos];
}
else{
return nullptr;
}
}
return node;
}
public:
/** Initialize your data structure here. */
Trie(): children(26), isEnd(false) {
}
/** Inserts a word into the trie. */
void insert(string word) {
Trie* node = this;
for(char c: word) {
int pos = c - 'a';
if(node->children[pos] == nullptr){
node->children[pos] = new Trie();
}
node = node->children[pos];
}
node->isEnd = true;
}
/** Returns if the word is in the trie. */
bool search(string word) {
Trie* node = searchPrefix(word);
return node != nullptr && node->isEnd;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
bool startsWith(string prefix) {
Trie* node = searchPrefix(prefix);
return node != nullptr;
}
};
/**
* 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);
*/
-
从链表中删去总和值为零的连续节点(题目编号1171:link,中等,2022.1.20)
- 区间和为0,那么就该想到用前缀和数组,如果两个位置i和j处的前缀和相等,那么i+1~j的区间和就是0,可以删掉。
- 用map记录前缀和和对应的节点,如果前缀和重复出现,那么就需要把中间这段链表断开,此外还需要把中间这段链表对应的前缀和从map中删掉。
- 另一种方法,两次遍历,第一次用map记录每个前缀和最后一次出现的链表节点,第二次遍历,每次都把前缀和第一次和最后一次出现的中间区间从链表中断开。
- 链表注意前边要加一个dummy节点,指向head,避免head被删掉。
- 注意map删除元素是erase函数,不是remove。
//第一种解法,一次遍历,但是需要回过头从map中删掉元素
/**
* 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* removeZeroSumSublists(ListNode* head) {
map<int, ListNode*> mp;
ListNode* dummy = new ListNode(-1);
dummy->next = head;
ListNode* p = head;
int preSum = 0;
mp[0] = dummy;
while(p) {
preSum += p->val;
if(mp.find(preSum) != mp.end()) {
ListNode* node = mp[preSum];
ListNode* del = node->next;
node->next = p->next;
int dSum = preSum;
while(del != p) {
dSum += del->val;
mp.erase(dSum);
del = del->next;
}
}
else{
mp[preSum] = p;
}
p = p->next;
}
return 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* removeZeroSumSublists(ListNode* head) {
ListNode* dummy = new ListNode(-1, head);
ListNode* p = head;
int preSum = 0;
map<int, ListNode*> mp;
mp[0] = dummy;
while(p) {
preSum += p->val;
mp[preSum] = p;
p = p->next;
}
p = dummy;
preSum = 0;
while(p) {
p->next = mp[preSum]->next;
p = p->next;
if(p) {
preSum += p->val;
}
else{
break;
}
}
return dummy->next;
}
};
-
重新排列后的最大子矩阵(题目编号1727:link,中等,2022.1.27)
- 本题跟之前的一些题目很类似,解决思路是统计每个位置往上看连续的1的数量得到一个新的矩阵up。接下来,对每一行及进行递增排序,对(i,j)位置来说,j+1列开始的右边的列都至少有up[i][j]个连续的1,这时候可以更新最大矩阵面积了。题目不难,可作为面试题。
class Solution {
public:
int largestSubmatrix(vector<vector<int>>& matrix) {
int rows = matrix.size(), cols = matrix[0].size();
vector<vector<int>> up(rows, vector<int>(cols));
for(int i = 0; i < rows; ++i){
for(int j = 0; j < cols; ++j) {
if(matrix[i][j] == 1){
if(i == 0){
up[i][j] = 1;
}
else{
up[i][j] = matrix[i][j] + up[i - 1][j];
}
}
}
}
int ans = 0;
for(int i = 0; i < rows; ++i){
sort(up[i].begin(), up[i].end());
for(int j = 0; j < cols; ++j){
ans = max(ans, up[i][j] * (cols - j));
}
}
return ans;
}
};
-
重排链表(题目编号:剑指Offer II 026,link,中等,2022.1.27)
- 用快慢指针法找到链表中间节点,然后把后半部分翻转,然后再合并两个链表。
/**
* 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:
void reorderList(ListNode* head) {
ListNode* dummy = new ListNode();
dummy->next = head;
ListNode *slow = dummy, *fast = dummy;
while(fast && fast->next){
slow = slow->next;
fast = fast->next->next;
}
ListNode* tail = reverseList(slow->next);
slow->next = nullptr;
slow = head;
while(slow && tail){
ListNode* next1 = slow->next;
ListNode* next2 = tail->next;
slow->next = tail;
tail->next = next1;
slow = next1;
tail = next2;
}
}
ListNode* reverseList(ListNode* head) {
if(!head) return nullptr;
ListNode *pre, *cur, *next;
pre = nullptr;
cur = head;
while(cur) {
next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
};
-
K次调整数组大小浪费的最小总空间(题目编号1950:link,中等,2022.1.27)
- 解法是动态规划,要从n个数分k段递推到n+1个数分k+1段。预先统计i~j区间分为一段的成本,然后遍历i0从0到i,选择从哪个地方开始作为最后一段。
- 此题难度中等,可作为面试题,需要一些思路分析才能做出来。
class Solution {
public:
int minSpaceWastedKResizing(vector<int>& nums, int k) {
int n = nums.size();
vector<vector<int>> dp(n, vector<int>(k+2, INT_MAX / 2));
vector<vector<int>> g(n, vector<int>(n));
for(int i = 0; i < n; ++i){
int max_val = INT_MIN;
int sum = 0;
for(int j = i; j < n; ++j){
max_val = max(max_val, nums[j]);
sum += nums[j];
g[i][j] = max_val * (j - i + 1) - sum;
}
}
for(int i = 0; i < n; ++i){
for(int j = 1; j <= k+1; ++j){
for(int i0 = 0; i0 <= i; ++i0){
dp[i][j] = min(dp[i][j], (i0 == 0? 0: dp[i0 - 1][j - 1]) + g[i0][i]);
}
}
}
return dp[n - 1][k + 1];
}
};
-
描述绘画结果(题目编号:1943,link,中等,2022.2.21)
- 难度中等,但是也比较巧妙,记录关键位置处颜色的增减量。
- 由于LeetCode网站出问题,无法提交测试。待验证代码。
- 需后面再自己写一遍
class Solution {
public:
vector<vector<long long>> splitPainting(vector<vector<int>>& segments) {
unordered_map<int, long long> ump;
for(auto&& v: segments) {
int l = v[0], r = v[1], c = v[2];
if(ump.count(l) == 0){
ump[l] = 0;
}
ump[l] += c;
if(ump.count(r) == 0){
ump[r] = 0;
}
ump[r] -= c;
}
vector<pair<int, long long>> vp;
for(auto&& [k, v]: ump) {
vp.emplace_back(k, v);
}
sort(vp.begin(), vp.end());
vector<vector<long long>> ans;
int n = vp.size();
for(int i = 0; i < n; ++i) {
if(vp[i].second) {
ans.emplace_back(vector<long long> {vp[i].first, vp[i+1].first, vp[i].second});
}
}
return ans;
}
};
-