力扣100
两数之和 E
双重循环
查找表(与顺序无关,用哈希表)
- 哈希表
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
map<int,int> a;
vector<int> b(2,-1);
for(int i=0;i<nums.size();i++){
if(a.count(target-nums[i])>0){
b[0]=a[target-nums[i]];
b[1]=i;
break;
}
else a[nums[i]]=i;
}
return b;
};
};
- 二叉排序树
两数相加 M
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode *head=nullptr,*tail=nullptr; //新节点指针
int carry=0; //保存进位
while(l1||l2){ //l1与l2均未遍历完
int n1=l1?l1->val:0; //若仍有则取指,无则取0
int n2=l2?l2->val:0;
int sum=n1+n2+carry;
if(!head){ //新建头结点
head=tail=new ListNode(sum%10);
}else{
tail->next=new ListNode(sum%10);//新建下一节点
tail = tail->next; //尾指针后移
}
carry=sum/10;
if(l1){ //若l1 l2仍存在,指针后移
l1=l1->next;
}
if(l2){
l2=l2->next;
}
}
if(carry>0){ //最高位有进位,新建节点保存
tail->next=new ListNode(carry);
}
return head;
}
};
- 时间复杂度 O(n)
- 空间复杂度 O(1)
无重复字符的最长子串 M
暴力解
- 时间复杂度 O(n³)
- 找到所有子串 双重循环,一层定头一层定尾 O(n²)
- 判断是否无重复:Hash Set O(n)
- 空间复杂度 O(m) m为哈希中可能出现的字符数
滑动窗口
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_set<char> occ;
int n = s.size();
int left=0,ans=0;
for(int right=0;right<n;right++){
//判断下一字符是否在子串中存在,若无则应在哈希表尾
while(occ.find(s[right])!=occ.end()){
occ.erase(s[left]); //子串左侧擦除一位
left++; //左指针向右移动一位
}
ans = max(ans,right-left+1); //取较长子串值
occ.insert(s[right]); //将下一字符插入子串尾
}
return ans;
}
};
- 时间复杂度 O(n)
- 空间复杂度 O(m) m为哈希中可能出现的字符数
寻找两个正序数组的中位数 H
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
if(nums1.size()>nums2.size()){ //将短数组作为第一个参数
return findMedianSortedArrays(nums2,nums1);
}
int m=nums1.size();
int n=nums2.size();
int left=0,right=m; //短数组的左右侧
int medianLeft=0,medianRight=0; //分界线左右的最值
while(left<=right){
int i=(left+right)/2; //短数组的中位数
int j=(m+n+1)/2-i; //长数组的中位数
int nums_iml = (i==0?INT_MIN:nums1[i-1]); //短数组分界线左侧数字
int nums_imr = (i==m?INT_MAX:nums1[i]); //短数组分界线右侧数字
int nums_jml = (j==0?INT_MIN:nums2[j-1]); //长数组分界线左侧数字
int nums_jmr = (j==n?INT_MAX:nums2[j]); //长数组分界线右侧数字
if(nums_iml <= nums_jmr){ //nums_iml <= nums_jmr,则表示短左侧大于长右侧,短数组分界线应右移,反之亦然
medianLeft = max(nums_iml,nums_jml);
medianRight = min(nums_imr,nums_jmr);
left = i+1; //分界线右移
}else{
right = i-1; //分界线左移
}
}
return (m+n)%2==0?(medianLeft+medianRight)/2.0:medianLeft; //判断总数组奇偶,对应计算中位数
}
};
- 时间复杂度 O(logmin(m,n)))
- 空间复杂度 O(1)
最长回文子串 M
动态规划
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size();
vector<vector<int>> dp(n,vector<int>(n));
string ans;
for(int step = 0; step<n; step++){ //step为步长,i为起点,j为终点
for(int i = 0; i+step<n; i++){
int j = i + step; //起点+步长=终点
if(step==0){ //步长为0时,子串长度为1,均为回文
dp[i][j] = 1;
}else if(step==1){
dp[i][j] = (s[i]==s[j]); //步长为1时,子串长度为2,判断
}else{
dp[i][j] = (s[i]==s[j] && dp[i+1][j-1]);
}
if(dp[i][j] && step+1>ans.size()){ //子串长度为步长+1.判断是否为最长串
ans = s.substr(i,step+1);
}
}
}
return ans;
}
};
- 时间复杂度 O(n²)
- 空间复杂度 O(n²)
中心扩展
思路:枚举所有的「回文中心」并尝试「扩展」,直到无法扩展为止,此时的回文串长度即为此「回文中心」下的最长回文串长度。对所有的长度求出最大值,即可得到最终的答案。
class Solution {
public:
pair<int,int> expandAroundCenter(const string& s,int left,int right){ //单次扩展函数
while(left >=0 && right < s.size() && s[left] == s[right]){
--left;
++right;
}
return {left+1,right-1}; //返回值为上一个子串
}
string longestPalindrome(string s) {
int start = 0,end = 0;
for(int i = 0;i < s.size(); i++){
auto [left1,right1] = expandAroundCenter(s,i,i); //边界1:步长为0
auto [left2,right2] = expandAroundCenter(s,i,i+1); //边界2:步长为1
if(right1 - left1 > end - start){
start = left1;
end = right1;
}
if(right2 - left2 > end - start){
start = left2;
end = right2;
}
}
return s.substr(start,end-start+1);
}
};
- 时间复杂度 O(n²)
- 空间复杂度 O(1)
Manacher 算法
在每个字符间插入“#”,转化为长度2n+1的奇数子串
利用已经计算的值(法一)以及回文的对称性加速中心扩展(法二)
- 时间复杂度 O(n)
- 空间复杂度 O(n)
*正则式匹配 H
动态规划
视频图解参考
字符比较状态转移方程
- 【*】:
- 回溯两列(前一字符不出现的情况),若为T则T,若为F转2
- 看正在比较的字符与【*】前字符是否相同,若相同则T,否则转3
- 回溯上一行同一列(前一字符出现过的情况),若相同则T,否则F
- 【字符相同】或者【.】:
- copy左对角(前字符串相同,则新加相同字符串仍相同)
- 【字符不同】F
盛水最多的容器 M
双指针
模式识别:需要移动左右两头
可以逐渐减小问题规模,排除不必要的计算,加速求解
三数之和 M
双指针
模式识别:利用排序避免重复答案
分治,降为twoSum问题