目录
142. 环形链表 II
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *fast=head;
ListNode *slow=head;
while(fast!=NULL)
{
slow=slow->next;
if(fast->next==NULL) return NULL;
fast=fast->next->next;
if(fast==slow)
{
ListNode*p=head;
while(p!=slow)
{
p=p->next;
slow=slow->next;
}
return p;
}
}
return NULL;
}
};
-
快慢指针+p指针
-
快慢指针相遇时,设置p=head
-
p和slow每次走一步,相遇时就是入口
146. LRU 缓存
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
class LRUCache {
public:
int n;
struct DoubleList
{
int key;
int val;
DoubleList* pre;
DoubleList* next;
DoubleList(int _key,int _val):key(_key),val(_val),pre(NULL),next(NULL){}; //初始化列表
};
DoubleList *head,*end; //定义边界节点,使head和end总是链表中的第一个和最后一个节点
unordered_map<int, DoubleList*> um;
void addToHead(DoubleList* node) //在头位置,(head节点后面)添加节点
{
node->next=head->next;
node->pre=head;
head->next->pre=node;
head->next=node;
}
void remove(DoubleList *node) //删除节点
{
node->next->pre=node->pre;
node->pre->next=node->next;
}
public:
LRUCache(int capacity) {
n=capacity;
head=new DoubleList(-1,-1);
end=new DoubleList(-1,-1); //new 边界节点
head->next=end; // 连接head和end节点
end->pre=head;
}
int get(int key) {
if(um.find(key)==um.end()) return -1;
auto p=um[key];
remove(p);
addToHead(p);
return p->val;
}
void put(int key, int value) {
if(um.find(key)!=um.end())
{
auto p=um[key];
p->val=value;
remove(p);
addToHead(p);
}
else
{
DoubleList *node=new DoubleList(key,value); //添加节点时先new新的节点
addToHead(node);
um[key]=node; //添加到哈希表
if(um.size()>n)
{
auto p=end->pre;
remove(end->pre);
um.erase(p->key); //哈希表中的key=p->key 由于不能得到哈希表中的key,所以用p->key代替
delete p; //释放空间
}
}
}
};
/**
* 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);
*/
-
自定义双链表和函数
155. 最小栈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。
class MinStack {
public:
stack<pair<int,int>> sk;
MinStack() {
sk.push(make_pair(INT_MAX,INT_MAX));
}
void push(int val) {
int min;
if(val<sk.top().second)
{
sk.push(make_pair(val, val));
}
else
{
sk.push(make_pair(val,sk.top().second));
}
}
void pop() {
sk.pop();
}
int top() {
return sk.top().first;
}
int getMin() {
return sk.top().second;
}
};
-
栈里保存插入值和当前栈的最小值
-
使用pair<int,int>
160. 相交链表
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
unordered_set<ListNode*> us;
while(headA!=NULL)
{
us.insert(headA);
headA=headA->next;
}
while(headB!=NULL)
{
if(us.count(headB)) return headB;
else
{
headB=headB->next;
}
}
return NULL;
}
};
-
哈希集合法,空间复杂度O(1)
-
us.count()返回0或1,存在返回1,不存在返回0
-
us.find()返回迭代器,存在返回存在的迭代器,不存在返回尾迭代器
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(headA==nullptr||headB==nullptr) return NULL;
ListNode*pa,*pb;
pa=headA;
pb=headB;
while(headA!=headB)
{
if(headA==NULL) headA=pb;
else
{
headA=headA->next;
}
if(headB==NULL) headB=pa;
else
{
headB=headB->next;
}
}
return headB;
}
};
-
双指针法
-
如果不相等,就继续下一个,如果为空就从另一个的头结点遍历
169. 多数元素
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
class Solution {
public:
int majorityElement(vector<int>& nums) {
int n=nums[0],c=1;
for(int i=1;i<nums.size();i++)
{
if(n==nums[i])
{
c++;
}
else
{
c--;
if(c==0)
{
n=nums[i];
c=1;
}
}
}
return n;
}
};
-
摩尔投票算法
-
候选人(cand_num)初始化为nums[0],票数count初始化为1。 当遇到与cand_num相同的数,则票数count = count + 1,否则票数count = count - 1。 当票数count为0时,更换候选人,并将票数count重置为1。 遍历完数组后,cand_num即为最终答案。
198. 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
class Solution {
public:
int rob(vector<int>& nums) {
vector<int> dp(nums.size());
if(nums.size()==1) return nums[0];
if(nums.size()==2) return max(nums[0],nums[1]);
dp[0]=nums[0];
dp[1]=max(nums[0],nums[1]);
for(int i=2;i<nums.size();i++)
{
dp[i]=max(dp[i-1],dp[i-2]+nums[i]);
}
return dp[nums.size()-1];
}
};
-
dp
200. 岛屿数量
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
class Solution {
public:
int ans=0;
int numIslands(vector<vector<char>>& grid) {
for(int i=0;i<grid.size();i++)
{
for(int j=0;j<grid[0].size();j++)
{
if(grid[i][j]=='1')
{
dfs(grid,i,j);
ans++;
}
}
}
return ans;
}
void dfs(vector<vector<char>>& grid,int line,int col)
{
if(line<0||line>=grid.size()||col<0||col>=grid[0].size()) return;
if(grid[line][col]!='1') return;
grid[line][col]='2';
dfs(grid,line-1,col);
dfs(grid,line+1,col);
dfs(grid,line,col-1);
dfs(grid,line,col+1);
}
};
-
dfs
695. 岛屿的最大面积
给你一个大小为 m x n 的二进制矩阵 grid 。
岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
岛屿的面积是岛上值为 1 的单元格的数目。
计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。
class Solution {
public:
int maxAreaOfIsland(vector<vector<int>>& grid) {
int ans=0;
for(int i=0;i<grid.size();i++)
{
for(int j=0;j<grid[i].size();j++)
{
if(grid[i][j]==1)
{
ans=max(ans,dfs(grid,i,j));
}
}
}
return ans;
}
int dfs(vector<vector<int>>& grid,int i,int j)
{
if(i<0||i>=grid.size()||j<0||j>=grid[0].size()) return 0;
if(grid[i][j]!=1) return 0;
grid[i][j]=2;
return dfs(grid,i-1,j)+dfs(grid,i+1,j)+dfs(grid,i,j-1)+dfs(grid,i,j+1)+1;
}
};
-
dfs
-
return 记得+1
206. 反转链表
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur=head;
ListNode* pre=NULL;
while(cur)
{
ListNode *next=cur->next;
cur->next=pre;
pre=cur;
cur=next;
}
return pre;
}
};
-
时间复杂度O(n)
-
空间复杂度O(1)
50. Pow(x, n)
实现 pow(x, n) ,即计算 x
的 n
次幂函数
class Solution {
public:
double myPow(double x, int n) {
double ans=1.0;
long long pow=abs(n);
while(pow)
{
if(pow&1) //pow为奇数
{
ans=ans*x;
}
x*=x;
pow>>=1; //pow/=2
}
if(n<0) ans=1/ans;
return ans;
}
};
-
快速幂
221. 最大正方形
在一个由 '0'
和 '1'
组成的二维矩阵内,找到只包含 '1'
的最大正方形,并返回其面积。
class Solution {
public:
int maximalSquare(vector<vector<char>>& matrix) {
vector<vector<int>> dp(matrix.size(),vector<int>(matrix[0].size()));
int maxL=0;
for(int i=0;i<matrix.size();i++)
{
if(matrix[i][0]=='1')
{
dp[i][0]=1;
maxL=1;
}
else dp[i][0]=0;
}
for(int j=0;j<matrix[0].size();j++)
{
if(matrix[0][j]=='1')
{
dp[0][j]=1;
maxL=1;
}
else dp[0][j]=0;
}
for(int i=1;i<matrix.size();i++)
{
for(int j=1;j<matrix[i].size();j++)
{
if(matrix[i][j]=='1')
dp[i][j]=min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1;
else dp[i][j]=0;
maxL=max(maxL,dp[i][j]);
}
}
return maxL*maxL;
}
};
-
dp(i,j)为以i,j为右下角的最大正方形的边长
226. 翻转二叉树
给你一棵二叉树的根节点 root
,翻转这棵二叉树,并返回其根节点。
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
TreeNode *head=root;
if(root==NULL) return NULL;
f(root);
return head;
}
void f(TreeNode*root)
{
if(root->left==NULL &&root->right==NULL) return;
TreeNode *t=root->left;
root->left=root->right;
root->right=t;
if(root->left!=NULL) f(root->left);
if(root->right!=NULL) f(root->right);
}
};
-
交换节点而不是交换值,树不一定对称
236. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == NULL)
return NULL;
if(root == p || root == q)
return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if(left == NULL)
return right;
if(right == NULL)
return left;
if(left && right) // p和q在两侧
return root;
return NULL; // 必须有返回值
}
};
253. 会议室 II
给你一个会议时间安排的数组 intervals ,每个会议时间都会包括开始和结束的时间 intervals[i] = [starti, endi] ,返回 所需会议室的最小数量 。
class Solution {
public:
int minMeetingRooms(vector<vector<int>>& nums) {
priority_queue<int, vector<int>, greater<int>> pq;
sort(nums.begin(),nums.end());
pq.push(nums[0][1]);
for(int i=1;i<nums.size();i++)
{
if(nums[i][0]<pq.top())
{
pq.push(nums[i][1]);
}
else
{
pq.pop();
pq.push(nums[i][1]);
}
}
return pq.size();
}
};
-
按照 开始时间 对会议进行排序。
-
初始化一个新的 最小堆,将第一个会议的结束时间加入到堆中。我们只需要记录会议的结束时间,告诉我们什么时候房间会空。
-
对每个会议,检查堆的最小元素(即堆顶部的房间)是否空闲。
-
若房间空闲,则从堆顶拿出该元素,将其改为我们处理的会议的结束时间,加回到堆中。
-
若房间不空闲。开新房间,并加入到堆中。
-
处理完所有会议后,堆的大小即为开的房间数量。这就是容纳这些会议需要的最小房间数。
-
128. 最长连续序列
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
sort(nums.begin(),nums.end()); //使数组有序
vector<int> dp(nums.size());
if(nums.size()<=1) return nums.size();
dp[0]=1;
int maxL=INT_MIN;
int p=nums[0]; //p为上一个数
for(int i=1;i<nums.size();i++)
{
if(nums[i]==p+1) //如果下一个数比前一个数大1(连续)
{
dp[i]=dp[i-1]+1;
}
else
{
if(nums[i]==p) //如果下一个数和前一个数相等
{
dp[i]=dp[i-1];
}
else
{
dp[i]=1; //下一个数与前一个数的差大于1,重新计数
}
}
p=nums[i]; //更新上一个数
maxL=max(maxL,dp[i]);
}
return maxL;
}
};
-
dp
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
set<int> s;
int maxL=1;
int ans=0;
for(auto &i:nums) s.insert(i);
for(auto ite=s.begin();ite!=s.end();ite++)
{
if(s.count(*ite-1)) maxL++;
else
{
maxL=1;
}
ans=max(ans,maxL);
}
return ans;
}
};
-
哈希表
347. 前 K 个高频元素
给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int,int> um1;
priority_queue<pair<int,int>, vector<pair<int,int>>, less<pair<int,int>>> pq;
vector<int> vec;
for(int i:nums)
{
um1[i]++;
}
for(auto i:um1)
{
pq.push(make_pair(i.second,i.first));
}
while(k--)
{
vec.push_back(pq.top().second);
pq.pop();
}
return vec;
}
};
-
大根堆
416. 分割等和子集
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum=accumulate(nums.begin(), nums.end(), 0);
if(sum&1) return false;
vector<vector<bool>> dp(nums.size(),vector<bool>(sum/2+1,false));
for(int i=0;i<nums.size();i++)
dp[i][0]=true;
for(int i=1;i<nums.size();i++)
{
for(int j=0;j<=sum/2;j++)
{
dp[i][j]=dp[i-1][j];
if(j-nums[i]>=0)
dp[i][j]=dp[i-1][j]|dp[i-1][j-nums[i]];
}
}
return dp[nums.size()-1][sum/2];
}
};
-
01背包问题
-
其中dp(i,j)表示从【0,i】下标范围内选取若干个正整数(可以是 0 个),是否存在一种选取方案使得被选取的正整数的和等于 j
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum=accumulate(nums.begin(), nums.end(), 0);
if(sum&1) return false;
vector<bool> dp(sum/2+1,0);
dp[0]=true;
for(int i=1;i<nums.size();i++)
{
for(int j=sum/2;j-nums[i]>=0;j--)
{
dp[j]=dp[j]|dp[j-nums[i]];
}
}
return dp[sum/2];
}
};
-
空间优化
-
01背包倒序遍历
739. 每日温度
请根据每日气温列表 temperatures,请计算在每一天需要等几天才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 0来代替。
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& nums) {
vector<int> vec(nums.size());
stack<int> sk;
sk.push(0);
for(int i=1;i<nums.size();i++)
{
while(!sk.empty()&&nums[i]>nums[sk.top()])
{
vec[sk.top()]=i-sk.top();
sk.pop();
}
sk.push(i);
}
return vec;
}
};
-
单调栈,储存数组下标
-
初始化为0
-
如果当前元素大于栈顶元素,栈顶元素出栈,直到当前元素小于栈顶元素或栈为空
-
当前元素入栈
238. 除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请不要使用除法,且在 O(n) 时间复杂度内完成此题。
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
vector<int> vec(nums.size());
vec[0]=1;
for(int i=1;i<nums.size();i++)
{
vec[i]=nums[i-1]*vec[i-1];
}
int r=1;
for(int i=nums.size()-1;i>=0;i--)
{
vec[i]*=r;
r*=nums[i];
}
return vec;
}
};
-
正序遍历数组,下标为i时,vec(i)为计算【0,i-1】的积,特殊:vec(0)=1,
-
倒序遍历数组,用r代表倒序累乘结果,再和输出数组相乘即为结果
309. 最佳买卖股票时机含冷冻期
给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
class Solution {
public:
int maxProfit(vector<int>& prices) {
vector<vector<int>> dp(prices.size(),vector<int>(3));
dp[0][0]=0; //不持股,而且当天没卖出(第二天不是冷冻期)
dp[0][1]=-prices[0]; //持股
dp[0][2]=0; //不持股,当天卖出(第二天是冷冻期)
for(int i=1;i<prices.size();i++)
{
dp[i][0]=max(dp[i-1][0],dp[i-1][2]); //昨天不持股也没卖出或者昨天不持股卖出了
dp[i][1]=max(dp[i-1][0]-prices[i],dp[i-1][1]); //今天继承昨天的股票或者昨天不持
//股且没卖出(不在冷冻期内)今天买入的股票
dp[i][2]=dp[i-1][1]+prices[i];
}
return max(dp[prices.size()-1][0],dp[prices.size()-1][2]); //最后一天肯定卖出才能最大利润
}
};
-
dp
-
见注释
437. 路径总和 III
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
class Solution {
public:
int ans=0;
int pathSum(TreeNode* root, int targetSum) {
if(!root) return 0;
dfs(root,targetSum);
pathSum(root->left,targetSum);
pathSum(root->right,targetSum);
return ans;
}
void dfs(TreeNode* root, int targetSum)
{
if(!root) return ;
targetSum-=root->val;
if(targetSum==0) ans++;
dfs(root->left,targetSum);
dfs(root->right,targetSum);
}
};
-
dfs()遍历路径找是否存在以root为根节点的所有子树和为sum(从root开始往下累加)
-
pathsum从root往下遍历把每个节点当做根节点调用dfs();
337. 打家劫舍 III
小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
class Solution {
public:
int rob(TreeNode* root) {
return max(dfs(root)[0],dfs(root)[1]);
}
vector<int> dfs(TreeNode*root)
{
if(root==nullptr) return{0,0};
vector<int> left=dfs(root->left);
vector<int> right=dfs(root->right);
vector<int> dp(2);
dp[0]=max(left[0],left[1])+max(right[0],right[1]);
dp[1]=left[0]+right[0]+root->val;
return dp;
}
};
-
后序遍历 左右根
-
dp[0]表示不投当前节点,dp[1]表示偷当前节点
-
如果偷当前节点,不能偷左右孩子,则dp[1]=left[0]+right[0]+root->val;
-
如果不偷当前节点,则偷不偷左右孩子都可以,取最大的,即dp[0]=max(left[0],left[1])+max(right[0],right[1]);
-
返回偷或者不偷的最大值
338. 比特位计数
给你一个整数 n
,对于 0 <= i <= n
中的每个 i
,计算其二进制表示中 1
的个数 ,返回一个长度为 n + 1
的数组 ans
作为答案。
class Solution {
public:
vector<int> countBits(int n) {
int ans=0;
vector<int> vec;
for(int i=0;i<=n;i++)
{
ans=0;
int t=i;
while(t)
{
if(t&1) ans++; //判断最低位是否为1
t=t>>1;
}
vec.push_back(ans);
}
return vec;
}
};
-
暴力解法
class Solution {
public:
vector<int> countBits(int n) {
int ans=0;
vector<int> dp(n+1);
dp[0]=0;
for(int i=1;i<=n;i++)
{
if(i&1)
{
dp[i]=dp[i-1]+1;
}
else
{
dp[i]=dp[i/2];
}
}
return dp;
}
};
-
dp
-
i为奇数时最低位为1,等于i-1的个数+1
-
i为偶数时最低位为0,等于i/2的个数(右移1位)