hot100 题单
二叉树 16道
层序遍历,左孩子下标 i*2;右孩子下标 i*2+1 (下标从1开始
栈
二叉树的中序遍历 94 简单
左根右
一直向左一直进栈,取栈输出,再向右
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> sk;
while (root || !sk.empty()) {
while (root) {
sk.push(root);
root = root->left;
}
root = sk.top();
sk.pop();
res.push_back(root->val);
root = root->right;
}
return res;
}
};
BFS
二叉树的层序遍历 102
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
if(!root) return {};
vector<vector<int>> res;
queue<TreeNode*> que;
que.push(root);
while(!que.empty()){
int n=que.size(); //这层的大小
vector<int> level;//这层的数据
for(int i=0;i<n;i++){ //遍历这一层
TreeNode* node=que.front();
que.pop();
level.push_back(node->val);
//将下一层结点放入队列
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
res.push_back(level);
}
return res;
}
};
递归
1. 翻转二叉树 226 简单
class Solution {
public:
void dfs(TreeNode* t){
if(!t) return;
dfs(t->left);
dfs(t->right);
TreeNode* l=t->left;
TreeNode* r=t->right;
t->left=r;
t->right=l;
}
TreeNode* invertTree(TreeNode* root) {
dfs(root);
return root;
}
};
2. 合并二叉树 617 简单
class Solution {
public:
TreeNode* dfs(TreeNode* t1,TreeNode* t2){
TreeNode* t=new TreeNode();
if(t1 && t2){
t->val=t1->val+t2->val;
t->left=dfs(t1->left,t2->left);
t->right=dfs(t1->right,t2->right);
return t;
}else if(t1){
return t1;
}else if(t2){
return t2;
}else{
return nullptr;
}
}
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
return dfs(root1,root2);
}
};
3. 二叉树的最大深度 104 简单 略
略
4. 对称二叉树 101 简单
root是轴对称的:root的左子树和右子树是对称的
树 a 和树 b 是对称的:根相等 && a 的左子树和b右子树是对称的 && a的右子树和b的左子树是对称的
class Solution {
public:
bool dfs(TreeNode* l, TreeNode* r) {
if (!l && !r)
return true;
if (!l && r || l && !r)
return false;
if (l->val != r->val) {
return false;
}
return dfs(l->left, r->right) && dfs(l->right, r->left);
}
bool isSymmetric(TreeNode* root) {
if (!root)
return true;
return dfs(root->left, root->right);
}
};
5. 把二叉搜索树转换为累加树 538
右根左
全局变量sum
class Solution {
public:
int sum=0;
void dfs(TreeNode* t){
if(!t){
return;
}
dfs(t->right);
sum+=t->val;
t->val=sum;
dfs(t->left);
}
TreeNode* convertBST(TreeNode* root) {
dfs(root);
return root;
}
};
6. 验证二叉搜索树 98 √
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
保证结点t的值在(low,up)之间
class Solution {
public:
bool dfs(TreeNode* t, long long low, long long up) {
if (!t) return true;
long long x = t->val;
return x < up && x > low && dfs(t->left, low, x)&&dfs(t->right, x, up);
}
bool isValidBST(TreeNode* root) {
return dfs(root, LLONG_MIN, LLONG_MAX);
}
};
7. 二叉树的最近公共祖先 236
先序遍历,目标:找到 p 和 q
class Solution {
public:
TreeNode* dfs(TreeNode* t, TreeNode* p, TreeNode* q) {
if (!t || t == p || t == q) {
return t;
}
TreeNode* l = dfs(t->left, p, q);
TreeNode* r = dfs(t->right, p, q);
if (l && r) { // p,q 在t左右两侧
return t;
}
if (l) { // p q都在左侧
return l;
}
if (r) { // 都在右侧
return r;
}
return nullptr; // 左右都没有
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
return dfs(root, p, q);
}
};
8. 二叉树的直径 543 简单
一条路径的长度 = 该路径经过的节点数 - 1
而任意一条路径均可以被看作由某个节点为起点,从其左儿子和右儿子向下遍历的路径拼接得到。
class Solution {
int ans;//答案经过的结点数
int depth(TreeNode* t){ //求以t为根的树的深度(深度意思是最大深度)
if (t == NULL) {
return 0; // 访问到空节点了,返回0
}
int L = depth(t->left); // 左儿子为根的子树的深度
int R = depth(t->right); // 右儿子为根的子树的深度
ans = max(ans, L + R + 1); // 经过t的结点数量为 左深度+右深度+t本身=L+R+1
return max(L, R) + 1; // 返回t为根的子树的深度
}
public:
int diameterOfBinaryTree(TreeNode* root) {
ans = 0;
depth(root);
return ans - 1;
}
};
9. 路径总和 III 437
根左右 前缀和
class Solution {
public:
unordered_map<long long,int> mp;//前缀和数组,mp[s]表示从根到某结点和为s的路径数量
//sum为从根节点到t的路径value和。需要作为参数传递,返回时sum回退。
int dfs(TreeNode* t,long long sum,int targetSum){
if(!t) return 0;
int res=0; //t子树包含的答案数
sum+=t->val;
if(mp.count(sum - targetSum)){//sum-mp=target,大前缀和-小前缀和 = 一段的和
res += mp[sum-targetSum];
}
mp[sum]++;
res+=dfs(t->left,sum,targetSum);
res+=dfs(t->right,sum,targetSum);
mp[sum]--;//需要回退
return res;
}
int pathSum(TreeNode* root, int targetSum) {
mp[0]=1; //考虑根节点value=tagert的情况
return dfs(root,0,targetSum);
}
};
10. 二叉树展开为链表 114
采用头插法构建链表,也就是从节点 6 开始,在 6 的前面插入 5,在 5 的前面插入 4,依此类推。
为此,要按照 6→5→4→3→2→1 的顺序访问节点,也就是按照右子树 - 左子树 - 根的顺序 DFS 这棵树。
DFS 的同时,记录当前链表的头节点为 head。一开始 head 是空节点。
class Solution {
public:
TreeNode* head=nullptr;
void dfs(TreeNode* t){
if(!t) return;
dfs(t->right);
dfs(t->left);
//头插法
t->right=head;
t->left=nullptr;
head=t;
}
void flatten(TreeNode* root) {
dfs(root);
}
};
11. 从前序与中序遍历序列构造二叉树 105
根左右
左根右
根据前序的根,去中序中定位根的位置,从而能确定左子树的范围。
map: <中序遍历中的数值,在中序遍历中的位置下标> 方便“去中序中定位根的位置”
int preorder_left, int preorder_right, int inorder_left, int inorder_right : 边界位置下标
class Solution {
public:
unordered_map<int, int> mp;
TreeNode* dfs(vector<int>& preorder, vector<int>& inorder,
int preorder_left, int preorder_right,
int inorder_left, int inorder_right) {
if (preorder_left > preorder_right) return nullptr;
int preorder_root = preorder_left; // 根节点在前序遍历中的位置下标
int inorder_root = mp[preorder[preorder_root]]; // 求出根节点在中序遍历中的位置下标
TreeNode* root = new TreeNode(preorder[preorder_root]); // 构建根节点
int size_left_subtree = inorder_root - inorder_left; // 左子树的节点数目
// 算出前序、中序的左、右子树的边界下标
root->left = dfs(preorder, inorder,
preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
root->right = dfs(preorder, inorder,
preorder_left + size_left_subtree + 1,preorder_right, inorder_root + 1, inorder_right);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int n = preorder.size();
// 建立map
for (int i = 0; i < n; i++) {
mp[inorder[i]] = i;
}
return dfs(preorder, inorder, 0, n - 1, 0, n - 1);
}
};
字典树
实现 Trie (前缀树) 208
Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补全和拼写检查。
请你实现 Trie 类:
Trie()初始化前缀树对象。void insert(String word)向前缀树中插入字符串word。boolean search(String word)如果字符串word在前缀树中,返回true(即,在检索之前已经插入);否则,返回false。boolean startsWith(String prefix)如果之前已经插入的字符串word的前缀之一为prefix,返回true;否则,返回false。
解:
class Trie {
public:
vector<Trie*> next;
bool isEnd;
Trie() {
next=vector<Trie*>(26);
isEnd=false;
}
void insert(string word) {
Trie* node=this; // 谁调用谁就是node
for(char c:word){
if(node->next[c-'a']==NULL){//新建一个“叉”
node->next[c-'a']=new Trie();
}
node=node->next[c-'a'];
}
node->isEnd=true;
}
bool search(string word) {
Trie* node=this;
for(char c:word){
if(node->next[c-'a']==NULL){
return false;
}
node=node->next[c-'a'];
}
if(node->isEnd){
return true;
}
return false;
}
bool startsWith(string prefix) {
Trie* node=this;
for(char c:prefix){
if(node->next[c-'a']==NULL){
return false;
}
node=node->next[c-'a'];
}
return true;
}
};
/**
* 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);
*/
位运算 3道
1 汉明距离 461 简单
两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。
给你两个整数 x 和 y,计算并返回它们之间的汉明距离。
示例 1:
输入:x = 1, y = 4
输出:2
解释:
1 (0 0 0 1)
4 (0 1 0 0)
↑ ↑
上面的箭头指出了对应二进制位不同的位置。
异或^
%2:二进制的最后一位
/2 :二进制右移一位
class Solution {
public:
int hammingDistance(int x, int y) {
int ans=0;
int z=x^y;
while(z>0){
if(z%2==1){
ans++;
}
z=z/2;
}
return ans;
}
};
2 比特位计数 338 动态规划
找规律 背诵
1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101
6 - ->110
奇数的个位一定是1,偶数是0
奇数:比前一个数多一个 个位的 1
偶数:比它除以2的数多一个0(因为/2相当于右移一位)
class Solution {
public:
vector<int> countBits(int n) {
vector<int> ans;
vector<int> dp(n+1); //dp[i]表示数字i的1的个数
dp[0]=0;
ans.push_back({0});
for(int i=1;i<=n;i++){
if(i%2==1){//奇数
dp[i]=dp[i-1]+1;
}
else{//偶数
dp[i]=dp[i/2];
}
ans.push_back(dp[i]);
}
return ans;
}
};
3 只出现一次的数字 136 简单
背诵
- 任何数和 0 做异或运算,结果仍然是原来的数,即 a⊕0=a。
- 任何数和其自身做异或运算,结果是 0,即 a⊕a=0。
- 异或运算满足交换律和结合律,即 a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b。
出现两次的数⊕ 得0,0⊕出现一次的数,得到出现一次的数
class Solution {
public:
int singleNumber(vector<int>& nums) {
int res=0;
for(int num:nums){
res=res^num;
}
return res;
}
};
字符串 4道
1 字母异位词分组 49 √
map<排序后的string,原string>
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
vector<vector<string>> ans;
unordered_map<string,vector<string>> mp;
for(string str:strs){
string x=str;
sort(x.begin(),x.end());
mp[x].push_back(str);
}
for(auto e:mp){
ans.push_back(e.second);
}
return ans;
}
};
2 找到字符串中所有字母异位词 438 滑动窗口
滑动窗口大小=p.size(),在s中滑动
p中每个字符出现次数 == s的滑动窗口中 每个字符出现次数
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> ans;
vector<int> count_s(26);
vector<int> count_p(26);
int n=s.size(),m=p.size();
if(n<m) return ans;
//计算p中每个字符出现次数。并初始滑动窗口
for(int i=0;i<m;i++){
count_p[p[i]-'a']++;
count_s[s[i]-'a']++;
}
if(count_p==count_s){
ans.push_back(0);
}
//在s中进行窗口滑动
for(int i=0;i<n-m;i++){
count_s[s[i]-'a']--; //左侧滑走
count_s[s[i+m]-'a']++; //右侧滑入
if(count_s==count_p){
ans.push_back(i+1);
}
}
return ans;
}
};
3 无重复字符的最长子串 3 滑动窗口
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char,int> mp;//滑动窗口中字母出现次数
int ans=0;
int n=s.size();
int start,end=0; //滑动窗口边界
while(end<n){
mp[s[end]]++;
while(mp[s[end]]>=2){//左边界向右滑,直至没有重复字母
mp[s[start]]--;
start++;
}
ans=max(ans,end-start+1);
end++;//右边界向左滑
}
return ans;
}
};
4 字符串解码 394 递归
每层dfs返回一个string,是这个括号内 解码后 的字符串
入口:遇到 [ 进入下一层递归
出口:遇到 ] 返回当前层递归处理后的字符串
class Solution {
public:
int idx=0;//注意是全局变量,每层递归共享
string dfs(string s){
int n=s.size();
string res;//当前层递归要返回的解码后字符串
while(idx<n){
if(s[idx]>='0'&&s[idx]<='9'){//数字
int num=0;//多位数字组合成的倍数
while(s[idx]>='0'&&s[idx]<='9'){
num=num*10+(s[idx]-'0');
idx++;
}
//数字后一定跟的是左括号,现在idx是左括号
idx++;
string ress= dfs(s); //ress是下层递归处理后的字符串,需要乘以当前倍数
for(int i=1;i<=num;i++){
res=res+ress;
}
}else if(s[idx]>='a'&&s[idx]<='z'){//字母拼入res
res=res+s[idx];
}else{//右括号
return res;
}
idx++;
}//while
return res;
}
string decodeString(string s) {
return dfs(s);
}
};
栈 3道
1 有效的括号 20 简单 √
( [ ) ] 是false
左括号进栈,右括号和栈顶匹配
class Solution {
public:
bool isValid(string s) {
stack<char> sk;
unordered_map<char,char> mp={
{')','('},
{']','['},
{'}','{'}
};
for(char x:s){
if(mp.count(x)>0){//右括号
if(sk.empty() || sk.top()!=mp[x]){
return false;
}
sk.pop();//匹配了
}else{ //左括号
sk.push(x);
}
}
return sk.empty();
}
};
2 每日温度 739
单调栈。从底向顶递减。从右往左遍历数组。向右看,看到第一个比自己高的。
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
int n=temperatures.size();
vector<int> ans(n);
stack<int> sk;
for(int i=n-1;i>=0;i--){
while(!sk.empty()){
if(temperatures[i]<temperatures[sk.top()]){//找到第一个比自己大的
ans[i]=sk.top()-i;
break;
}
sk.pop();//栈里比自己小的不要,出栈
}
if(sk.empty()){
ans[i]=0;
}
sk.push(i);
}
return ans;
}
};
3 最小栈 155
辅助栈
class MinStack {
public:
stack<int> sk; //主栈,正常入栈出栈
stack<int> sk_min; //栈顶保存主栈当前的最小值
MinStack() {
sk_min.push(INT_MAX);
}
void push(int val) {
sk.push(val);
if(val<=sk_min.top()){
sk_min.push(val);
}
}
void pop() {
if(sk.top()==sk_min.top()){
sk_min.pop();
}
sk.pop();
}
int top() {
return sk.top();
}
int getMin() {
return sk_min.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();
*/
链表 11道
1 反转链表 206 简单 √
反转单链表head
/**
* 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) return nullptr;
ListNode* cur=head,*pre=nullptr;
while(cur){
ListNode* nxt=cur->next;
cur->next=pre;
pre=cur;
cur=nxt;
}
return pre;
}
};
哈希:
2 相交链表 160 简单√
遍历A,将每个结点加入set
遍历B,返回第一个在set中的结点
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
unordered_set<ListNode*> set_a;
ListNode* a=headA;
while(a){
set_a.insert(a);
a=a->next;
}
ListNode* b=headB;
while(b){
if(set_a.count(b)>0){
return b;
}
b=b->next;
}
return NULL;
}
};
3 环形链表 141 简单√
与上一题类似
class Solution {
public:
bool hasCycle(ListNode *head) {
unordered_set<ListNode*> set;
ListNode* p=head;
while(p){
if(set.count(p)>0){
return true;
}
set.insert(p);
p=p->next;
}
return false;
}
};
4 合并两个有序链表 21 简单 √
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* h=new ListNode();
ListNode *m=h,*a=list1,*b=list2;
while(a&&b){
if(a->val<b->val){
m->next=a;
a=a->next;
}else{
m->next=b;
b=b->next;
}
m=m->next;
}
if(a){
m->next=a;
}else{
m->next=b;
}
return h->next;
}
};
扩展:
双指针:
5 排序链表 148
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
解:分成左右两个子链表,分别排序 —–> 合并两个升序链表
class Solution {
public:
//找中间节点函数。偶数返回前一半的最后一个节点,奇数返回中间结点
ListNode* findmid(ListNode* head){
ListNode* slow=head,*fast=head;
while(fast->next&&fast->next->next){
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* h=new ListNode();
ListNode *m=h,*a=list1,*b=list2;
while(a&&b){
if(a->val<b->val){
m->next=a;
a=a->next;
}else{
m->next=b;
b=b->next;
}
m=m->next;
}
if(a){
m->next=a;
}else{
m->next=b;
}
return h->next;
}
ListNode* sortList(ListNode* head) {
//递归出口:空结点或只有一个节点,不用排序了
if(!head|| !head->next){
return head;
}
ListNode * mid=findmid(head);
ListNode* head2=mid->next;
mid->next=nullptr;
return mergeTwoLists(sortList(head),sortList(head2));
}
};
6 回文链表 简单 234 (1+5)
拆成两半(用快慢指针找到中间结点),第二半反转,再比较
class Solution {
public:
//找中间节点函数。偶数返回前一半的最后一个节点,奇数返回中间结点
ListNode* findmid(ListNode* head){
ListNode* slow=head,*fast=head;
while(fast&&fast->next&&fast->next->next){
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
//反转函数
ListNode* reverse(ListNode* head){
ListNode* cur=head;//当前遍历结点
ListNode* ne;//下一个要处理的结点
ListNode* pre=nullptr;//哨兵
while(cur){
ne=cur->next;
cur->next=pre;
pre=cur;
cur=ne;
}
return pre;
}
//主函数
bool isPalindrome(ListNode* head) {
ListNode* tail=findmid(head);
ListNode* right=tail->next;
tail->next=nullptr;
ListNode* head2=reverse(right);
//如果是奇数,head2比head短
while(head2){
if(head->val!=head2->val) return false;
head=head->next;
head2=head2->next;
}
return true;
}
};
7 环形链表 II 142
快慢指针。
背诵方法:快慢指针相等时,慢指针=head,两指针同时移动一步,直到重合
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* slow=head,*fast=head;
while(true){
if(fast==NULL|| fast->next==NULL) return NULL;
slow=slow->next;
fast=fast->next->next;
if(slow==fast) break;
}
slow=head;
while(slow!=fast){
slow=slow->next;
fast=fast->next;
}
return fast;
}
};
类似思想:
8 寻找重复数 287 位运算
与上一题一样:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int slow=0,fast=0;
while(true){
slow=nums[slow];//移动一步
fast=nums[nums[fast]];//移动两步
if(slow==fast) break;
}
slow=0;
while(slow!=fast){
slow=nums[slow];
fast=nums[fast];
}
return slow;
}
};
9 删除链表的倒数第 N 个结点 19 √ 注意哨兵
fast 移动n个。slow在头。
fast和slow同时移动。fast移动到尾,要移动len-n个; 此时slow也移动len-n个,则slow到尾就是len-(len-n)=n个
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode *a=new ListNode(0,head);//哨兵结点
ListNode *slow=a,*fast=head;
//q移动n个
for(int i=1;i<=n;i++){
fast=fast->next;
}
//同时移动
while(fast){
slow=slow->next;
fast=fast->next;
}
//pre->next就是倒数第n个结点
slow->next=slow->next->next; //删除操作
return a->next;
}
};
10 两数相加 2
模拟。
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* l=new ListNode();
ListNode* head=l;//保存头结点,以便最后返回
int carry=0;
while(l1 || l2){
int n1=l1?l1->val:0; //不存在则补0
int n2=l2?l2->val:0;
int sum=n1+n2+carry;
carry=sum/10;
ListNode* t=new ListNode();
t->val=sum%10;
l->next=t;
l=t;
if(l1) l1=l1->next;
if(l2) l2=l2->next;
}
if(carry>0){
l->next=new ListNode(carry);
}
return head->next;
}
};
11 LRU 缓存 146
双链表。dummy->pre即为队尾
O(1),需要用map
struct Node {
int key;
int value;
Node* pre;
Node* next;
Node() {
key = 0;
value = 0;
}
};
class LRUCache {
public:
int capacity;
Node* dummy;
unordered_map<int,Node*> key_to_node;
LRUCache(int c) {
capacity=c;
dummy=new Node();
dummy->pre=dummy;
dummy->next=dummy;
}
void remove_node(Node* x){
x->pre->next=x->next;
x->next->pre=x->pre;
}
void add_to_head(Node* x){
x->next=dummy->next;
x->pre=dummy;
dummy->next->pre=x;
dummy->next=x;
}
void move_to_head(Node* x){
remove_node(x);
add_to_head(x);
}
int get(int key) {
if(key_to_node.count(key)==0){
return -1;
}
Node* a=key_to_node[key];
move_to_head(a);
return a->value;
}
void put(int key, int value) {
//有这个key
if(key_to_node.count(key)>0){
key_to_node[key]->value=value;
move_to_head(key_to_node[key]);
return;
}
//没有
Node* node=new Node();
node->key=key;
node->value=value;
add_to_head(node);
key_to_node[key]=node;
if(key_to_node.size()>capacity){
Node* a=dummy->pre;
remove_node(a);
key_to_node.erase(a->key);
}
}
};
/**
* 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);
*
数组 18道
1 旋转图像 48
背诵
先对矩阵求对称,再对每一行左右翻转
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n=matrix.size();
for(int i=0;i<n;i++){
for(int j=0;j<i;j++){
swap(matrix[i][j],matrix[j][i]);
}
}
for(int i=0;i<n;i++){
for(int j=0;j<n/2;j++){
swap(matrix[i][j],matrix[i][n-j-1]);
}
}
}
};
二分查找 3道
模板:
- 返回第一个出现的位置
左侧是<它的,右侧是>=它的
int find(vector<int> nums,int target){
int n=nums.size();
int l=0,r=n-1;
while(l<=r){
int mid=l+(r-l)/2;
if(target>nums[mid]) l=mid+1;
else r=mid-1;
}
return l;
}
- 简单的找
int find(vector<int> nums,int target){
int n=nums.size();
int l=0,r=n-1;
while(l<=r){
int mid=l+(r-l)/2;
if(nums[mid]==targer) return mid;
if(target>nums[mid]) l=mid+1;
else r=mid-1;
}
return -1;
}
1 在排序数组中查找元素的第一个和最后一个位置 34
第一个:用模板1.
最后一个:用模板1查target+1,得到的位置再减一
class Solution {
public:
//模板
int find(vector<int> nums,int target){
int n=nums.size();
int l=0,r=n-1;
while(l<=r){
int mid=l+(r-l)/2;
if(target>nums[mid]) l=mid+1;
else r=mid-1;
}
return l;
}
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> ans;
int first=find(nums,target);
if(first>=nums.size()||nums[first]!=target){
return {{-1,-1}};
}
int last=find(nums,target+1);
ans.push_back(first);
ans.push_back(last-1);
return ans;
}
};
2 搜索旋转排序数组 33
nums是无序的,不能直接用二分
nums一定是这样的左右两段有序数组,称为左段、右段。
左段都是>=nums[0]的,右段都是<nums[0]的。
先判断mid在左段还是右段,再判断target在mid左侧还是右侧
class Solution {
public:
int search(vector<int>& nums, int target) {
int n=nums.size();
int l=0,r=n-1;
while(l<=r){
int mid=(l+(r-l)/2);
if(nums[mid]==target) return mid;
if(nums[0]<=nums[mid]){//说明mid在左段
//tagert在mid左侧还是右侧?
if(target>=nums[0]&&target<nums[mid]){//在左侧
r=mid-1;
}else{//在右侧
l=mid+1;
}
}else{//mid在右段
//同理
if(target>nums[mid]&&target<=nums[n-1]){
l=mid+1;
}else{
r=mid-1;
}
}
}
return -1;
}
};
3 搜索二维矩阵 II 240 矩阵 √
遍历每行,对每一行用二分查找。
class Solution {
public:
bool find(vector<int> &nums,int target){//注意要写&号,不然会超出内存限制
int n=nums.size();
int l=0,r=n-1;
while(l<=r){
int mid=l-(l-r)/2;
if(nums[mid]==target) return true;
if(target<nums[mid]) r=mid-1;
else l=mid+1;
}
return false;
}
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m=matrix.size();
int n=matrix[0].size();
for(int i=0;i<m;i++){
if(find(matrix[i],target)){
return true;
}
}
return false;
}
};
快排 2道
模板
void quickSort1(vector<int>& nums, int l, int r) {
// 1. 递归终止条件:当子数组长度为1或空时,无需排序
if (l >= r) return;
// 2. 初始化分区参数:
// - i 初始在左边界左侧(l-1)
// - j 初始在右边界右侧(r+1)
// - 基准值 x 选中间元素(避免极端情况如全排序数组导致最坏时间复杂度)
int i = l - 1, j = r + 1;
int x = nums[l+(r-l)/2];
// 3. Hoare 分区循环:双指针从两端向中间移动
while (i < j) {
// 3.1 移动左指针 i:跳过所有小于 x 的元素,直到找到 >=x 的元素
do i++; while (nums[i] < x);
// 3.2 移动右指针 j:跳过所有大于 x 的元素,直到找到 <=x 的元素
do j--; while (nums[j] > x);
// 3.3 如果指针未交错,交换左右指针的元素(确保左边 <=x,右边 >=x)
if (i < j) swap(nums[i], nums[j]);
}
// 4. 递归排序左右子数组:
// - 分区点为 j(因为当 i >= j 时,j 是左半部分的最右端)
// - 左子数组:[l, j](所有元素 <=x)
// - 右子数组:[j+1, r](所有元素 >=x)
quickSort1(nums, l, j);
quickSort1(nums, j + 1, r);
}
1 数组中的第K个最大元素 215
class Solution {
public:
//从大到小排序。 k是数组下标
int helper(vector<int>& nums,int l,int r,int k){
if(l>=r) return nums[k];//递归出口.排好序了
int n=nums.size();
int p=l+(r-l)/2;
int x=nums[p]; //枢轴
int i=l-1,j=r+1;
while(i<j){
do{i++;}while(nums[i]>x);
do{j--;}while(nums[j]<x);
if(i<j){
swap(nums[i],nums[j]);
}
}
//现在[l,j]中的元素均>[j+1,r]中的元素
if(k<=j) return helper(nums,l,j,k);//不用管右边的具体顺序
else return helper(nums,j+1,r,k);//不用管左边
}
int findKthLargest(vector<int>& nums, int k) {
int n=nums.size();
return helper(nums,0,n-1,k-1);
}
};
2 前 K 个高频元素 347
class Solution {
public:
vector<int> ans;
//按频率由大到小排序。k:还要找出k个高频数
void helper(vector<int> nums,int l,int r,int k,unordered_map<int,int> v_f){
if(k==0) return;
int p=l+(r-l)/2;
int fre=v_f[nums[p]];
int i=l-1,j=r+1;
while(i<j){
do{i++;} while(v_f[nums[i]]>fre);
do{j--;} while(v_f[nums[j]]<fre);
if(i<j){
swap(nums[i],nums[j]);
}
}
int len=j-l+1;
if(k>=len){
//[l,j]的数全加进ans
for(int i=l;i<=j;i++){
ans.push_back(nums[i]);
}
helper(nums,j+1,r,k-len,v_f);//还要找k-len个
}else{
helper(nums,l,j,k,v_f);
}
}
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int,int> v_f;//值--频次
unordered_set<int> v; //set用于数组去重
//遍历nums,构造map
for(int x:nums){
v_f[x]++;
v.insert(x);//去重
}
//构造去重后的数组a
vector<int> a;
for(int x:v){
a.push_back(x);
}
helper(a,0,a.size()-1,k,v_f);
return ans;
}
};
双指针 6道
1 移动零 283 数组 简单
时刻保证 p1左边全是非0
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int n=nums.size();
int p1=0,p2=0;
while(p2<n){
if(nums[p2]!=0){
swap(nums[p1],nums[p2]);
p1++;
}
p2++;
}
}
};
类似:
2 颜色分类 75
保证:p0左面全是0,p2右面全是2
class Solution {
public:
void sortColors(vector<int>& nums) {
int n=nums.size();
int p0=0,p1=0,p2=n-1;
while(p1<=p2){
if(nums[p1]==0){
swap(nums[p0],nums[p1]);
p0++;
p1++;
}else if(nums[p1]==2){
swap(nums[p1],nums[p2]);
p2--;
//不能p1++,因为i的位置可能是0,要和p0换
}else{
p1++;
}
}
}
};
3 盛最多水的容器 11 √
class Solution {
public:
int maxArea(vector<int>& height) {
int n=height.size();
int i=0,j=n-1;
int ans=0;
while(i<j){
ans=max(ans,(j-i)*min(height[i],height[j]));
if(height[i]<height[j]){
i++;
}else{
j--;
}
}
return ans;
}
};
4 最短无序连续子数组 581
左段的最大值小于右边,右段的最小值大于左边
找中段的左右边界begin end
end:从左到右,最后一个<左面的max的
class Solution {
public:
int findUnsortedSubarray(vector<int>& nums) {
int n=nums.size();
int begin=0,end=-1;
int max=nums[0],min=nums[n-1];
for(int i=0;i<n;i++){
//从左到右确定end 、更新max
//end最终停在 最后一个<max的地方。所以end右边都是>max的
if(nums[i]>=max){
max=nums[i];
}else{
end=i;
}
//从右到左确定begin 、更新min
//begin最终停在 最后一个>min的地方。所以begin左边都是<min的
int j=n-i-1;
if(nums[j]<=min){
min=nums[j];
}else{
begin=j;
}
}
return end-begin+1;
//初始化begin=0,end=-1,这样end-begin+1=0
}
};
5 三数之和 15
先排序,再枚举。这样枚举的[a,b,c] 满足 a<=b<=c ,时间复杂度低
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n=nums.size();
sort(nums.begin(),nums.end());
vector<vector<int>> ans;
for(int i=0;i<n-2;i++){
if(i>0&&nums[i]==nums[i-1]) continue;// 跳过重复元素
// 双指针,目标是找到 nums[l] + nums[r] = -nums[i]
int l=i+1,r=n-1;
int target=-nums[i];
while(l<r){
int sum=nums[l]+nums[r];
if(sum==target){
ans.push_back({nums[i],nums[l],nums[r]});
l++;
r--;
// 跳过重复元素
while(l<r&&nums[l]==nums[l-1]) l++;
while(l<r&&nums[r]==nums[r+1]) r--;
}else if(sum<target){
l++;
}else{
r--;
}
}
}
return ans;
}
};
6 下一个排列 31
升序是最小的排列,降序是最大的排列。变大需要升序变降序
改变的地方越靠后,改变越小
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int n=nums.size();
//从后往前找 第一个升序对nums[i]< nums[j]
int i=n-2,j=n-1;
while(i>=0&&nums[i]>=nums[j]){
i--;
j--;
}
if(i>=0){
//找满足nums[k]>nums[i]的最小的nums[k]
int k=n-1;
while(nums[k]<=nums[i]) k--;
swap(nums[i],nums[k]);
}
//逆置 [j,n-1],使其升序。升序是最小的排列
int l=j,r=n-1;
while(l<r){
swap(nums[l],nums[r]);
l++;
r--;
}
}
};
蓝色的下一个排列为橙色:
回溯 6道
-
确定回溯函数参数
-
确定终止条件(叶子
-
确定单层遍历逻辑(处理、递归、回溯)
-
组合
1 组合总和 39 搜索
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
void backtracking(vector<int>& candidates,int target,int sum,int idx){
if(sum==target){
ans.push_back(path);
return;
}
if(sum>target){
return;
}
for(int i=idx;i<candidates.size();i++){
path.push_back(candidates[i]);
backtracking(candidates,target,sum+candidates[i],i);
//可重复选,所以是i,不是i+1
path.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
backtracking(candidates,target,0,0);
return ans;
}
};
2 电话号码的字母组合 17 搜索
class Solution {
public:
string map[10]={
"","","abc","def",//0,1,2,3
"ghi","jkl","mno",//4,5,6
"pqrs","tuv","wxyz"//7,8,9
};
vector<string> ans;
string path;
void backtracking(string digits,int idx){
if(idx==digits.size()){
ans.push_back(path);
return;
}
string letters=map[digits[idx]-'0'];
for(int i=0;i<letters.size();i++){//遍历数字对应的字母集合
path.push_back(letters[i]);
backtracking(digits,idx+1);
path.pop_back();
}
}
vector<string> letterCombinations(string digits) {
if(digits.size()==0){
return {};
}
backtracking(digits,0);
return ans;
}
};
子集:
要获取所有节点
3 子集 78
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
void backtracking(vector<int>& nums,int idx){
if(idx>nums.size()){
return;
}
ans.push_back(path);
for(int i=idx;i<nums.size();i++){
path.push_back(nums[i]);
backtracking(nums,i+1);
path.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
backtracking(nums,0);
return ans;
}
};
- 排列
- 每层都是从0开始搜索而不是idx
- 需要used数组记录path里都放了哪些元素了
4 全排列 46 搜索
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
void backtracking(vector<int> &nums,vector<bool> used){
if(path.size()==nums.size()){
ans.push_back(path);
return;
}
for(int i=0;i<nums.size();i++){
if(used[i]==true) continue;
path.push_back(nums[i]);
used[i]=true;
backtracking(nums,used);
path.pop_back();
used[i]=false;
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<bool> used(nums.size(),false);
backtracking(nums,used);
return ans;
}
};
- 其他
5 括号生成 22 搜索
class Solution {
public:
vector<string> ans;
string path;
void dfs(int l,int r,int n){//左括号数量、右括号数量
if(l==n&&r==n){
ans.push_back(path);
return;
}
if(l<n){//如果还能加入左括号 就加入
path.push_back('(');
dfs(l+1,r,n);
path.pop_back();
}
if(r<l&&r<n){//只有还有左括号的时候才能压入右括号
path.push_back(')');
dfs(l,r+1,n);
path.pop_back();
}
}
vector<string> generateParenthesis(int n) {
dfs(0,0,n);
return ans;
}
};
6 单词搜索 79 矩阵
class Solution {
public:
bool dfs(vector<vector<char>>& board,string word,int i,int j,int idx){
int m=board.size();
int n=board[0].size();
//过界 或 不是要找的字母
if(i<0||i>=m||j<0||j>=n||board[i][j]!=word[idx]){
return false;
}
//找完了
if(idx==word.size()-1){
return true;
}
//符合board[i][j]=word[idx],继续找下一个字母:
board[i][j]='\0';//标记,防止重复
bool res=dfs(board,word,i+1,j,idx+1)||dfs(board,word,i-1,j,idx+1)||
dfs(board,word,i,j+1,idx+1)||dfs(board,word,i,j-1,idx+1);
//上下左右继续
board[i][j]=word[idx];//回溯
return res;
}
bool exist(vector<vector<char>>& board, string word) {
int m=board.size();
int n=board[0].size();
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(dfs(board,word,i,j,0)){
return true;
}
}
}
return false;
}
};
动态规划 20道
动规五部曲分别为:
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
基础
1 爬楼梯 70 简单 √
class Solution {
public:
int climbStairs(int n) {
vector<int> dp(50);
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
};
2 不同路径 62 √
class Solution {
public:
int uniquePaths(int m, int n) {
//dp[i][j]:到这格有多少条不同路径
vector<vector<int>> dp(m,vector<int>(n));
//最左一列
for(int i=0;i<m;i++){
dp[i][0]=1;
}
//最上一行
for(int j=0;j<n;j++){
dp[0][j]=1;
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
dp[i][j]=dp[i-1][j]+dp[i][j-1];//从左边来+从上边来
}
}
return dp[m-1][n-1];
}
};
3 最小路径和 64 动态规划 √
dp[i][j]:走到grid[i][j]处的最小路径和
分从左走过来、从上走过来 两种情况
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m=grid.size();
int n=grid[0].size();
vector<vector<int>> dp(m,vector<int>(n));
dp[0][0]=grid[0][0];
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(i==0&&j==0) continue;
if(i==0){ //第一行 边界
dp[0][j]=dp[0][j-1]+grid[0][j];
}else if(j==0){ //第一列边界
dp[i][0]=dp[i-1][0]+grid[i][0];
}else{
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j];
}
}
}
return dp[m-1][n-1];
}
};
4 最大正方形 221 动态规划
左、上、左上,三者取最小
class Solution {
public:
int maximalSquare(vector<vector<char>>& matrix) {
int m=matrix.size();
int n=matrix[0].size();
vector<vector<int>> dp(m,vector<int>(n));
int max_side=0;//最大边长
//处理第一行和第一列边界
for(int i=0;i<m;i++){
if(matrix[i][0]=='1'){
dp[i][0]=1;
max_side=1;
}
}
for(int j=0;j<n;j++){
if(matrix[0][j]=='1'){
dp[0][j]=1;
max_side=1;
}
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
if(matrix[i][j]=='1'){
dp[i][j]=min(dp[i-1][j-1],min(dp[i][j-1],dp[i-1][j]))+1;
max_side=max(max_side,dp[i][j]);
}
}
}
return max_side*max_side;
}
};
5 不同的二叉搜索树 96 树
dp[i] :总结点数为i的数,有dp[i]种
一棵二叉搜索树,总结点数为num,根节点的值为value,则左子树的结点个数为value-1,右子树的结点个数为num-value
种数=左子树种数*右子树种数
class Solution {
public:
int numTrees(int n) {
vector<int> dp(n+1);
dp[0]=1; //空结点也算1种
dp[1]=1;
for(int num=2;num<=n;num++){//num是总结点数
for(int value=1;value<=num;value++){//value是根结点的值,范围肯定是在[1,num]之间
dp[num]+=dp[value-1]*dp[num-value];
}
}
return dp[n];
}
};
子序列
1 回文子串 647 字符串
dp[i][j]:s[i]到s[j]的子串是否是回文串
注意:i和j遍历的顺序,要先遍历更短的子串。(动态规划的依赖性)
class Solution {
public:
int countSubstrings(string s) {
int ans=0;
int n=s.size();
vector<vector<bool>> dp(n,vector<bool>(n));
for(int j=0;j<n;j++){
for(int i=0;i<=j;i++){
if(i==j){
dp[i][j]=true;
}else if(j==i+1){
dp[i][j]=s[i]==s[j];
}else{
dp[i][j]=s[i]==s[j]&&dp[i+1][j-1];
}
if(dp[i][j]) ans++;
}
}
return ans;
}
};
2 最长回文子串 5 字符串
和上一题几乎一样,只要记录、更新长度就好
class Solution {
public:
string longestPalindrome(string s) {
int maxlen=0;
int begin=0;
int n=s.size();
vector<vector<bool>> dp(n,vector<bool>(n));
for(int j=0;j<n;j++){
for(int i=0;i<=j;i++){
if(i==j){
dp[i][j]=true;
}else if(j==i+1){
dp[i][j]=s[i]==s[j];
}else{
dp[i][j]=s[i]==s[j]&&dp[i+1][j-1];
}
if(dp[i][j]){
if(j-i+1>maxlen){
maxlen=j-i+1;
begin=i;
}
}
}
}
return s.substr(begin,maxlen);
}
};
3 编辑距离 72 字符串
dp[i][j]:word1的前i个字符 转变成 word2的前j个字符,最少的操作次数
遍历两个字符串
- 字符相等。转化为:word1-1转变为word2-1
- 字符不等
- 替换这个字符。转化为:word1-1转变为word2-1 +1次操作
- word1插入这个字符。word1转变为word2 -1 +1次操作
- word2删除这个字符。word1-1转变为word2 +1次操作
dp[i][j]为以上情况的最小的那个
class Solution {
public:
int minDistance(string word1, string word2) {
int len1 = word1.size();
int len2 = word2.size();
vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0x3f3f3f3f));
// 初始化dp
for (int i = 0; i <= len1; i++) {
dp[i][0] = i; // word1删除i次变成空
}
for (int j = 0; j <= len2; j++) {
dp[0][j] = j; // 空串插入n次变成word2
}
// 遍历两个字符串
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
if (word1[i - 1] == word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
}
int replace = dp[i - 1][j - 1] + 1;
int insert = dp[i][j - 1] + 1;
int deletee = dp[i - 1][j] + 1;
dp[i][j] = min(dp[i][j], min(replace, min(insert, deletee)));
}
}
return dp[len1][len2];
}
};
4 最大子数组和 53 数组
dp[i]:以第i个数结尾的子数组(必须选上),最大和为dp[i]
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n=nums.size();
vector<int> dp(n+1);
int ans=-0x3f3f3f3f;
dp[0]=0;
for(int i=1;i<=n;i++){
if(dp[i-1]<=0){
dp[i]=nums[i-1];
}else{
dp[i]=dp[i-1]+nums[i-1];
}
ans=max(ans,dp[i]);
}
return ans;
}
};
5 乘积最大子数组 152 数组
类似上一题
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n=nums.size();
int dp_max[n+1];//以第i个数为结尾的子数组的乘积最大值
int dp_min[n+1];
memset(dp_max,0,n+1);
memset(dp_min,0,n+1);
dp_max[0]=1;
dp_min[0]=1;
int ans=nums[0];
for(int i=1;i<=n;i++){
dp_max[i]=max(nums[i-1],
max(dp_max[i-1]*nums[i-1],dp_min[i-1]*nums[i-1]));
dp_min[i]=min(nums[i-1],
min(dp_max[i-1]*nums[i-1],dp_min[i-1]*nums[i-1]));
ans=max(ans,dp_max[i]);
}
return ans;
}
};
6 最长递增子序列 300 数组
dp[i]表示以nums的第i个元素结尾的最长递增子序列长度
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n=nums.size();
int dp[n+1];
memset(dp,0,n+1);
int ans=0;
for(int i=1;i<=n;i++){
dp[i]=1;
for(int j=1;j<i;j++){
if(nums[j-1]<nums[i-1]){
dp[i]=max(dp[i],dp[j]+1);
}else{
dp[i]=dp[i];
}
}
ans=max(ans,dp[i]);
}
return ans;
}
};
7 最长连续序列 128 数组 不是动规
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
int ans=0;
unordered_set<int> st(nums.begin(),nums.end());//nums加入set去重
//遍历set
for(int num:st){
if(st.count(num-1)) continue;
//以num-1开头的序列肯定比以num开头的序列长,已经处理过了,跳过
int x=num+1;
//不断找num+1,num+2.....把此序列全找完,断了就结束
while(st.count(x)){
x++;
}
//此序列以num开头,x-1结尾
ans=max(ans,x-num);
}
return ans;
}
};
其他
1 打家劫舍 198 数组
class Solution {
public:
int rob(vector<int>& nums) {
int n=nums.size();
vector<int> dp(n+1);//dp[i]:偷前i间房子的最高金额
dp[0]=0;
dp[1]=nums[0];
for(int i=2;i<=n;i++){
dp[i]=max(nums[i-1]+dp[i-2],dp[i-1]);//偷,不偷
}
return dp[n];
}
};
2 打家劫舍 III 337 树
递归。左右根,因为要先知道左右子树,才能知道根
两个dp数组:
yes[t] : 以t为根的树,选择偷t,获得的最高金额
no[t] : 以t为根的树,选择不偷t,获得的最高金额
class Solution {
public:
unordered_map<TreeNode*,int> yes,no;
void dfs(TreeNode* t) {
if (!t) return;
dfs(t->left);
dfs(t->right);
yes[t] = no[t->left] + no[t->right] + t->val;
no[t] = max(yes[t->left], no[t->left]) + max(yes[t->right], no[t->right]);
}
int rob(TreeNode* root) {
dfs(root);
return max(yes[root], no[root]);
}
};
3 最佳买卖股票时机含冷冻期 309 数组
今天结束后,有四种状态:
- 不持有(今天肯定没买)
- 今天卖了——昨天是持有状态
- 没买没卖——昨天不持有
- 持有(今天肯定没卖)
- 今天买了——昨天不持有、没卖(冷冻)
- 没买没卖——昨天持有
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
int dp[n][4]; //dp[i][j]:第i天结束,状态为j,目前的收益。
//prices从第0天开始
memset(dp,0,sizeof dp);
//4种状态:
dp[0][0]=0;//不持有,今天卖出
dp[0][1]=0;//不持有,今天没卖(也没买)
dp[0][2]=-prices[0];//持有,今天买的(也没卖)
dp[0][3]=-prices[0];//持有,今天没买,之前买的(今天没买没卖)
for(int i=1;i<n;i++){
dp[i][0]=prices[i]+max(dp[i-1][2],dp[i-1][3]);//昨天是持有状态
dp[i][1]=max(dp[i-1][0],dp[i-1][1]);//昨天不持有
dp[i][2]=-prices[i]+dp[i-1][1];//今天买,说明昨天不持有且没卖
dp[i][3]=max(dp[i-1][2],dp[i-1][3]);//昨天持有
}
return max(dp[n-1][0],dp[n-1][1]);//最后肯定全卖了,不持有
}
};
背包 5道
01 背包:
物品不可重复使用。
外循环遍历物品,内循环遍历背包 target,且内循环倒序
完全背包:
元素可重复使用。内循环正序。
(1)组合:不考虑物品之间顺序:物品放在外循环,背包target在内循环。
(2)排列:需考虑物品之间的顺序:背包 target 放在外循环,物品放在内循环。
01背包:
1 分割等和子集 416 数组
物品:nums 物品重量:nums[i] 背包容量:sum/2
//bool dp[i][j]:从前i个数选,和为j,可以选出来吗
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum=0;
for(int x:nums){
sum+=x;
}
if(sum%2==1){//不能是奇数
return false;
}
int n=nums.size();
int target=sum/2;
vector<vector<bool>> dp(n+1,vector<bool>(target+1));
dp[0][0]=true;
for(int i=1;i<=n;i++){
for(int j=0;j<=target;j++){
if(nums[i-1]<=j){//可选可不选
dp[i][j]=dp[i-1][j]||dp[i-1][j-nums[i-1]];//不选 or 选
}else{//不能选
dp[i][j]=dp[i-1][j];
}
}
}
return dp[n][target];
}
};
//公式版:
class Solution {
public:
bool canPartition(vector<int>& nums) {
int n=nums.size();
int sum=0;
for(int num:nums){
sum+=num;
}
if(sum%2==1) return false;
int target=sum/2;
vector<bool> dp(target+1); //dp[j]:和为j能不能选出
dp[0]=true;
for(int i=0;i<n;i++){
for(int j=target;j>=0;j--){
if(j>=nums[i]){
dp[j]=dp[j]||dp[j-nums[i]]; //不选 or 选
}else{
dp[j]=dp[j]; //只能不选 //这行可以没有
}
}
}
return dp[target];
}
};
2 目标和 494
充当正数的num和 - 充当负数的num和=要求的target
充当正数的num和 + 充当负数的num和 =sum 是固定的
综合得,2*充当正数的num和=target+sum
target_new=(target+sum)/2
//dp[i][j]:从前i个数中选,和为j的种数
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int n=nums.size();
int sum=0;
for(int x:nums){
sum+=x;
}
if((sum+target)%2==1) return 0;
int t=(sum+target)/2;
if(t<0) return 0;
vector<vector<int>> dp(n+1,vector<int>(t+1));
dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=t;j++){
if(nums[i-1]<=j){//可选可不选
dp[i][j]=dp[i-1][j]+dp[i-1][j-nums[i-1]];//不选+选
}else{//不能选
dp[i][j]=dp[i-1][j];
}
}
}
return dp[n][t];
}
};
//公式法:
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int n=nums.size();
int sum=0;
for(int num:nums){
sum+=num;
}
if((sum+target)%2==1) return 0;
int target_new=(sum+target)/2;
if(target_new<0) return 0;
vector<int> dp(target_new+1);//dp[j] 表示和为 j 的 num 组合有 dp[j] 种。
dp[0]=1;
for(int i=0;i<n;i++){
for(int j=target_new;j>=0;j--){
if(j>=nums[i]){
dp[j]=dp[j]+dp[j-nums[i]];//不选的组合种数+选的组合种数
}else{//不选
dp[j]=dp[j];
}
}
}
return dp[target_new];
}
};
完全背包:
1 零钱兑换 322
组合。
dp[j]:j元最少需要的硬币数
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
int n=coins.size();
vector<long long> dp(amount+1,INT_MAX);
dp[0]=0;
for(int i=0;i<n;i++){
for(int j=1;j<=amount;j++){
if(coins[i]<=j){
dp[j]=min(dp[j],dp[j-coins[i]]+1);//不选或选
}
}
}
if(dp[amount]==INT_MAX) return -1;//没找到
return dp[amount];
}
};
2 完全平方数 279
**组合。**物品:1,4,16,25…;背包:n
class Solution {
public:
int numSquares(int n) {
vector<int> dp(n+1,INT_MAX);
dp[0]=0;
for(int i=1;i<=sqrt(n);i++){
for(int j=1;j<=n;j++){
if(j<i*i){
dp[j]=dp[j];
}else{
dp[j]=min(dp[j],dp[j-i*i]+1);
}
}
}
return dp[n];
}
};
3 单词拆分 139
排列。物品:wordDict;背包:s
dp[i]:s的以第i个字母结尾的字符串是否能由 wordDict 中组合而成
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
int n=s.size();
vector<bool> dp(n+1);
dp[0]=true;
for(int i=1;i<=n;i++){
for(int j=0;j<wordDict.size();j++){
string word=wordDict[j];
int len=word.size();
if(len>i){//不能选
dp[i]=dp[i];
}else{
dp[i]=dp[i]|| (dp[i-len]&&word==s.substr(i-len,len));
//不选或选
}
}
}
return dp[n];
}
};
贪心 10道
1 多数元素 169 简单√
1
2 买卖股票的最佳时机 121 简单
1
3 两数之和 1 简单
map
4 找到所有数组中消失的数字 448
奇技淫巧
num范围是1到n,可以映射数组的[0,n),n个位置。
当前元素是 nums[i],那么我们把第 nums[i]−1 位置的元素 乘以 -1,表示这个该位置出现过
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
int n=nums.size();
vector<int> ans;
for(int i=0;i<n;i++){
int j=abs(nums[i]);
if(nums[j-1]>0){
nums[j-1]=-nums[j-1];
}
}
for(int i=0;i<n;i++){
if(nums[i]>0){
ans.push_back(i+1);
}
}
return ans;
}
};
5 根据身高重建队列 406
h从大到小,k从小到大 排序。依次插入ans中
class Solution {
public:
bool static compare_design(vector<int> a,vector<int> b){
if(a[0]!=b[0]) return a[0]>b[0];
return a[1]<b[1];
}
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
int n=people.size();
vector<vector<int>> ans;
sort(people.begin(),people.end(),compare_design);
for(auto p:people){
if(p[1]>=ans.size()){
ans.push_back(p);
}else{
ans.insert(ans.begin()+p[1],p);
}
}
return ans;
}
};
6 除自身以外数组的乘积 238 前缀和
前缀和数组left:左面的元素乘积
right:右面的元素乘积
ans=left*right
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n=nums.size();
vector<int> left(n);
vector<int> right(n);
left[0]=1;
for(int i=1;i<n;i++){
left[i]=left[i-1]*nums[i-1];
}
right[n-1]=1;
for(int i=n-2;i>=0;i--){
right[i]=right[i+1]*nums[i+1];
}
vector<int> ans(n);
for(int i=0;i<n;i++){
ans[i]=left[i]*right[i];
}
return ans;
}
};
7 和为 K 的子数组 560 前缀和
想找[i , j]数组和为k
前缀和数组sum
sum[j]-sum[i]=k
想找前面有没有sum[i]
即有没有sum[i]=sum[j]-k
前面都保存到map里,去map中找 sum[j]-k
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int n=nums.size();
vector<int> sum(n+1);
sum[0]=0;
for(int i=1;i<=n;i++){
sum[i]=nums[i-1]+sum[i-1];
}
unordered_map<int,int> mp;//前缀和为sum的数量是mp[sum]
int ans=0;
//遍历sum
for(int i=0;i<=n;i++){
int x=sum[i]-k;
if(mp.count(x)>0){
ans+=mp[x];
}
mp[sum[i]]++;
}
return ans;
}
};
8 任务调度器 621
建立大小为 n+1 的桶子,个数为任务数量最多的那个任务
答案 = (桶个数 - 1) * (n + 1) + 最后一桶的任务数
情况:如果被桶全被填满了,没有空闲时间,答案就是任务的数量
class Solution {
public:
int leastInterval(vector<char>& tasks, int n) {
unordered_map<char,int> mp;
int tong=0;//桶个数
for(char task:tasks){
mp[task]++;
tong=max(tong,mp[task]);
}
int count=0;//最后一桶的任务数
for(auto x:mp){
if(x.second==tong){
count++;
}
}
int ans=(tong-1)*(n+1)+count;
int len=tasks.size();
return max(ans,len);
}
};
9 跳跃游戏 55
- 如果某一个作为 起跳点 的格子可以跳跃的距离是 3,那么表示后面 3 个格子都可以作为 起跳点
- 可以对每一个能作为 起跳点 的格子都尝试跳一次,把 能跳到最远的距离 不断更新
- 如果可以一直跳到最后,就成功了
class Solution {
public:
bool canJump(vector<int>& nums) {
int n=nums.size();
int reach=0;
for(int i=0;i<n;i++){
if(i>reach) return false;//前面没有能跳到i的
reach=max(reach,i+nums[i]);
if(reach>=n-1){
return true;
}
}
return false;
}
};
10 合并区间 56 双指针
j,起点比i起点小的都合并
class Solution {
public:
//按起点由小到大排序
static bool cmp(vector<int> a,vector<int> b){
if(a[0]!=b[0]) return a[0]<b[0];
return a[1]<b[1];
}
vector<vector<int>> merge(vector<vector<int>>& intervals) {
sort(intervals.begin(),intervals.end(),cmp);
int n=intervals.size();
vector<vector<int>> ans;
int i=0;
while(i<n){
int t=intervals[i][1];//目前最右的
int j=i+1;
//起点<t的合并
while(j<n&&intervals[j][0]<=t){
t=max(t,intervals[j][1]);//更新最右
j++;
}
ans.push_back({intervals[i][0],t});
i=j;
}
return ans;
}
};
图 3道
1 岛屿数量 200 搜索(联通块)dfs
- 0 —— 海洋格子
- 1 —— 陆地格子(未遍历过)
- 2 —— 陆地格子(已遍历过) 避免重复遍历
遍历。是1则DFS它相邻的。
class Solution {
public:
void dfs(vector<vector<char>> &grid,int i,int j){//注意&,否则不对
if(!isValid(grid,i,j)){
return;
}
if(grid[i][j]!='1'){
return;
}
grid[i][j]='2';
// 访问上、下、左、右四个相邻结点
dfs(grid,i-1,j);
dfs(grid,i+1,j);
dfs(grid,i,j-1);
dfs(grid,i,j+1);
}
//判断是否出界
bool isValid(vector<vector<char>> &grid,int i,int j){////注意&,否则超时
int m=grid.size();
int n=grid[0].size();
if(i<0||j<0||i>=m||j>=n) return false;
return true;
}
int numIslands(vector<vector<char>>& grid) {
int m=grid.size();
int n=grid[0].size();
int ans=0;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(grid[i][j]=='1'){//b
ans++;
dfs(grid,i,j);
}
}
}
return ans;
}
};
2 课程表 207 搜索(拓扑排序)
拓扑排序的过程,其实就两步:
- 找到入度为0 的节点,加入结果集
- 将该节点从图中移除
循环以上两步,直到 所有节点都在图中被移除了。
结果集的顺序,就是我们想要的拓扑排序顺序
入度为0的课程可以学。
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<int> indegree(numCourses);//记录每个课程的入度(它的前置课程数)
unordered_map<int,vector<int>> mp; //课程号-它的后置课程集合
//构造indegree和mp:
for(int i=0;i<prerequisites.size();i++){
indegree[prerequisites[i][0]]++;
mp[prerequisites[i][1]].push_back(prerequisites[i][0]);
}
queue<int> q;//存放入度为0的课程号
//构造q:
for(int i=0;i<numCourses;i++){
if(indegree[i]==0){
q.push(i);
}
}
int count=0;
while(!q.empty()){ //入度为0的课程出队,从图中去掉
int x=q.front();
q.pop();
count++;
//x从图中去掉,那么x的后置课程入度要减1:
for(int i=0;i<mp[x].size();i++){
int num=mp[x][i];//x的后置课程
if(indegree[num]>0){
indegree[num]--;
if(indegree[num]==0){
q.push(num);
}
}
}
}
if(count==numCourses) return true;
return false;
}
};
3 除法求值 399 搜索(图,dfs)
那么我们求解 a / c 相当于计算从节点 a 到 节点 c 的路径的权重乘积。
class Solution {
public:
vector<double> ans;
unordered_map<string,vector<pair<string,double>>> graph;
unordered_map<string,bool> visited;
bool found;
void dfs(string start,string end,double path){
//path是一路走来的乘积和
if(found){
return;
}
if(start==end){
found=true;
ans.push_back(path);
return;
}
// 枚举该节点的所有邻节点:
for(pair<string,double> neighbor:graph[start]){
string node=neighbor.first;
double weight=neighbor.second;
if(visited[node]==0){
visited[node]=1;
dfs(node,end,path*weight);//递归处理邻节点
visited[node]=0;
}
}
}
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
//构造双向图:
for(int i=0;i<equations.size();i++){
graph[equations[i][0]].push_back(make_pair(equations[i][1],values[i]));
graph[equations[i][1]].push_back(make_pair(equations[i][0],1.0/values[i]));
}
// 对于每个query,寻找从起点start到终点end的最短路径,并计算权重积
for(int i=0;i<queries.size();i++){
if(graph.count(queries[i][0])==0){//没有这个字母
ans.push_back(-1.0);
continue;
}
found=false;
dfs(queries[i][0],queries[i][1],1);
if(found==false){
ans.push_back(-1.0);
}
}
return ans;
}
};
426

被折叠的 条评论
为什么被折叠?



