2022/7/22
52 LRU缓存
考查知识点: 设计题 LRU LinkedHashMap HashMap DLinkedList
思路1: 使用哈希表+双向链表 (推荐)
难度: hard 没有思路
难点在于 如果插入关键字导致缓存超出,要找到最长时间没有使用过的。
get和put时间复杂度是O(1)
struct Node{
int key;
int value;
Node* prev;
Node* next;
Node(): key(0), value(0), prev(nullptr), next(nullptr){}
Node(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr){}
};
class LRUCache {
private:
Node *dummyHead, *dummyTail;
unordered_map<int , Node*> map;
int capacity;
int cur_size;
public:
LRUCache(int capacity): capacity(capacity) { // 形参和类变量重名了
dummyTail=new Node();
dummyHead= new Node();
dummyHead->next=dummyTail;
dummyTail->prev=dummyHead;
cur_size=0;
}
int get(int key) {
if(!map.count(key)){
return -1;
}
Node* node=map[key];
moveToHead(node);
return node->value;
}
void put(int key, int value) {
if(!map.count(key)){
Node* node = new Node(key, value);
map[key]=node;
if(cur_size==capacity){
Node* tail = removeTail();
map.erase(tail->key);
delete tail;
--cur_size;
}
addToHead(node);
++cur_size;
return;
}
Node* node = map[key];
node->value=value;
moveToHead(node);
return;
}
void addToHead(Node* node){
dummyHead->next->prev=node;
node->next=dummyHead->next;
node->prev=dummyHead;
dummyHead->next=node;
}
void moveToHead(Node* node){
removeNode(node);
addToHead(node);
}
Node* removeNode(Node* node){ // 逻辑删除节点,一定要返回这个节点。
node->next->prev=node->prev;
node->prev->next=node->next;
return node;
}
Node* removeTail(){
if(dummyTail->prev==dummyHead){
return nullptr;
}
Node* node = dummyTail->prev;
return removeNode(node);
}
};
53 排序链表
考查: 链表 排序
思路1: 插入排序
思路2:快排
思路3: 堆排
思路4:(推荐)时间复杂度在nlogn级别的排序算法有快排,归并,堆排。其中快排用一个vector,
堆排用priority_queue这样的中间容器也可以实现排序。但是空间复杂度在O(N)。
所以面试官最想看到的算法是归并。 如果空间复杂度要求在O(1)。 无疑是自底向上的归并排序。
如果空间复杂度要求在O(logN), 可以是自顶向下的归并排序。
综上,采用自底向上的归并排序最佳。 否则回家等通知
class Solution {
public:
ListNode* sortList(ListNode* head) {
if(!head||!head->next) return head;
ListNode* p=head;
int len=0;
while(p){
++len;
p=p->next;
}
int sublength=1;
ListNode* dummyHead=new ListNode();
dummyHead->next=head;
for(int sublength=1;sublength<len;sublength*=2){ // 枚举每轮归并排序
ListNode* prev = dummyHead;
ListNode* cur=dummyHead->next;
while(cur){ // 遍历整个链表
ListNode* head1 = cur;
for(int i=1;i<sublength&&cur->next!=nullptr;++i){ // 找到第一个子链表尾结点
cur=cur->next;
}
ListNode* head2=cur->next;
cur->next=nullptr;
cur=head2;
if(head2){ // 第二个链表如果存在, 找到第二个子链表尾结点
for(int i=1;i<sublength&&cur->next!=nullptr;++i){
cur=cur->next;
}
}
ListNode* next=nullptr;// 如果第二个子链表不存在,next=nullptr.否则就是第二个子链表尾结点的后继
if(head2){
next=cur->next;
cur->next=NULL;
}
prev->next = merge(head1, head2);
while(prev->next!=nullptr){
prev=prev->next;
} // prev指向每轮归并排序已经处理好的子链表最后一个节点。
cur=next;
}
}
return dummyHead->next;
}
ListNode* merge(ListNode* head1, ListNode* head2){
ListNode *p1=head1, *p2=head2;
ListNode* dummyHead = new ListNode();
ListNode* tail=dummyHead;
while(p1&&p2){
if(p1->val<p2->val){
tail->next=p1;
tail=p1;
p1=p1->next;
}else{
tail->next=p2;
tail=p2;
p2=p2->next;
}
}
if(p1){
tail->next=p1;
}
if(p2){
tail->next=p2;
}
ListNode* p = dummyHead->next;
return dummyHead->next;
}
};
54 乘积最大子数组
题意: 求乘积最大的子数组
考查: dp
思路1: dp
状态: dp[i]表示以第i个数字结尾的最大乘积
状态转移方程: 我的考虑是这样的,如果当前数字是正数, 那么前面如果有偶数个负数,那么dp[i]可以是
从数组开头到当前元素的成绩。 如果前面有奇数个负数,那么dp[i]可以是从第一个负数之后到当前元素的乘积。
如果当前数字是负数,那么前面如果有偶数个负数,那么从第一个负数之后到当前元素乘积。如果前面有
奇数的负数,那么dp[i]可以是从开头到当前元素的乘积。
上述讨论时,没有考虑到0。如果出现,可以想象成0以及0左边的数组删去之后留下来的数组作为新数组。
思路2: 一开始,我猜测 dp[i] = max(nums[i], nums[i]*dp[i-1])。 但是这是错的。当nums[i]是负数。
会出现错误。 之后一直想不出来,看了题解才知道,维护了两个dp数组, 一个存最大乘积,一个存最小乘积。
正确的状态转移方程: max_dp[i] = max(nums[i], nums[i]*max_dp[i-1], nums[i]*min_dp[i-1])。
min_dp[i] = min(nums[i], nums[i]*max_dp[i-1], nums[i]*min_dp[i-1])。 (推荐)
难度: meduim
// 思路1实现
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n=nums.size();
if(n==0) return 0;
if(n==1) return nums[0];
vector<int> dp(n, 0);
dp[0]=nums[0];
int negative_first=-1;
int negative=0;
int prefix1=1;
int prefix2=1;
bool flag=false;
if(nums[0]!=0){
if(nums[0]<0){
negative=1;
flag=true;
}
prefix1*=nums[0];
}
for(int i=1;i<n;++i){
if(nums[i]==0){
dp[i]=0;
negative=0;
prefix1=1; // 从开始到i-1的乘积
prefix2=1; // 从第一个负数之后到i-1的乘积
flag=false;
}else if(nums[i]>0){ // 正数
if(negative%2==0){
dp[i]=nums[i]*prefix1;
}else{
dp[i]=nums[i]*prefix2;
}
prefix1*=nums[i];
if(flag) prefix2*=nums[i];
}else{ // 负数
if(negative%2==0){
dp[i]=nums[i]*prefix2;
}else{
dp[i]=nums[i]*prefix1;
}
if(flag) prefix2*=nums[i];
flag=true;
prefix1*=nums[i];
++negative;
}
}
int ans=INT_MIN;
for(int i=0;i<n;++i){
ans = max(dp[i], ans);
}
return ans;
}
};
2022/7/23
55 最小栈
题意: 实现一个最小栈, 包含getMin()函数, 每次能返回栈中最小数。
思路1: 借助一个辅助栈, 这个栈栈顶存放当前数据栈中最小值。 官方有点trick,可以学学。 (推荐)
扩展: 实现最小队列 [有难度]
难度: medium
// 我实现的代码
class MinStack {
public:
MinStack() {
}
void push(int val) {
stk.push(val);
if(min_stk.empty()){
min_stk.push(val);
}else{
min_stk.push(min(min_stk.top(), val));
}
}
void pop() {
stk.pop();
min_stk.pop();
}
int top() {
return stk.top();
}
int getMin() {
return min_stk.top();
}
private:
stack<int> stk;
stack<int> min_stk;
};
// 官方
class MinStack {
public:
MinStack() {
min_stk.push(INT_MAX);
}
void push(int val) {
stk.push(val);
min_stk.push(min(min_stk.top(), val));
}
void pop() {
stk.pop();
min_stk.pop();
}
int top() {
return stk.top();
}
int getMin() {
return min_stk.top();
}
private:
stack<int> stk;
stack<int> min_stk;
};
56 相交链表
考查: 链表 双指针
思路1: 快慢指针 (推荐)
难度: easy
// 思路1
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(!headA||!headB) return nullptr;
ListNode *p1=headA, *p2=headB;
int len1=0, len2=0;
while(p1){
++len1;
p1=p1->next;
}
while(p2){
++len2;
p2=p2->next;
}
p1=headA, p2=headB;
if(len1<len2){
swap(p1, p2);
swap(len1, len2);
}
int k = len1-len2;
while(k--){
p1=p1->next;
}
while(p1&&p2&&p1!=p2){
p1=p1->next;
p2=p2->next;
}
if(p1&&p1==p2) return p1;
return nullptr;
}
};
57 多数元素
考查: 数组 多数元素 脑筋急转弯 抵消思想
思路1: 因为多数元素在数组中出现次数最多,在数量上占有优势,按照抵消的思想,对两个不同的元素进行
相消。遇到两个相同的元素, 暂时保留下来。 这样数组最后剩余的肯定是多数元素。
比如[2,3,3,1,4,5,4,4,4,4,4]。
第一次 消去2,3
第二次 消去3 1
第三次消去 4 5
第四次 4和4 暂时保留。。。。最终剩下来4
这种方法叫做摩尔投票法 (推荐)
class Solution {
public:
int majorityElement(vector<int>& nums) {
// 抵消思想
int cur=INT_MIN; // cur表示当前的候选者, 值为INT_MIN, 表示当前候选者为空
int cnt=0; // 表示投当前这个候选者的票数
for(int i=0;i<nums.size();++i){
if(cur==INT_MIN){
cur=nums[i];
++cnt;
}else if(nums[i]==cur){
++cnt;
}else{
--cnt;
if(cnt==0) cur=INT_MIN;
}
}
return cur;
}
};
58 打家劫舍
考查: 回溯 dp
思路1: 回溯
思路2: 线性dp dp[i]表示第i个屋子结尾的最大money。
思路3: 线性dp dp[i]表示从0到i个屋子的最大money (推荐)
// 思路1
class Solution {
public:
int rob(vector<int>& nums) {
int n=nums.size();
if(n==0) return 0;
int cur_money=0;
for(int i=0;i<n;++i){
dfs(nums,i, cur_money, n );
}
return ans;
}
void dfs(vector<int> &nums, int pos, int cur_money, int n){
cur_money+=nums[pos];
if(pos+2>=n){ // 判断当前状态是否是叶子结点
ans=max(ans, cur_money);
return;
}
for(int i=pos+2;i<n;++i){ // 枚举可能的下一状态
dfs(nums, i, cur_money, n);
}
}
private:
int ans=0;
};
// 思路2
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
if(n==0) return 0;
if(n==1) return nums[0];
vector<int> dp(n, 0);
dp[0]=nums[0]; // dp[i] 以偷第i个屋子结尾的最大值
dp[1]=nums[1];
int cur_max=nums[0]; // 记录[0, i-2]前缀最大值
for(int i=2;i<n;++i){
dp[i]=nums[i]+cur_max;
cur_max=max(cur_max, dp[i-1]);
}
int ans=0;
ans=max(dp[n-1], dp[n-2]);
return ans;
}
};
// 思路3
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
if(n==0) return 0;
if(n==1) return nums[0];
vector<int> dp(n, 0);
dp[0]=nums[0];
dp[1]=max(dp[0], nums[1]);
for(int i=2;i<n;++i){
dp[i]=max(dp[i-1], dp[i-2]+nums[i]);
}
return dp[n-1];
}
};
59 岛屿数量
考查: DFS
难度: easy
思路1: 每次进入的都是正确且合法状态
思路2: 不管合不合法,进入下一个可能状态。 在执行具体操作前,先判断合不合法。
60 反转链表
考查: 链表 头插法
难度: easy
61 课程表
题意: 有向图判环
考查: 拓扑排序
思路1: BFS版本 (推荐)
思路2: DFS版本 (推荐)
难度: medium
// BFS版本
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
int n = numCourses;
if(n<=1) return true;
vector<int> inDegree(n, 0);
vector<vector<int> >G(n+1, vector<int>(n+1, 0));
for(int i=0;i<prerequisites.size();++i){
int cur=prerequisites[i][0];
int prev=prerequisites[i][1];
G[prev][cur]=1;
inDegree[cur]++;
}
queue<int> q; // 只保存入度为0的顶点
for(int i=0;i<n;++i){
if(inDegree[i]==0){
q.push(i);
}
}
int cnt=0;
while(!q.empty()){
int cur = q.front();
q.pop();
++cnt;
for(int i=0;i<n;++i){
if(G[cur][i]==1){
inDegree[i]--;
if(inDegree[i]==0){
q.push(i);
}
}
}
}
if(cnt<n) return false;
return true;
}
};
// DFS版本
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
visit.resize(numCourses, 0);
vector<vector<int> >edges(numCourses); // 邻接表
for(int i=0;i<prerequisites.size();++i){
int cur = prerequisites[i][0];
int prev=prerequisites[i][1];
edges[prev].push_back(cur);
}
flag=true;
for(int i=0;i<numCourses;++i){
if(flag&&visit[i]==0){
dfs(i,edges); // 判断当前连通块有无环
}
}
return flag;
}
void dfs(int cur, vector<vector<int> > &edges){ // 每次访问的节点一定是未访问过的
if(!flag) return;
visit[cur]=1;
for(int i=0;i<edges[cur].size();++i){
int next=edges[cur][i];
if(visit[next]==0){
dfs(next, edges);
}else if(visit[next]==1){ // 如果正在访问节点的邻点是在栈中。 那么一定存在环
flag=false; return;
}
}
visit[cur]=2; // 已经访问完
}
private:
vector<int> visit; // 0表示未访问 1表示在栈中 2表示访问完
bool flag;
};
62 实现Trie树
考查: 字典树
思路: 要形成自己的代码风格, 记忆 (推荐)
难度: medium
struct TrieNode{
bool isLeaf;
TrieNode* children[26];
TrieNode(): isLeaf(false), children{nullptr}{}
};
class Trie {
private:
TrieNode *root;
public:
Trie() {
root = new TrieNode();
}
void insert(string word) {
TrieNode* p=root;
for(auto &ch: word){
int i=ch-'a';
if(p->children[i]==nullptr){
p->children[i] = new TrieNode();
}
p=p->children[i];
}
p->isLeaf=true;
return;
}
bool search(string word) {
TrieNode* p=root;
for(auto &ch:word){
int i=ch-'a';
if(p->children[i]==nullptr) return false;
p=p->children[i];
}
return p->isLeaf;
}
bool startsWith(string prefix) {
TrieNode* p=root;
for(auto &ch:prefix){
int i=ch-'a';
if(p->children[i]==nullptr) return false;
p=p->children[i];
}
return true;
}
};
63 数组中第K个最大元素
考查: 快排 堆排
思路1: 快排 (推荐)
思路2: 堆排 自己实现一个大顶堆 (推荐)
主要操作包括:
建堆: 静态建堆, 此时数组已经固定下来。 动态建堆, 实际上就是插入操作
调整: 向上调整 向下调整 堆特有的操作,为了保持堆的性质
插入: 堆尾插入新元素
删除: 删除堆顶元素
在很多语言中,都有优先队列或者堆的的容器可以直接使用,但是在面试中,面试官更倾向于让面试者自己实现一个堆。所以建议读者掌握这里大根堆的实现方法,在这道题中尤其要搞懂「建堆」、「调整」和「删除」的过程。
友情提醒:「堆排」在很多大公司的面试中都很常见,不了解的同学建议参考《算法导论》或者大家的数据结构教材,一定要学会这个知识点哦!_
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int n = nums.size();
k = n-k; // 第k大, 就是第n-k+1小
quickSort(nums, 0, n-1,k);
return nums[k];
}
void quickSort(vector<int>&nums, int ll, int rr, int k){
if(ll>=rr) return;
int low=ll, high=rr;
int idx = rand()%(rr+1-ll)+ll; // 从ll...rr中选取一个随机数作为枢轴
swap(nums[low], nums[idx]);
int pivot=nums[low];
while(low<high){
while(low<high&&nums[high]>=pivot) --high; // 至少有一个>=, 否则震荡
nums[low]=nums[high];
while(low<high&&nums[low]<pivot) ++low;
nums[high]=nums[low];
}
int pos=low;
nums[pos]=pivot;
if(pos==k) return;
if(pos<k) quickSort(nums, pos+1, rr, k);
if(pos>k) quickSort(nums, ll, pos-1, k);
return;
}
};
/*
时间复杂度: O(NlogN)
空间复杂度: O(logN)
这里采用从ll...rr中选取一个随机数, 耗时从96ms下降到64ms.
*/
64 最大正方形*
考查: 矩阵 最值
思路1: 借鉴最大矩形
运行效率低, 想想没有更好的方法。
// 思路1实现
class Solution {
public:
int maximalSquare(vector<vector<char>>& matrix) {
int m=matrix.size(), n=matrix[0].size();
vector<vector<int> > heights(m+1, vector<int>(n+1, 0));
for(int i=0;i<m;++i){
for(int j=0;j<n;++j){
if(i==0) heights[i][j]=matrix[i][j]-'0';
else{
if(matrix[i][j]!='0'){
heights[i][j]=heights[i-1][j]+1;
}
}
}
}
int max_area=0;
for(int i=0;i<m;++i){
for(int mid=0;mid<n;++mid){
int cur_height=heights[i][mid];
if(cur_height==0) continue;
int left=mid, right=mid;
while(left-1>=0&&heights[i][left-1]>=cur_height) left=left-1;
while(right+1<=n-1&&heights[i][right+1]>=cur_height) right=right+1;
int width = right+1-left;
if(width>=cur_height){
int cur_area=cur_height*cur_height;
max_area=max(max_area, cur_area);
}
}
}
return max_area;
}
};
65 翻转二叉树
考查: 树 树的遍历
思路1: 先序,后序都可以
难度: easy
扩展: 非递归怎么写
// 思路1实现
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root==nullptr) return nullptr;
swap(root->left,root->right);
invertTree(root->left);
invertTree(root->right);
return root;
}
};
66 回文链表
考查: 链表 链表分割
思路1: 用一个数组存储链表中的值, 之后双指针判断是否是回文串。
此题可以练习 递归
// 思路1
class Solution {
public:
bool isPalindrome(ListNode* head) {
vector<int> res;
ListNode *p=head;
while(p){
res.push_back(p->val);
p=p->next;
}
int i=0,j=res.size()-1;
while(i<j){
if(res[i]!=res[j]) return false;
++i;
--j;
}
return true;
}
};
67 二叉树的最近公共祖先
考查: 树 树的遍历 递归
思路1:比较low的思路, 先找到root--->p的路径,再找到root--->q的路径。之后找两个路径中的相交点。
思路2: 用哈希表存储每个节点的父节点。
思路3: 递归 (推荐)
// 思路2实现
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(!root||!p||!q) return nullptr;
dfs(root);
hash[root]=nullptr;
if(!hash.count(p)||!hash.count(q)) return nullptr;
TreeNode* cur=p;
while(cur){
visit[cur]=true;
cur=hash[cur];
}
cur=q;
while(cur){
if(visit.count(cur)) return cur;
cur=hash[cur];
}
return nullptr;
}
void dfs(TreeNode* root){
if(root==nullptr) return;
if(root->left){
hash[root->left]=root;
dfs(root->left);
}
if(root->right){
hash[root->right]=root;
dfs(root->right);
}
}
private:
unordered_map<TreeNode*, TreeNode*> hash;
unordered_map<TreeNode*, bool> visit;
};
68 除自身以外数组的乘积
考查: 前缀积 左右扫描
思路1: 左右扫描
// 思路1实现
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n=nums.size();
vector<int> left(n, 1);
vector<int> right(n,1);
for(int i=1;i<n;++i){
left[i]=left[i-1]*nums[i-1];
}
for(int i=n-2;i>=0;--i){
right[i]=right[i+1]*nums[i+1];
}
vector<int> ans;
for(int i=0;i<n;++i){
ans.push_back(left[i]*right[i]);
}
return ans;
}
};
69滑动窗口最大值
考查: 滑动窗口
思路1: 使用单调递减队列实现。 注意自己的风格 (推荐)
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n=nums.size();
q.resize(n+1);
vector<int> ans;
for(int i=0;i<n;++i){
if(hh<=tt&&i+1-q[hh]>k) ++hh;
while(hh<=tt&&nums[q[tt]]<=nums[i]) --tt;
q[++tt]=i;
if(i+1>=k) ans.push_back(nums[q[hh]]);
}
return ans;
}
private:
vector<int> q;
int hh=0, tt=-1;
};
2022/7/24
70 搜索二维矩阵II
考查: 二分查找
思路1: 按行进行二分查找
思路2: Z字型查找 (推荐)
由于至多走N+M步, 所以时间复杂度在O(M+N)
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m=matrix.size();
for(int i=0;i<m;++i){
int pos = binarySearch(matrix[i], 0, matrix[i].size()-1, target);
if(pos!=-1) return true;
}
return false;
}
int binarySearch(vector<int> &nums, int left, int right, int x){
if(left>right) return -1;
while(left<=right){
int mid = (left+right)/2;
if(nums[mid]==x){
return mid;
}else if(nums[mid]>x){
right=mid-1;
}else if(nums[mid]<x){
left=mid+1;
}
}
return -1;
}
};
// 思路2实现
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m=matrix.size(), n=matrix[0].size();
int x=m-1, y=0;
while(x>=0&&y<=n-1){ // Z字型搜索, 每次排除一行或者一列
if(matrix[x][y]==target) return true;
else if(matrix[x][y]>target){
--x;
}else if(matrix[x][y]<target){
++y;
}
}
return false;
}
};
71 会议室 II
需要会员
// 思路1
/*
假设当前不重叠的区间集合为S, 遍历整个数组, 向这个S中加入区间,要去这个区间和这个集合不重叠。
计数器加1. 一轮迭代之后,如果当前数组中还有未处理的区间,那么重复上述步骤,否则程序结束。
*/
class Solution {
public:
static bool cmp(const vector<int> &a, const vector<int> &b){
if(a[1]!=b[1]) return a[1]<b[1];
else return a[0]<b[0];
}
int minMeetingRooms(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end(), cmp);
int n=intervals.size();
vector<bool> visit(n+10, false);
int cnt=0;
for(int i=0;i<n;++i){
if(!visit[i]){
++cnt;
int curId=i;
visit[curId]=true;
for(int j=i+1;j<n;++j){
if(intervals[curId][0]<=intervals[j][1]&&intervals[j][0]<=intervals[curId][1]) continue;
curId=j;
visit[curId]=true;
}
}
}
return cnt;
}
};
72 完全平方数
没有思路
// 超时版
class Solution {
public:
int numSquares(int n) {
vector<int> dp(n+10, 0);
dp[1]=1;dp[2]=2;dp[3]=3; dp[4]=1;
for(int i=1;i*i<=n;++i){
dp[i*i]=1;
}
for(int i=5;i<=n;++i){
if(dp[i]==1) continue;
int _min=INT_MAX;
for(int j=i-1;j>=1;--j){
_min=min(_min, dp[j]+dp[i-j]);
if(dp[j]==1&&i%j==0) break; // 这句是猜测后加上的。
}
dp[i]=_min;
}
return dp[n];
}
};
/*
输入8831, 执行用时是388ms
*/
class Solution {
public:
int numSquares(int n) {
vector<int> dp(n+10, 0);
dp[1]=1;
for(int i=2;i<=n;++i){
int _min=INT_MAX;
for(int j=1;j<=sqrt(i);++j){
_min = min(_min, dp[i-j*j]+1);
}
dp[i]=_min;
}
return dp[n];
}
};
/*
输入8831, 执行用时4ms
*/
73 移动零
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int numPos=0; // 将非零数放好的下一个位置
int n=nums.size();
for(int i=0;i<n;++i){
if(nums[i]) nums[numPos++]=nums[i];
}
for(int i=numPos;i<n;++i){
nums[i]=0;
}
return;
}
};
2022/7/25
74 寻找重复数
没有思路
想到之前做过的多数元素, 只出现一次的数字。 对这道题没啥用。
又想到通用解法使用哈希表存储每个值出现的次数, 但是本题要求空间复杂度是O(1)
看了题解,发现技巧性极强。 之前居然做过类似思想的题。 如swap(0, *)。
就是将数组看出一个有向图。
如果一个数组不重复并且在下标范围内,那么这个有向图由自环,环组成。
如果一个数组重复并且在下标范围内,那么这个有向图可以由自环, 环, 含环的连通块(至少是弱连通块,
可以是单向连通块,强连通块)。
如果一个数组数字超出下标范围, 那么这个有向图可以由自环, 环, 含环的连通块, 不含环的连通块组成。
// trick题
class Solution {
public:
int findDuplicate(vector<int>& nums) {
// 1...n
int n=nums.size();
int slow=0, fast=0;
while(slow==0||slow!=fast){
slow=nums[slow];
fast=nums[nums[fast]];
}
slow=0;
while(slow!=fast){
slow=nums[slow];
fast=nums[fast];
}
return slow;
}
};
75 二叉树的序列化与反序列化
考查: 树 树的遍历 字符串
思路1: 之前实现过一次
// 自己实现的版本
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
string ans="";
if(root==nullptr) return ans;
queue<TreeNode*> q;
q.push(root);
while(!q.empty()){
int _size=q.size();
for(int i=0;i<_size;++i){
TreeNode* cur=q.front();
q.pop();
if(cur!=root) ans+=",";
if(cur==nullptr){
ans+="#";
}else{
ans+=to_string(cur->val);
q.push(cur->left);
q.push(cur->right);
}
}
}
int pos=ans.size()-1;
while(pos>=0&&(ans[pos]==','||ans[pos]=='#')) --pos;
ans = ans.substr(0, pos+1);
return ans;
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
if(data=="") return nullptr;
string str;
int lchild=0;
queue<TreeNode*> q;
TreeNode *root, *cur, *fa;
bool first=true;
for(auto &ch: data){
if(ch==','){
if(!first) fa = q.front();
cur=nullptr;
if(str!="#"){
int num = 1;
cur=new TreeNode(stoi(str));
q.push(cur);
if(first){
root=cur;
}
}
str="";
if(first){
first=false; continue;
}
if(lchild==0){
fa->left=cur;
++lchild;
}else{
fa->right=cur;
lchild=0;
q.pop();
}
continue;
}
str+=ch;
}
cur = new TreeNode(stoi(str));
if(first) return cur;
fa = q.front();
if(lchild==0){
fa->left=cur;
}else{
fa->right=cur;
}
return root;
}
};
76 最长递增子序列 (需要优化)
无思路 之前做过一道和子序列相关的题, 求s中能覆盖t的子串
思路1: dp 无优化 没想到过了。。。。。
思路2: 重新定了一个dp状态, 加上二分查找 (推荐)
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n= nums.size();
if(n<=1) return n;
vector<int> dp(n+10, 0);
dp[0]=1;
int ans=1;
for(int i=1;i<n;++i){
int _max=0;
for(int j=0;j<i;++j){
if(nums[j]<nums[i]&&dp[j]>_max){
_max=dp[j];
}
}
dp[i]=_max+1;
ans=max(ans, dp[i]);
}
return ans;
}
};
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n=nums.size();
if(n<=1) return n;
vector<int> dp(n+1, 0);
dp[1]=nums[0];
int len=1;
for(int i=1;i<n;++i){
if(nums[i]>dp[len]){
dp[++len]=nums[i];
}else{
int left=1, right=len+1; // 找到大于或者等于nums[i]的第一个元素位置
while(left<right){
int mid=(left+right)/2;
if(dp[mid]>nums[i]){
right=mid;
}else if(dp[mid]<nums[i]){
left=mid+1;
}else if(dp[mid]==nums[i]){
right=mid;
}
}
if(dp[left]>nums[i]){ // 如[0,1] 0这种就不需要更新
dp[left]=nums[i];
}
}
}
return len;
}
};
77 删除无效括号
无思路
思路1: 看到数据规模n<=25, 直接DFS暴力搜索 超时!!!
// DFS
class Solution {
public:
vector<string> removeInvalidParentheses(string s) {
int n=s.size();
if(n==0) return ans;
string cur;
dfs(s,cur, 0, n );
return ans;
}
void dfs(string &s, string cur, int pos, int &n){
if(pos==n){
if(isValid(cur)){
if(ans.empty()){
ans.push_back(cur);
}else if(cur.size()>ans[0].size()){
ans.clear();
ans.push_back(cur);
}else if(cur.size()==ans[0].size()){
for(int i=0;i<ans.size();++i){
if(ans[i]==cur) return;
}
ans.push_back(cur);
}
}
return;
}
if(s[pos]<='z'&&s[pos]>='a'){
dfs(s, cur+s[pos], pos+1, n);
}else{
dfs(s,cur, pos+1, n);
dfs(s, cur+s[pos], pos+1, n);
}
}
bool isValid(string str){
stack<char> stk;
for(int i=0;i<str.size();++i){
if(str[i]=='('){
stk.push('(');
}else if(str[i]==')'){
while(!stk.empty()&&stk.top()!='('&&stk.top()!=')') stk.pop();
if(!stk.empty()&&stk.top()=='('){
stk.pop();
}else{
return false;
}
}
}
return stk.empty();
}
private:
vector<string> ans;
};
// 写于2022/7/28
class Solution {
public:
vector<string> removeInvalidParentheses(string s) {
int len=s.size();
int lremove=0,rremove=0;
for(int i=0;i<len;++i){
if(s[i]=='('){
++lremove;
}else if(s[i]==')'){
if(lremove!=0) --lremove;
else ++rremove;
}
}
dfs(s, lremove, rremove, 0);
return ans;
};
void dfs(string s, int lremove, int rremove, int start){
if(lremove+rremove==0){
if(isValid(s)&&!dict.count(s)){
dict[s]=1;
ans.push_back(s);
}
return ;
}
if(lremove){
for(int i=start;i<s.size();++i){ // 这里i将0改为start 优化1
if(i!=0&&s[i]==s[i-1]) continue; // 优化2 防止这种 (((((()
if(s[i]=='('){
string next = s.substr(0, i)+s.substr(i+1);
dfs(next, lremove-1, rremove, i);
}
}
}
if(rremove){
for(int i=start;i<s.size();++i){
if(i!=0&&s[i]==s[i-1]) continue;
if(s[i]==')'){
string next = s.substr(0, i)+s.substr(i+1);
dfs(next, lremove, rremove-1, i);
}
}
}
}
bool isValid(string s){
int cnt=0;
for(int i=0;i<s.size();++i){
if(s[i]=='(') ++cnt;
else if(s[i]==')'){
--cnt;
if(cnt<0) return false;
}
}
return cnt==0;
}
private:
unordered_map<string, int> dict;
vector<string> ans;
};
78 最佳买卖股票时机含冷冻期
class Solution {
public:
int maxProfit(vector<int>& prices) {
// dp[i] days from 0 to i to get maximum profit
// dp[i] = max{ dp[i-1], max[0<=j<i-1&&prices[j]<prices[i]] {dp[j-2]+prices[i]-prices[j]} }
int n =prices.size();
if(n<=1) return 0;
vector<int> dp(n+10, 0);
for(int i=1;i<n;++i){
int _max=0;
for(int j=0;j<i;++j){
if(prices[j]<prices[i]){
int cur = prices[i]-prices[j]+ (j-2>=0?dp[j-2]:0);
if(cur>_max) _max=cur;
}
}
dp[i]=max(dp[i-1],_max);
}
return dp[n-1];
}
};
2022/7/26
79 戳气球
没有思路
80 零钱兑换
题意: 背包问题
思路1: dp 自己实现
// 思路1
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<int> dp(amount+1, -1); // dp[i]表示凑成i元所需的最少硬币数
dp[0]=0;
int n=coins.size(); // dp[i] = min [0<=j<i] dp[i-coins[j]]+1
for(int i=1;i<=amount;++i){
int _min=INT_MAX;
for(int j=0;j<n;++j){
if(i-coins[j]>=0&&dp[i-coins[j]]!=-1){
int cur = dp[i-coins[j]]+1;
_min=min(_min, cur);
}
}
if(_min!=INT_MAX) dp[i]=_min;
}
return dp[amount];
}
};
81 打家劫舍III
一开始觉得自己有思路, dp[cur]表示从根到当前结点抢到的最高金额,
dp[cur] = max(cur->val+dp[cur's grandfather], dp[cur' father])。 但是提交答案后,发现自己计算的实际上是从
根到叶子结点路径上可以获得去的最高金额。 但实际上整个一棵树都可以偷窃, 而不是仅仅其中一条路径。
如果对叶子结点累加,会重复计算。
82 比特位计数
思路1: 右移操作, 居然过了
在c++中 ~取反操作对包含符号位在内的32个位数都取反。 这点和书上不一致。
假设a=128。 取反之后是-129。 如果要获取a的相反数, ~a+1
class Solution {
public:
vector<int> countBits(int n) {
vector<int> ans;
for(int i=0;i<=n;++i){
int cnt=0;
int vv=i;
for(int j=0;j<=31;++j){
if(vv&1){
++cnt;
}
vv=vv>>1;
}
ans.push_back(cnt);
}
return ans;
}
};
83 前K个高频元素
struct cmp{
bool operator()(const pair<int,int> &a, const pair<int,int> &b) const{
if(a.second!=b.second) return a.second>b.second; // 这3个const必须要加, 并且这里两个判断句都要写, 否则set会漏元素
return a.first<b.first;
}
};
class Solution {
private:
unordered_map<int, int> cnt;
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
set<pair<int, int>, cmp> st;
for(auto &num: nums){
cnt[num]++;
}
for(auto &num:nums){
st.insert(make_pair(num, cnt[num]));
}
vector<int> ans;
int cur=0;
for(auto &p: st){
++cur;
if(cur>k) break;
ans.push_back(p.first);
}
return ans;
}
};
2022/7/28 写了14道题
84 字符串解码
// 栈
class Solution {
public:
string decodeString(string s) {
string ans;
stack<char> stk;
for(int i=0;i<s.size();++i){
if(stk.empty()){
if(s[i]>='a'&&s[i]<='z'){
ans+=s[i];
continue;
}
stk.push(s[i]);
}else{
if(s[i]==']'){
string tmp;
while(!stk.empty()&&stk.top()>='a'&&stk.top()<='z'){
tmp+=stk.top();
stk.pop();
}
if(!stk.empty()&&stk.top()=='[') stk.pop();
string num;
while(!stk.empty()&&stk.top()>='0'&&stk.top()<='9'){
num+=stk.top();
stk.pop();
}
reverse(tmp.begin(), tmp.end());
reverse(num.begin(), num.end());
int _num=stoi(num);
string tmpStr;
while(_num--){
tmpStr+=tmp;
}
if(stk.empty()){
ans+=tmpStr;
}else{
for(int j=0;j<tmpStr.size();++j){
stk.push(tmpStr[j]);
}
}
}else{
stk.push(s[i]);
}
}
}
return ans;
}
};
85 除法求值
class Solution {
private:
unordered_map<string, string> father;
public:
string findfather(string x){
string a=x;
while(father[x]!=x) x=father[x];
while(a!=father[a]){
string z=a;
a=father[a];
father[z]=x;
}
return x;
}
void _union(string a, string b){
string fa=findfather(a);
string fb=findfather(b);
if(fa!=fb){
father[fa]=fb;
}
}
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
for(int i=0;i<equations.size();++i){
vector<string> cur=equations[i];
father[cur[0]]=cur[0];
father[cur[1]]=cur[1];
}
for(int i=0;i<equations.size();++i){
vector<string> cur = equations[i];
_union(cur[0], cur[1]);
}
unordered_map<string, double> dict;
bool flag=false;
for(int i=0;i<equations.size();++i){ // 多次迭代
flag=false;
for(int j=0;j<equations.size();++j){
vector<string> cur = equations[j];
if(!dict.count(cur[0])){
if(dict.count(cur[1])){
dict[cur[0]]=values[j]*dict[cur[1]];
flag=true;
}
}else{
if(!dict.count(cur[1])){
dict[cur[1]]=dict[cur[0]]/values[j];
flag=true;
}
}
}
if(!flag){ // 没有发生更新, 至多更新一个
bool isEnd=true;
for(int j=0;j<equations.size();++j){
vector<string> cur = equations[j];
if(!dict.count(cur[0])&&!dict.count(cur[1])){
isEnd=false;
dict[cur[1]]=1.0;
dict[cur[0]]=values[j];
break;
}
}
if(isEnd) break;
}
}
vector<double> ans;
for(int i=0;i<queries.size();++i){
string a=queries[i][0];
string b=queries[i][1];
if(!father.count(a)||!father.count(b)){
ans.push_back(-1);
continue;
}
if(findfather(a)!=findfather(b)){ // 如果说两个数没有比例关系, 那么使用并查集
ans.push_back(-1);
continue;
}
ans.push_back(dict[a]/dict[b]);
}
return ans;
}
};
86 根据身高重建队列
智力题
class Solution {
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
vector<vector<int> > ans;
int n=people.size();
if(n==0) return ans;
sort(people.begin(), people.end(), cmp);
vector<int> q(n+1, 0);
int hh=0, tt=-1;
for(int i=0;i<people.size();++i){ // 找到这个元素在当前队列的相对位置
vector<int> cur=people[i]; // cur[1]有范围, 0<=cur[1]<=(tt-hh+1)
if((tt-hh+1)==cur[1]){
q[++tt]=i;
}else{
int remove = tt-hh+1-cur[1]; // 需要搬动的元素个数
int j=tt;
while(remove--){
q[j+1]=q[j];
--j;
}
// q[j]=i;
q[j+1]=i;
++tt;
}
}
for(int i=hh;i<=tt;++i){
ans.push_back(people[q[i]]);
}
return ans;
}
static bool cmp(vector<int> &a, vector<int> &b){
if(a[0]!=b[0]) return a[0]>b[0];
else return a[1]<b[1]; // 不可能存在两对序偶完全一样
}
};
87 分割等和子集
88 路径总和 III
89找到字符串中所有字母异位词
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
int len=p.size();
int pFreq[26]={0}; // 这里必须初始化, 否则会初始化任意数字
int sFreq[26]={0};
int distance=0;
vector<int> ans;
queue<int> q;
for(auto &ch: p){
int i=ch-'a';
pFreq[i]++;
}
for(int i=0;i<s.size();++i){
q.push(i);
int pos=s[i]-'a';
if(sFreq[pos]<pFreq[pos]){
++distance;
}
sFreq[pos]++;
while(distance==len){
int _sz = q.size();
int head = q.front();
q.pop();
if(_sz==len){
ans.push_back(head);
}
pos=s[head]-'a';
sFreq[pos]--;
if(sFreq[pos]<pFreq[pos]){
--distance;
}
}
}
return ans;
}
};
90 找到所有数组中消失的数字
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
// 将数组想象成有向图
vector<int> ans;
int n=nums.size();
if(n<=1) return ans;
for(int i=0;i<n;++i){
int j=i;
if(nums[j]==0) continue;
j=nums[j]-1;
while(nums[j]!=0){
int tmp=nums[j]-1;
nums[j]=0;
j=tmp;
}
}
for(int i=0;i<n;++i){
if(nums[i]!=0){
ans.push_back(i+1);
}
}
return ans;
}
};
91 汉明距离
class Solution {
public:
int hammingDistance(int x, int y) {
// 位运算
int distance=0;
for(int i=0;i<32;++i){
if((x&1)!=(y&1)){
distance++;
}
x>>=1;
y>>=1;
}
return distance;
}
};
92 目标和
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum=0, pos=0;
int n=nums.size();
dfs(nums, sum, target, 0, n);
return ans;
}
void dfs(vector<int> &nums, int sum, int target, int pos, int &n){
if(pos==n){
if(sum==target) ++ans;
return;
}
dfs(nums, sum+nums[pos], target, pos+1, n);
dfs(nums, sum-nums[pos], target, pos+1, n);
}
private:
int ans=0;
};
93 把二叉搜索树转换为累加树
class Solution {
public:
TreeNode* convertBST(TreeNode* root) {
// 利用BST树中序遍历有序这一性质
if(!root) return nullptr;
inOrder(root);
int sum=0;
for(int i=nums.size()-1;i>=0;--i){
TreeNode* cur = nums[i];
sum+=cur->val;
cur->val=sum;
}
return root;
}
void inOrder(TreeNode* cur){
if(!cur) return;
inOrder(cur->left);
nums.push_back(cur);
inOrder(cur->right);
}
private:
vector<TreeNode*> nums;
};
94 二叉树的直径
class Solution {
public:
int diameterOfBinaryTree(TreeNode* root) {
if(!root) return 0;
postOrder(root);
return ans;
}
int postOrder(TreeNode* cur){
if(!cur) return 0;
int left = postOrder(cur->left);
int right=postOrder(cur->right);
ans=max(left+right, ans); // 求出穿过当前结点的最大长度, 并和之前保存的最大值比较
return max(left, right)+1;
}
private:
int ans=INT_MIN;
};
95 和为 K 的子数组
96 最短无序连续子数组
class Solution {
public:
int findUnsortedSubarray(vector<int>& nums) {
int n=nums.size();
vector<int> nums1=nums;
sort(nums.begin(), nums.end());
int left=0;
while(left<n&&nums[left]==nums1[left]) ++left;
if(left==n) return 0;
int right=n-1;
while(right>=0&&nums[right]==nums1[right]) --right;
return right-left+1;
}
};
97 合并二叉树
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if(!root1&&!root2) return nullptr;
if(!root1) return root2;
if(!root2) return root1;
root1->val=root1->val + root2->val;
root1->left=mergeTrees(root1->left, root2->left);
root1->right=mergeTrees(root1->right, root2->right);
return root1;
}
};
98 任务调度器
99 回文子串
class Solution {
public:
int countSubstrings(string s) {
int len=s.size();
vector<vector<bool> > dp(len+1, vector<bool> (len+1, false));
// dp[i][j] = dp[i+1][j-1]&&s[i]==s[j]
for(int curLen=0;curLen<=len;++curLen){
for(int j=0;j<len;++j){
int i=j+1-curLen;
if(i>j||i<0) continue;
if(i+1>j-1){
dp[i][j]=(s[i]==s[j]);
}else{
dp[i][j]=(dp[i+1][j-1])&&(s[i]==s[j]);
}
}
}
int cnt=0;
for(int i=0;i<len;++i){
for(int j=0;j<len;++j){
if(i>j) continue;
if(dp[i][j]){
++cnt;
}
}
}
return cnt;
}
};
100 每日温度
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
int n=temperatures.size();
vector<int> ans(n, 0);
vector<int> stk(n+10, 0);
int hh=0, tt=-1;
for(int i=0;i<n;++i){
while(hh<=tt&&temperatures[stk[tt]]<temperatures[i]){
ans[stk[tt]]=i-stk[tt];
--tt;
}
stk[++tt]=i;
}
return ans;
}
};
没写出来的题目:
312. 戳气球
313. 打家劫舍 III
2022/7/28
315. 分割等和子集
316. 路径总和 III
317. 和为 K 的子数组
318. 任务调度器
2022/7/29 做了五道题
313. 打家劫舍 III 用到之前做过的题的思想
315. 分割等和子集 经典问题
316. 路径总和 III 写的不够优雅
317. 和为 K 的子数组 技巧性很强 这道题看了题解!!
318. 任务调度器 这道题做了一下午, 一直在找bug。 map删除有陷阱
// 313. 打家劫舍 III
class Solution {
public:
int rob(TreeNode* root) {
pair<int,int> cur=dfs(root);
return max(cur.first, cur.second);
}
pair<int, int> dfs(TreeNode* cur){
if(!cur) return make_pair(0, 0);
pair<int, int> left = dfs(cur->left);
pair<int, int>right = dfs(cur->right);
int walk = cur->val + left.second+right.second;
int notwalk = max(left.first, left.second)+max(right.first, right.second);
return make_pair(walk, notwalk);
}
};
//315. 分割等和子集
class Solution {
public:
bool canPartition(vector<int>& nums) {
int n=nums.size();
int sum=0;
for(const auto&x :nums){
sum+=x;
}
if(sum&1) return false;
int target=sum/2;
vector<vector<bool> > dp(n+10, vector<bool>(target, false));
// dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]]
for(int i=0;i<=n;++i){
for(int j=0;j<=target;++j){
if(j==0) dp[i][j]=true;
else{
if(i==0) dp[i][j]=false;
else{
if(j-nums[i-1]<0) dp[i][j]=dp[i-1][j];
else{
dp[i][j]=(dp[i-1][j])||(dp[i-1][j-nums[i-1]]);
}
}
}
}
}
return dp[n][target];
}
};
//316. 路径总和 III
class Solution {
public:
int pathSum(TreeNode* root, int targetSum) {
if(!root) return 0;
dfs(root, targetSum);
return ans;
}
void dfs(TreeNode* cur, int targetSum){
if(!cur) return;
dfs(cur->left, targetSum);
dfs(cur->right, targetSum);
ll cur_sum=0;
preOrder(cur, cur_sum, targetSum);
}
void preOrder(TreeNode* cur, ll cur_sum, int targetSum){
if(!cur) return;
cur_sum=cur_sum+cur->val;
if(cur_sum==targetSum){
++ans;
}
preOrder(cur->left, cur_sum, targetSum);
preOrder(cur->right, cur_sum, targetSum);
}
private:
int ans=0;
};
//317. 和为 K 的子数组
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int cnt=0;
unordered_map<int, int> dict;
dict[0]=1; // 空前缀
int cur_sum=0;
for(int i=0;i<nums.size();++i){ // prefix[i]-k=prefix[j]
cur_sum+=nums[i];
int x=cur_sum-k;
if(dict.count(x)){ // 先问后存
cnt+=dict[x];
}
dict[cur_sum]++;
}
return cnt;
}
};
//318. 任务调度器
class Solution {
public:
struct cmp {
bool operator()(const pair<char, int> &a, const pair<char, int> &b) const{
return a.second<b.second;
}
};
int leastInterval(vector<char>& tasks, int n) {
priority_queue<pair<char, int>, vector<pair<char, int> >, cmp>pq;
map<char, int> dict; // dict存放 每个任务需要完成的次数
for(int i=0;i<tasks.size();++i){
char ch = tasks[i];
dict[ch]++;
}
for(map<char,int>::iterator it=dict.begin();it!=dict.end();++it){
char ch = it->first;
int cnt = it->second;
pq.push(make_pair(ch, cnt));
}
map<char, int> cold; //cold里存放没有完成并且冻结的任务
int cur_time=0;
int cur_num=0;
int _sz = tasks.size();
while(true){
if(!pq.empty()){ // 当前有无可执行的任务
pair<char, int> cur = pq.top();
pq.pop();
dict[cur.first]-=1;
cold[cur.first]=n+1; // 这里赋值n+1是为了统一减去。 初始状态->状态1->状态2->..... 状态1看到n, 状态2看到n-1.
++cur_num; // 第一次状态变化, 如果是n,不应该减1
}
++cur_time;
if(cur_num==_sz) break;
for(map<char, int>::iterator it=cold.begin();it!=cold.end();){ // 更新cold和队列
char task = it->first;
cold[task]-=1;
if(dict[task]==0) cold.erase(it++);
else{
if(cold[task]==0){
pq.push(make_pair(task, dict[task]));
cold.erase(it++);
}
else{
++it;
}
}
}
}
return cur_time;
}
};
2022/7/30
戳气球
// 戳气球
class Solution {
public:
int maxCoins(vector<int>& nums) {
vector<int> nums1;
nums1.push_back(1);
for(int i=0;i<nums.size();++i){
nums1.push_back(nums[i]);
}
nums1.push_back(1);
int n=nums1.size();
vector<vector<int> >dp(n+10, vector<int> (n+10, 0));
for(int len=0;len<=n; ++len){
for(int j=0;j<n;++j){
int i=j+1-len;
if(i>=0&&i+1<j){
int _max=INT_MIN;
for(int k=i+1; k<j;++k){
int cur = dp[i][k]+dp[k][j]+nums1[k]*nums1[i]*nums1[j];
_max = max(_max, cur);
}
if(_max!=INT_MIN) dp[i][j]=_max;
}
}
}
return dp[0][n-1];
}
};
终于结束了。
从2022/7/2 - 2022/7/30。做完了hot 100题。
一刷结束。
前50道题从2022/7/2 - 2022/7/21 用了20天
后50道题从2022/7/22 - 2022/7/30 用了9天
优点:做完了HOT100题。多数题目自己解决的。 有些总结了模版。
从别人的新颖的解决方法和简约有效的代码风格中,给人启发。
缺点:
首先一刷, 掌握的不很牢靠。
同类型的题目没有归类,进一步扩展,进一步深入思考, 进一步练习。
线段树, 记忆化搜索, 简洁的递归写法要去多试试。
有些题目,没有尝试多种方法,或者推荐解法。
最后,戒骄戒躁。 像ACMer, 可能都做了1000-2000道左右的题,甚至更多。你题量和见过的题,敲代码
的熟练水平,思路, 编码知识体系肯定不如的。 所以戒骄戒躁, 才100题量,你能玩出什么花来呢?但是
不要妄自菲薄,觉得自己不行, 保持自信。找工作而已,生活和工作是两件事。
建议:
多刷几遍,提高时间和准确度, 重点在熟练, 知识点就那么多
要尝试多种解法,最佳解法 看到题要立马想到推荐解法,因为面试可能只有10多分钟时间,否则可能回家等通知。
提高自己思路,代码实践能力。
自己尝试口头表达这个题解法思路。 可以看看求s中能覆盖t的最短子串。 官方题解小哥哥表达的言简意赅。
对于重点,难点,常考点, 要集中总结,思考,练习, 突破。 比如二分查找细节, dp类型, 链表的归并排序。 思考也有深度,自己出题。
要尝试新题,扩展思路和视野
场景设计题,要多练练, 是一类题型