Day 1 求和问题
1. 两数之和 [数组 哈希表]
两数之和 🔗
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值target的那两个整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
思路一:暴力解法
思路:
一层遍历:从左到右 依次为i
二层遍历:从i到右 依次为j
check:是否第i个和第j个数字加和为target
时间复杂度:
O
(
N
2
)
O(N^2)
O(N2)
空间复杂度:
O
(
1
)
O(1)
O(1)
思路二 哈希表
一次遍历:从左到右
check哈希表中有无当前值的补数
有,则直接输出答案
无,则向哈希表中添加当前key及index
时间复杂度:
O
(
N
)
O(N)
O(N)
空间复杂度:
O
(
N
)
O(N)
O(N),多为哈希表开销
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> hashmap;
int other;
for(int i=0;i<nums.size();++i){
auto it = hashmap.find(target - nums[i]);
if (it != hashmap.end()) { return {it->second, i}; }
hashmap.insert(pair<int,int>(nums[i],i));
}
return {};
}
167. 两数之和 II - 输入有序数组【数组 双指针 二分查找】
两数之和 II - 输入有序数组 🔗
给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。
函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length 。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
注:本题是有序数组,是1的特例
思路一:二分查找
思路:
一层遍历:从左到右 依次为i——固定一值 O(N)
在i右侧查找num[i]的补值(利用二分法) O(logN)
时间复杂度:
O
(
N
l
o
g
N
)
O(NlogN)
O(NlogN)
空间复杂度:
O
(
1
)
O(1)
O(1)
思路二 双指针
left指左,right指右
两个元素之和与target进行比较
大:右-1
小:左+1
==:getAnswer
时间复杂度:
O
(
N
)
O(N)
O(N)
空间复杂度:
O
(
1
)
O(1)
O(1)
vector<int> twoSum(vector<int>& numbers, int target) {
int low = 0, high = numbers.size() - 1;
while (low < high) {
int sum = numbers[low] + numbers[high];
if (sum == target) { return {low + 1, high + 1}; }
else if (sum < target) { ++low; }
else { --high;}
}
return {-1, -1};
}
Day 2 求和问题
15. 三数之和 [数组 双指针 排序]
三数之和 🔗
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
思路:排序+双指针
思路:
排序
一层遍历:从左到右 依次为 first [0,nsize-2) //确定第一个值 及 其余两值范围为右侧
二层遍历:从first+1到右 依次为left
从nsize-1到左 依次为right
转换为两数之和的问题
check:是否第left个和第right个数字加和为 -first
时间复杂度:
O
(
N
2
)
O(N^2)
O(N2)
空间复杂度:
O
(
l
o
g
N
)
O(logN)
O(logN)
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
int nSize=nums.size();
if(nSize<3) return res;
sort(nums.begin(),nums.end());
int second=1,third=2;
for(int first=0;first<nSize-2;++first ){
if(first>0&& nums[first]==nums[first-1]) { continue; }
if(nums[first]>0) break;
int right=nSize-1;
int third=right;
int target=-nums[first];
for(int second=first+1;second<nSize-1;++second){
if(second>first+1&& nums[second]==nums[second-1]) { continue; }
while(third>second&&nums[second] + nums[third] > target){ third--;}
if (second == third) { break; }
if (nums[second] + nums[third] == target) {
res.push_back({nums[first], nums[second], nums[third]});
}
}
}
return res;
}
18. 四数之和 [数组 双指针 排序]
四数之和 🔗
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [ n u m s [ a ] , n u m s [ b ] , n u m s [ c ] , n u m s [ d ] ] [nums[a], nums[b], nums[c], nums[d]] [nums[a],nums[b],nums[c],nums[d]] :
- 0 < = a , b , c , d < n 0 <= a, b, c, d < n 0<=a,b,c,d<n
- a、b、c 和 d 互不相同
- n u m s [ a ] + n u m s [ b ] + n u m s [ c ] + n u m s [ d ] = = t a r g e t nums[a] + nums[b] + nums[c] + nums[d] == target nums[a]+nums[b]+nums[c]+nums[d]==target
你可以按 任意顺序 返回答案 。
思路:排序+双指针
思路:确定两值,以 转换为二数之和的问题
排序
一层遍历:从左到右 依次为 first [0,nsize-2) //确定第一个值 及 其余三值范围为右侧
二层遍历:从左到右 依次为 second [first+1,nsize-2) //确定第2个值 及 其余两值范围为右侧
二层遍历:从second +1到右 依次为left
从nsize-1到左 依次为right
转换为两数之和的问题
check:是否第left个和第right个数字加和为 -first -second
时间复杂度:
O
(
N
3
)
O(N^3)
O(N3)
空间复杂度:
O
(
l
o
g
N
)
O(logN)
O(logN)
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
int s=nums.size();
if(s<4) return res;
sort(nums.begin(),nums.end());
for(int first=0;first<s-3;++first ){
if(first>0&& nums[first]==nums[first-1]) continue;
for(int second=first+1;second<s-2;++second){
if(second>first+1&& nums[second]==nums[second-1]) continue;
int t2=target-nums[first]-nums[second];
int right=s-1;
for(int third=second+1;third<s-1;++third){
int forth=right;
if(third>second+1&& nums[third]==nums[third-1]) continue;
while(forth>third&&nums[forth] + nums[third] > t2) forth--;
if (forth == third) break;
if (nums[forth] + nums[third] == t2) {
right=forth;
res.push_back({nums[first], nums[second], nums[third],nums[forth]});
}
}
}
}
return res;
}
Day 3 斐波那契数列
509. 斐波那契数列 [递归 记忆化搜索 数学] E
509 斐波那契数 🔗
斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F ( 0 ) = 0 , F ( 1 ) = 1 F(0) = 0,F(1) = 1 F(0)=0,F(1)=1 , F ( n ) = F ( n − 1 ) + F ( n − 2 ) , 其 中 n > 1 F(n) = F(n - 1) + F(n - 2),其中 n > 1 F(n)=F(n−1)+F(n−2),其中n>1
给你 n ,请计算 F(n) 。
思路:记忆化递归
思路:
固定变量 f0, f1 恒存储F(n-2) F(n-1)
迭代获取F(n)即可
时间复杂度:
O
(
N
)
O(N)
O(N)
空间复杂度:
O
(
1
)
O(1)
O(1)
int fib(int n) {
if(n<2)return n;
int f0=0,f1=1,res;
while(n>1){
res=f1+f0;
f0=f1;
f1=res;
--n;
}
return res;
}
70. 爬楼梯 [记忆化搜索 数学 动态规划]
70 爬楼梯 🔗
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 注意:给定 n 是一个正整数。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
思路:记忆化递归
思路:
固定变量 f0, f1 恒存储F(n-2) F(n-1)
迭代获取F(n) = F(n - 1) + F(n - 2)即可
时间复杂度:
O
(
N
)
O(N)
O(N)
空间复杂度:
O
(
1
)
O(1)
O(1)
int climbStairs(int n) {
if(n<3)return n;
int f1=1,f2=2,res;
while((n--)>2){
res=f1+f2;
f1=f2;
f2=res;
}
return res;
}
Day 4 动态规划法
53. 最大子序和 [数组 分治 动态规划] E
53 最大子序和 🔗
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
思路:记忆化递归
思路:
动态规划转移方程:
f
(
i
)
=
max
{
f
(
i
−
1
)
+
f(i)=\max \{f(i-1)+
f(i)=max{f(i−1)+ nums
[
i
]
[i]
[i], nums
[
i
]
}
[i]\}
[i]}
时间复杂度:
O
(
N
)
O(N)
O(N)
空间复杂度:
O
(
1
)
O(1)
O(1)
int maxSubArray(vector<int>& nums) {
int pre = 0, maxAns = nums[0];
for (const auto &x: nums) {
pre = max(pre + x, x);
maxAns = max(maxAns, pre);
}
return maxAns;
}
416. 分割等和子集 [数组 动态规划] M
416 分割等和子集 🔗
给一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
思路:动态规划
思路:
动态规划转移方程:
p
[
j
]
=
d
p
[
j
]
∣
d
p
[
j
−
n
u
m
s
[
i
]
]
p[j]=dp[j] ∣ dp[j−nums[i]]
p[j]=dp[j]∣dp[j−nums[i]]
时间复杂度:
O
(
n
×
t
a
r
g
e
t
)
O(n×target)
O(n×target)
空间复杂度:
O
(
t
a
r
g
e
t
)
O(target)
O(target)
bool canPartition(vector<int>& nums) {
int sum=accumulate(nums.begin(),nums.end(),0);//求取总和
if(sum%2) return false; //若有余 则无法均分
int target=sum/2;
int n=nums.size();
vector<bool>dp(target+1,false);//看是否可达成target的总和
dp[0]=true;
for(int i=0;i<nums.size();++i){
for(int j=target;j>=nums[i];--j){
dp[j]=dp[j]||dp[j-nums[i]];
}
}
return dp[target];
}
322. 零钱兑换 [广度优先搜索 数组 动态规划] M
322 零钱兑换 🔗
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
1 < = c o i n s . l e n g t h < = 12 1 < = c o i n s [ i ] < = 2 31 − 1 0 < = a m o u n t < = 1 0 4 1 <= coins.length <= 12\\1 <= coins[i] <= 2^{31} - 1 \\0 <= amount <= 10^4 1<=coins.length<=121<=coins[i]<=231−10<=amount<=104
思路:动态规划
思路:
动态规划转移方程:
d
p
[
a
m
o
u
n
t
]
=
m
i
n
(
d
p
[
a
m
o
u
n
t
]
,
d
p
[
a
m
o
u
n
t
−
c
o
i
n
s
[
i
]
]
+
1
)
dp[amount]=min(dp[amount], dp[amount−coins[i]]+1)
dp[amount]=min(dp[amount],dp[amount−coins[i]]+1)
时间复杂度:
O
(
a
m
o
u
n
t
×
c
o
i
n
s
.
s
i
z
e
(
)
)
O(amount×coins.size())
O(amount×coins.size())
空间复杂度:
O
(
a
m
o
u
n
t
)
O(amount)
O(amount)
const int top=10001;
int coinChange(vector<int>& coins, int amount) {
if(amount==0)return 0;
sort(coins.begin(),coins.end());
int n=coins.size();
vector<int> dp(amount+1,top);
dp[0]=0;
for(int j=coins[0];j<=amount;++j){
for(int i=0;i<n;++i){
if(j<coins[i])break;
dp[j]=min(dp[j],dp[j-coins[i]]+1);
}
}
return dp[amount]==top?-1:dp[amount];
}
Day 5 堆栈
20.有效的括号 [栈 字符串] E
20 有效的括号 🔗
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。
思路:遍历+栈
思路:
将左括号放入栈 右括号消除左括号, 看可否成功消除及最终栈是否为空
时间复杂度:
O
(
N
)
O(N)
O(N)
空间复杂度:
O
(
1
)
O(1)
O(1)
char isright(char &c){
char left='#';//isleft;
if(c==')')left='(';
else if(c=='}')left='{';
else if(c==']')left='[';
return left;
}
bool isValid(string s) {
if(s.length()%2) return false;
stack<char> stk;
char left;
for(char& c:s){
left=isright(c);
if(left=='#')stk.push(c);
else{
if(stk.empty()||stk.top()!=left)return false;
stk.pop();
}
}
return stk.empty();
}
496. 下一个更大元素I [栈 数组 哈希表 单调栈] E
496 下一个更大元素I 🔗
给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。
请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。
nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。
思路:哈希表+单调栈
- 对 nums2 中的每一个元素,求出它的右边第一个更大的元素;//单调栈的方法
- 将上一步的对应关系放入哈希表(HashMap)中;
- 遍历数组 nums1,根据哈希表找出答案。
时间复杂度: O ( N + M ) O(N+M) O(N+M),分别遍历数组 nums1 和数组 nums2 各一次即可
空间复杂度: O ( N ) O(N) O(N)
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
unordered_map<int,int>hashmap;
int res=-1;
stack<int>stk;
stk.push(nums2[0]);
for(int i=1;i<nums2.size();++i){
while(!stk.empty()&&stk.top()<nums2[i]){
hashmap[stk.top()]=nums2[i];
stk.pop();
}
stk.push(nums2[i]);
}
while(!stk.empty()){
hashmap[stk.top()]=-1;
stk.pop();
}
vector<int>ans(nums1.size());
for(int i=0;i<nums1.size();++i){
ans[i]=hashmap[nums1[i]];
}
return ans;
}
456. 132模式 [栈 数组 二分查找 有序集合] M
456 132模式 🔗
给你一个整数数组 nums ,数组中共有 n 个整数。132 模式的子序列 由三个整数 nums[i]、nums[j] 和 nums[k] 组成,并同时满足:i < j < k 和 nums[i] < nums[k] < nums[j] 。
如果 nums 中存在 132 模式的子序列 ,返回 true ;否则,返回 false 。
思路:动态规划
思路:
时间复杂度:
O
(
n
)
O(n)
O(n)。枚举
i
i
i 的次数为
O
(
n
)
O(n)
O(n),由于每一个元素最多被加入和弹出单调栈各一次,因此操作单调栈的时间复杂度一共为
O
(
n
)
O(n)
O(n),总时间复杂度为
O
(
n
)
O(n)
O(n)。
空间复杂度:
O
(
n
)
O(n)
O(n),即为单调栈需要使用的空间。
bool find132pattern(vector<int>& nums) {
int n = nums.size();
stack<int> candidate_k;
candidate_k.push(nums[n - 1]);
int max_k = INT_MIN;
for (int i = n - 2; i >= 0; --i) {
if (nums[i] < max_k) {
return true;
}
while (!candidate_k.empty() && nums[i] > candidate_k.top()) {
max_k = candidate_k.top();
candidate_k.pop();
}
if (nums[i] > max_k) {
candidate_k.push(nums[i]);
}
}
return false;
}
Day 6 数字
119.杨辉三角II [数组 动态规划] E
119 杨辉三角 II 🔗
给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
思路:一维动态规划
思路:
从右往左层序更新
时间复杂度:
O
(
r
o
w
I
n
d
e
x
2
)
O(rowIndex^2)
O(rowIndex2)
空间复杂度:
O
(
1
)
O(1)
O(1) 不考虑返回值的空间占用。
vector<int> getRow(int rowIndex) {
vector <int> res(rowIndex+1,1);
for(int r=2;r<=rowIndex;++r){
for(int i=r-1;i>0;--i){
res[i]+=res[i-1];
}
}
return res;
}
279. 完全平方数 [DFS 数字 动态规划] M
279 完全平方数 🔗
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
思路:动态规划
状态转移。
时间复杂度:
O
(
n
n
)
O(n\sqrt n)
O(nn)
空间复杂度:
O
(
n
)
O(n)
O(n)
int numSquares(int n) {
vector<int> dp(n + 1, INT_MAX);
dp[0] = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j * j <= i; ++j) {
dp[i] = min(dp[i], dp[i-j*j] + 1);
}
}
return dp[n];
}
483. 最小好进制 [数字 二分查找] H
483 最小好进制 🔗
对于给定的整数 n, 如果n的k(k>=2)进制数的所有数位全为1,则称 k(k>=2)是 n 的一个好进制。
以字符串的形式给出 n, 以字符串的形式返回 n 的最小好进制。
思路:数学
思路:
string smallestGoodBase(string n) {
long nVal = stol(n);
int mMax = floor(log(nVal) / log(2));
for (int m = mMax; m > 1; m--) {
int k = pow(nVal, 1.0 / m);
long mul = 1, sum = 1;
for (int i = 0; i < m; i++) {
mul *= k;
sum += mul;
}
if (sum == nVal) {
return to_string(k);
}
}
return to_string(nVal - 1);
}
Day 7 树
112.路径总和 [树 dfs 二叉树] E
112 路径总和 🔗
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。
叶子节点 是指没有子节点的节点。
思路:迭代+dfs
思路:
时间复杂度:
O
(
N
)
O(N)
O(N) N:树的节点数。对每个节点访问一次
空间复杂度:
O
(
H
)
O(H)
O(H),
H
H
H 是树的高度。空间复杂度主要取决于递归时栈空间的开销,最坏情况下,树呈现链状,空间复杂度为
O
(
N
)
O(N)
O(N)。平均情况下树的高度与节点数的对数正相关,空间复杂度为
O
(
l
o
g
N
)
O(logN)
O(logN)。
bool hasPathSum(TreeNode* root, int targetSum) {
if(!root) return false;
if(!root->left&&!root->right) return root->val==targetSum;
return ( root->left&&hasPathSum(root->left,targetSum-root->val))||(root->right&&hasPathSum(root->right,targetSum-root->val));
}
230. 二叉搜索树中第K小的元素 [树 DFS 二叉搜索树] M
230 二叉搜索树中第K小的元素 🔗
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。
思路:动态规划
int count=0;
int ans;
void search(TreeNode* t,int k){
if(t->left!=nullptr) search(t->left,k);
count++;
if(count==k){ ans=t->val; return; }
else if(count>k) return ;
if(t->right!=nullptr) search(t->right,k);
}
int kthSmallest(TreeNode* root, int k) {
if(root==nullptr) return 0;
search(root,k);
return ans;
}
968. 监控二叉树 [树 DFS 动态规划] H
968 监控二叉树 🔗
给定一个二叉树,我们在树的节点上安装摄像头。节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。计算监控树的所有节点所需的最小摄像头数量。
思路:数学
思路:
struct Status {
int a, b, c;
};
Status dfs(TreeNode* root) {
if (!root) return {INT_MAX / 2, 0, 0};
auto [la, lb, lc] = dfs(root->left);
auto [ra, rb, rc] = dfs(root->right);
int a = lc + rc + 1;
int b = min(a, min(la + rb, ra + lb));
int c = min(a, lb + rb);
return {a, b, c};
}
int minCameraCover(TreeNode* root) {
auto [a, b, c] = dfs(root);
return b;
}
Day 8 字符串
720.词典中最长的单词 [字典树 数组 哈希表 字符串] E
720 词典中最长的单词 🔗
给出一个字符串数组words组成的一本英语词典。从中找出最长的一个单词,该单词是由words词典中其他单词逐步添加一个字母组成。若其中有多个可行的答案,则返回答案中字典序最小的单词。
若无答案,则返回空字符串。
思路:哈希表
string longestWord(vector<string>& words) {
sort(words.begin(), words.end());//从小到大排序
unordered_set<string> mySet;
string ans = ""; //从空开始记录
mySet.insert(ans);
for(auto& word: words){
if(mySet.find(string(word.begin(), word.end() - 1)) != mySet.end()){
if(word.size() > ans.size()) ans = word; //如果长度更长,更新答案
mySet.insert(word); //记录这个单词
}
}
return ans;
}
3. 无重复字符的最长字串 [哈希表 字符串 滑动窗口] M
3 无重复字符的最长字串 🔗
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
思路:哈希表+滑动窗口
状态转移。
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
int lengthOfLongestSubstring(string s) {
unordered_map<char,int>hashmap;
int res=0, start=0;
for(int i=0;i<s.size();++i){
if(hashmap.count(s[i])>0&&hashmap[s[i]]>=start){
res=max(res,i-start);
start=hashmap[s[i]]+1;
}
hashmap[s[i]]=i;
}
res=max(res,(int)s.size()-start);
return res;
}
97. 交错字符串 [字符串 动态规划] M 📓
97 交错字符串 🔗
给定三个字符串 s1、s2、s3,请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。
两个字符串 s 和 t 交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串:
s = s1 + s2 + … + sn
t = t1 + t2 + … + tm
|n - m| <= 1
交错 是 s1 + t1 + s2 + t2 + s3 + t3 + … 或者 t1 + s1 + t2 + s2 + t3 + s3 + …
提示:a + b 意味着字符串 a 和 b 连接。
思路:数学
思路:
bool isInterleave(string s1, string s2, string s3) {
int l1 = s1.length(), l2 = s2.length(), l3 = s3.length();
s1.insert(s1.begin(),'0');
s2.insert(s2.begin(),'0');
s3.insert(s3.begin(),'0');
if(l3 != l1 + l2) return false;
bool dp[l3+1][l1+1];
for(int i = 0; i <= l3; ++i)
memset(dp[i], false, sizeof(dp[i]));
for(int i = 0; i <= l1; ++i)
dp[0][i] = true;
for(int i = 1; i <= l3; ++i)
for(int j = 0; j <= min(i, l1); ++j) {
int k = i - j;
dp[i][j] = (j>=1 && dp[i-1][j-1] && s1[j] == s3[i]) || (dp[i-1][j] && s2[k]==s3[i]);
}
return dp[l3][l1];
}