最近准备找实习,开始刷LeetCode~
注:不会写的题都参考了ACWing
1. 两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
你可以按任意顺序返回答案。
思路:
时间复杂度 O ( N ) O(N) O(N)
代码:
C++
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> res;
unordered_map<int,int>hash;
for(int i=0;i<nums.size();++i){
int j=target-nums[i];
if(hash.count(j)){
res={i,hash[j]};
break;
}
hash[nums[i]]=i;
}
return res;
}
};
2. 两数相加
给你两个非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
思路:
时间复杂度 O ( N ) O(N) O(N)
代码:
C++
/**
* 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* addTwoNumbers(ListNode* l1, ListNode* l2) {
auto dummy=new ListNode(-1), cur=dummy;
int t=0;
while(l1 || l2 || t){
if(l1) t += l1->val, l1 = l1->next;
if(l2) t += l2->val, l2 = l2->next ;
/*
auto tmp=new ListNode(t%10); //新建一个节点,表示下一位
cur->next = tmp; //cur的next指针指向新节点
cur = tmp; //cur移向下一位
*/
cur = cur->next = new ListNode(t % 10);
t=t/10; //进位
}
return dummy->next;
}
};
注:
auto tmp=new ListNode(t%10);
tmp是ListNode指针类型,也就是auto是ListNode*而不是ListNode。指针类型访问成员变量要用"->",不能用"."。
3. 无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
思路:
双指针算法。[l,r]区间内字符最多出现一次,如果出现两次,就将l指针不停向后移,直到只出现一次。
时间复杂度 O ( N ) O(N) O(N)
代码:
C++
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int maxn=0;
unordered_map<char,int>cnt;
for(int l=0,r=0;r<s.size();++r){
++cnt[s[r]];
while(cnt[s[r]]==2){
--cnt[s[l++]];
}
maxn=max(maxn,r-l+1);
}
return maxn;
}
};
4. 寻找两个正序数组的中位数
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数。
进阶: 你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?
思路:
时间复杂度
O
(
N
)
O(N)
O(N)
代码:
5. 最长回文子串
给你一个字符串 s,找到 s 中最长的回文子串。
思路:
- 马拉车算法(思想类似于KMP),时间复杂度 O ( N ) O(N) O(N),专用于解决最长回文子串问题,不常用。
- 字符串哈希+二分,时间复杂度 O ( N l o g ( N ) ) O(Nlog(N)) O(Nlog(N)),指路ACWing139. 回文子串的最大长度。
- 枚举,枚举中点,再用两个指针同时向两边延展(分奇偶),直到两个字符不同或某个指针走出边界为止,就可以找到以该中点为中心的最长回文串,时间复杂度 O ( n 2 ) O(n^2) O(n2),此题我们选用这种做法。
- DP
代码:
C++
class Solution {
public:
string longestPalindrome(string s) {
string res;
for(int i=0;i<s.size();++i){
int l=i-1, r=i+1;
while(l>=0&&r<s.size()&&s[l]==s[r]) --l, ++r; //O(N)
if(res.size()<r-l-1) res=s.substr(l+1,r-l-1); //O(N)
l=i, r=i+1;
while(l>=0&&r<s.size()&&s[l]==s[r]) --l, ++r;
if(res.size()<r-l-1) res=s.substr(l+1,r-l-1);
}
return res;
}
};
6. Z 字形变换
将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:
P A H N
A P L S I I G
Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“PAHNAPLSIIGYIR”。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
思路:
找规律
代码:
C++
10. 正则表达式匹配
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
‘.’ 匹配任意单个字符
‘*’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖整个字符串s的,而不是部分字符串。
思路:
会存在很多匹配方案,因为*可以匹配多个字符。
要在所有匹配方案里判断是否有可行解存在。
如果搜索全部这些方案,复杂度是指数级别的,会超时。
动态规划!
将一类方案看做一个集合,状态表示和状态计算如下图。
这题的优化和完全背包一样。
时间复杂度
O
(
N
2
)
O(N^2)
O(N2)
代码:
C++
class Solution {
public:
bool isMatch(string s, string p) {
int n=s.size(), m=p.size();
s=' '+s,p=' '+p;
vector<vector<bool>> f(n+1,vector<bool>(m+1));
f[0][0]=true;
for(int i=0;i<=n;++i){
for(int j=1;j<=m;++j){ //
if(j+1<=m && p[j+1]=='*') continue;
if(i && p[j]!='*'){
f[i][j]=f[i-1][j-1] && (s[i]==p[j]||p[j]=='.');
}else if(p[j]=='*'){
f[i][j]=f[i][j-2] || i && f[i-1][j]&&(s[i]==p[j-1]||p[j-1]=='.');
}
}
}
return f[n][m];
}
};
17. 电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
思路:
dfs的过程:逐位填字母,每一位填完遍历下一位,直到填满位数。
回溯:每一位的可选字母全部填完,回到上一位,改变字母,再往下填。
时间复杂度 O ( 4 n × n ) O(4^n \times n ) O(4n×n):每一位最多有4种选择(“wxyz”),递归的时间复杂度 O ( 4 n ) O(4^n) O(4n),再乘上push_back答案一次是 O ( n ) O(n) O(n)。
dfs函数第一个参数是digits,因为digits不是全局变量,所以要带着它跑。
代码:
C++
class Solution {
public:
string str[10]={
"", "", "abc", "def",
"ghi", "jkl", "mno",
"pqrs", "tuv", "wxyz"};
vector<string> res;
vector<string> letterCombinations(string digits) {
if(digits.empty()) return res;
dfs(digits, 0, "");
return res;
}
void dfs(string& digits, int u, string tmp){
if(u==digits.size()){
res.push_back(tmp);
//return; //可省
}else{
for(auto e: str[digits[u]-'0']){
dfs(digits, u+1, tmp+e);
}
}
}
};
32. 最长有效括号
给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
思路:
合法括号序列两个性质:
- 左右括号数相等
- 任意前缀中,左括号数>=右括号数
时间复杂度 O ( N ) O(N) O(N)
代码:
C++
44. 通配符匹配
给定一个字符串 s 和一个字符模式 p ,实现一个支持 ‘?’ 和 ‘*’ 的通配符匹配。
‘?’ 可以匹配任何单个字符。
‘*’ 可以匹配任意字符串(包括空字符串)。
两个字符串完全匹配才算匹配成功。
思路:
类似第10题。
具体分析见下图。
时间复杂度
O
(
N
2
)
O(N^2)
O(N2)
代码:
C++
class Solution {
public:
bool isMatch(string s, string p) {
int n=s.size(), m=p.size();
s=' '+s, p=' '+p;
vector<vector<bool>> f(n+1, vector<bool>(m+1));
f[0][0]=true;
for(int i=0;i<=n;++i){
for(int j=1;j<=m;++j){
if(p[j]!='*'){
f[i][j]= i && f[i-1][j-1] && (s[i]==p[j] || p[j]=='?');
}else{
f[i][j]=f[i][j-1] || i && f[i-1][j];
}
}
}
return f[n][m];
}
};
53. 最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
思路:
在线算法的核心思想基于下面的事实:
如果整数序列 { a 1 , a 2 , . . . , a n } \{a_1, a_2, ..., a_n\} {a1,a2,...,an}的最大和子列是 { a i , a i + 1 , . . . , a j } \{ a_{i}, a_{i+1}, ..., a_{j} \} {ai,ai+1,...,aj},那么必定有 ∑ k = i l a k ≥ 0 \sum_{k=i}^{l} a_k \geq 0 ∑k=ilak≥0 对任意 i ≤ l ≤ j i\le l\le j i≤l≤j 成立。因此,一旦发现当前子列和为负,则可以重新开始考察一个新的子列。
时间复杂度 O ( N ) O(N) O(N)
代码:
C++
class Solution {
public:
int maxSubArray(vector<int>& nums) {
long long tmp=0, maxn=-3e9;
for(int i=0;i<nums.size();++i){
tmp+=nums[i];
maxn=max(maxn,tmp); //注意 此句在前
tmp=max(tmp,0ll); //注意 此句在后
}// 否则全负数会出问题
return maxn;
}
};
72. 编辑距离
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
思路:
动态规划-编辑距离问题,详见下图。
代码:
C++
class Solution {
public:
int minDistance(string word1, string word2) {
int n=word1.size(), m=word2.size();
word1=' '+word1, word2=' '+word2;
vector<vector<int>> f(n+1, vector<int>(m+1));
for(int i=1;i<=n;++i) f[i][0]=i;
for(int j=1;j<=m;++j) f[0][j]=j;
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
f[i][j]=min(min(f[i-1][j],f[i][j-1])+1, f[i-1][j-1]+(word1[i]!=word2[j]));
}
}
return f[n][m];
}
};
206. 反转链表
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
思路:
迭代:
递归:
两种方法的时间复杂度都是
O
(
N
)
O(N)
O(N)
代码:
C++
迭代
/**
* 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 || !head->next) return head; //如果是空链表或只剩一个结点 就返回
auto a=head,b=head->next;
while(b){
auto c=b->next;
b->next=a; //b指向a
a=b,b=c; //a、b各向后移
}
head->next=NULL;
return a;
}
};
递归
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(!head->next || !head) return head;
auto tail=reverseList(head->next);
head->next->next=head; //让head->next反过来指向head(本来是指向NULL的)
head->next=NULL;
return tail; //新的头结点(原链表的最后一个结点)
}
};
92. 反转链表 II
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
示例 1:
输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]
思路:
时间复杂度 O ( N ) O(N) O(N)
代码:
C++
/**
* 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* reverseBetween(ListNode* head, int left, int right) {
//if(!head || !head->next) return head;
auto dummy=new ListNode(-1);
dummy->next=head;
auto a=dummy;
for(int i=0;i<left-1;++i){ //a从dummy移动到left前一个结点
a=a->next;
}
auto b=a->next,c=b->next; //b指向left
for(int i=0;i<right-left;++i){ //此过程结束后 b指向right
auto d=c->next;
c->next=b;
b=c,c=d;
}
a->next->next=c; //left的next指针指向right后面的一个结点
a->next=b; //a的next指针指向right
return dummy->next;
}
};
96. 不同的二叉搜索树
给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?
示例:
输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
思路:
- 动态规划
O
(
n
2
)
O(n^2)
O(n2)
状态表示: f [ n ] f[n] f[n] 表示 n个节点的二叉搜索树共有多少种。
状态转移:左子树可以有 0 , 1 , … n − 1 0,1,…n−1 0,1,…n−1 个节点,对应的右子树有 n − 1 , n − 2 , … , 0 n−1,n−2,…,0 n−1,n−2,…,0 个节点, f [ n ] f[n] f[n] 是所有这些情况的加和,所以 f [ n ] = ∑ k = 0 n − 1 f [ k ] ∗ f [ n − 1 − k ] f[n]=∑^{n−1}_{k=0}f[k]∗f[n−1−k] f[n]=∑k=0n−1f[k]∗f[n−1−k] 。
时间复杂度分析:状态总共有 n n n 个,状态转移的复杂度是 O ( n ) O(n) O(n),所以总时间复杂度是 O ( n 2 ) O(n^2) O(n2)。 - 卡特兰数
结论:n个节点的二叉搜索树一共有 1 n + 1 C 2 n n \frac{1}{n+1}C^n_{2n} n+11C2nn种。
O ( n 2 ) O(n^2) O(n2)杨辉三角递推求组合数。
代码:
1.动态规划
class Solution {
public:
int numTrees(int n) {
vector<int>f(n+1);
f[0]=1;
for(int i=1;i<=n;++i){
for(int j=0;j<i;++j){
f[i]+=f[j]*f[i-j-1];
}
}
return f[n];
}
};
2.卡特兰数(注意爆int)
class Solution {
public:
long long c[2005][2005]={0};
int numTrees(int n) {
int a=n*2,b=n;
for(int i=0;i<=a;++i){
for(int j=0;j<=i;++j){
if(!j) c[i][j]=1;
else c[i][j]=c[i-1][j]+c[i-1][j-1];
}
}
return c[a][b]/(n+1);
}
};
97. 交错字符串
给定三个字符串 s1、s2、s3,请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。
两个字符串 s 和 t 交错的定义与过程如下,其中每个字符串都会被分割成若干非空子字符串:
s = s 1 + s 2 + . . . + s n s = s_1 + s_2 + ... + s_n s=s1+s2+...+sn
t = t 1 + t 2 + . . . + t m t = t_1 + t_2 + ... + t_m t=t1+t2+...+tm
∣ n − m ∣ ≤ 1 |n - m| \le 1 ∣n−m∣≤1
交错 是 s 1 + t 1 + s 2 + t 2 + s 3 + t 3 + . . . s_1 + t_1 + s_2 + t_2 + s_3 + t_3 + ... s1+t1+s2+t2+s3+t3+... 或者 t 1 + s 1 + t 2 + s 2 + t 3 + s 3 + . . . t_1 + s_1 + t_2 + s_2 + t_3 + s_3 + ... t1+s1+t2+s2+t3+s3+...
提示: a + b a + b a+b 意味着字符串 a a a 和 b b b 连接。
思路:
(动态规划)
O
(
n
2
)
O(n^2)
O(n2)
状态表示:
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示 s1 的前
i
i
i 个字符和 s2 的前
j
j
j 个字符是否可以交错组成 s3 的前
i
+
j
i+j
i+j 个字符。
状态转移:如果
s
3
[
i
+
j
]
s_3[i+j]
s3[i+j] 匹配
s
1
[
i
]
s_1[i]
s1[i],则问题就转化成了
f
[
i
−
1
]
[
j
]
f[i−1][j]
f[i−1][j];如果
s
3
[
i
+
j
]
s_3[i+j]
s3[i+j] 匹配
s
2
[
j
]
s_2[j]
s2[j],则问题就转化成了
f
[
i
]
[
j
−
1
]
f[i][j−1]
f[i][j−1]。两种情况只要有一种为真,则
f
[
i
]
[
j
]
f[i][j]
f[i][j] 就为真。
时间复杂度分析:状态总共有 n 2 n^2 n2个,状态转移复杂度是 O ( 1 ) O(1) O(1)。所以总时间复杂度是 O ( n 2 ) O(n^2) O(n2)。
代码:
C++
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
int n=s1.size(), m=s2.size();
if(n+m!=s3.size()) return 0;
s1 = ' ' + s1, s2 = ' ' + s2, s3 = ' ' + s3;
vector<vector<bool>> f(n+1,vector<bool>(m+1));
for(int i=0;i<=n;++i){
for(int j=0;j<=m;++j){
if(!i&&!j) f[i][j]=1;
if(i && s1[i]==s3[i+j]) f[i][j]=f[i-1][j];
if(j && s2[j]==s3[i+j]) f[i][j]=f[i][j] || f[i][j-1];
}
}
return f[n][m];
}
};
115. 不同的子序列
给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。
字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)
题目数据保证答案符合 32 位带符号整数范围。
思路:
时间复杂度 O ( N 2 ) O(N^2) O(N2)
代码:
C++
class Solution {
public:
int numDistinct(string s, string t) {
int n=s.size(), m=t.size();
s=' '+s, t=' '+t;
vector<vector<unsigned long long>> f(n+1, vector<unsigned long long>(m+1));
for(int i=0;i<=n;++i) f[i][0]=1;
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
f[i][j]=f[i-1][j];
if(s[i]==t[j]){ //选s[i]匹配
f[i][j]+=f[i-1][j-1];
}
}
}
return f[n][m];
}
};
516. 最长回文子序列
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
思路:
待填
代码:
C++
class Solution {
public:
int longestPalindromeSubseq(string s) {
int n=s.size();
vector<vector<int>> f(n, vector<int>(n));
for(int len=1;len<=n;++len){
for(int i=0;i+len-1<n;++i){
int j=i+len-1;
if(i==j) f[i][j]=1;
else{
if(s[i]==s[j]) f[i][j]=f[i+1][j-1]+2;
f[i][j]=max(f[i][j], max(f[i+1][j],f[i][j-1]));
}
}
}
return f[0][n-1];
}
};
583. 两个字符串的删除操作
给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。
思路:
答案 = (n - 最长公共子序列) + (m - 最长公共子序列)
代码:
C++
class Solution {
public:
int minDistance(string word1, string word2) {
int n=word1.size(), m=word2.size();
word1=' '+word1, word2=' '+word2;
vector<vector<int>> f(n+1, vector<int>(m+1));
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
f[i][j]=max(f[i-1][j],f[i][j-1]);
if(word1[i]==word2[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
}
}
return n+m-2*f[n][m];
}
};
思路:
时间复杂度
O
(
N
)
O(N)
O(N)
代码:
C++