Week 01 - Leetcode 01 - 10
01 - 两数之和
哈希表记录target - nums[i] 是否出现过
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> heap;
for(int i = 0; i < nums.size(); i++)
{
int r = target - nums[i];
if(heap.count(r)) return {heap[r], i};
heap[nums[i]] = i;
}
return {};
}
}02 - 两数相加
对于头结点特判 可以用一个dummy head 避免
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* dummy = new ListNode(-1), *cur = dummy;
int t = 0;
while(l1 || l2 || t) // 还有l或者还有进位标志
{
if(l1) t += l1->val, l1 = l1->next;
if(l2) t += l2->val, l2 = l2->next;
cur = cur->next = new ListNode(t % 10);
t /= 10;
}
return dummy->next;
}
};03 - 无重复字符的最长子串
双指针算法的应用,左指针l 右指针r; 单调性使得遍历r时 l不必每次都回溯到初始位置,保证了每个点都只被遍历一次-> o(n);
证明:反证法 证明l指针只需要在上次的位置向后走
04 - 寻找两个正序数组的中位数
经典劝退题目 QAQ
奇数个数: 中位数
偶数个数: 中间两个数的平均数
nums1, nums2都是有序的;
方法: 递归 (不要用二分去做 二分太难了)
原问题难以直接递归求解,所以我们先考虑这样一个问题:
Note: 在两个有序数组中,找出第k小数。
如果该问题可以解决,那么第 (n+m)/2 小数就是我们要求的中位数.
先从简单情况入手,假设 m,n≥k/2,我们先从 nums1和 nums2中各取前 k/2个元素:
如果 nums1[k/2−1] <= nums2[k/2−1],则说明 nums1中取的元素过多,nums2中取的元素过少;因此 nums2中的前 k/2个元素一定都小于等于第 k小数,所以我们可以先取出这些数,将问题归约成在剩下的数中找第 k−⌊k/2⌋小数.
如果 nums1[k/2−1] >= nums2[k/2−1],同理可说明 nums1中的前 k/2个元素一定都小于等于第 k小数,类似可将问题的规模减少一半.
现在考虑边界情况,如果 m < k/2,则我们从 nums1中取m个元素,从nums2中取 k/2个元素(由于 k=(n+m)/2,因此 m,n不可能同时小于 k/2
如果 nums1[m−1] > nums2[k/2−1],则 nums2中的前 k/2个元素一定都小于等于第 k小数,我们可以将问题归约成在剩下的数中找第 k−⌊k/2⌋小数.如果 nums1[m−1] <= nums2[k/2−1],则 nums1中的所有元素一定都小于等于第 k小数,因此第k小数是 nums2[k−m−1].
时间复杂度分析: k=(m+n)/2,且每次递归k的规模都减少一半,因此时间复杂度是 O(log(m+n)).
05 - 最长回文子串
马拉车算法 - o(n) 仅能做最长回文子串 就不学了
字符串哈希 + 二分 o(nlogn)
分别记录前序和后序的哈希值 把逐个判断是否对称转换为直接判断哈希值是否相等
回文子串有两种,偶数长度和奇数长度,为了化简代码在每两个字符内插入一个 ‘ | ’来把所有的偶数长度回文子串转化为奇数的;
举例来说:
aba -> a|b|a
abba -> a|b|b|a
这样可能会存在|a|b|a|形如这样的回文子串问题 边界问题最后解决就好;
分别从前序和后序记录字符串哈希值 这里后序的哈希要注意下标怎么来的;
字符串哈希: base = 131或者13331, h[r] - h[l - 1] * p[r - l + 1]
记得初始化p[0] = 1(p是用来记录base的p次幂的)
另外补一个动态规划的o(n^2)算法,注意下遍历顺序是长度-> 起点即可
06 - Z字形变换
等差数列找规律的问题 注意开一个字符串数组 别用+
07 - 整数反转
注意的点在于 -4 % 10 = -4 (在c++中) 而python和数学的定义都是等于6的
所以下面的代码可以同时处理正数和负数的情况;
08 - 实现atoi
模拟 判断是否为数字 isdigit()
09 - 回文数
字符串方法
记一下cpp中如何反转字符串
数字方法
和整数反转的方法类似
10 - 正则表达式匹配
两个注意的点: - 1. i是从0开始 而j是从1开始 f[0, j]是有意义的 - 2. 当p[j + 1]是通配符*的时候,可以直接跳过,因为只和f[i, j - 2]有关 - 3. 写 i - 1的时候记得放置越界
实际上是动态规划问题 (好难)
状态表示 f(i, j) 表示所有s(1 - i) 和p(1 - j) 的匹配方案, 值是一个bool值 表示是否存在一个合法方案。
设状态 f(i,j) 表示字符串 s 的前 i 个字符和字符串 p 的前 j 个字符能否匹配.
这里假设 s 和 p 的下标均从 1 开始。初始时,f(0,0)=true
转移1 - p(j)不为*: f(i, j)=f(i, j) or f(i−1, j−1) ,当 i>0 且 s(i) == p(j) || p(j) == '.'
转移2 - p(j)为*: 若 j>=2 ,f(i,j) 可以从 f(i,j−2) 转移,表示丢弃这一次的 '*‘ 和它之前的那个字符;
若 i>0 且 s(i) == p(j - 1),表示这个字符可以利用这个 '*',则可以从 f(i−1, j) 转移,表示利用 '*’ 来代替前面的字符;
利用这个, 就可以枚举*用来表示多少个字符, 有一种情况成立即可: 所以在f(i - 1, j - 2) && si, f(i - 2, j - 2) && si && si - 1, f( i - 3, j - 2 ) && si && si-1 && si-2.. 中只要有一个满足即可 在这里我们注意到:
f(i, j) = f(i, j - 2) || f(i - 1, j - 2) && si || f(i - 2, j - 2) && si && si - 1||f( i - 3, j - 2 ) && si && si-1 && si-2..
f(i - 1, j) = f(i - 1, j - 2)||f(i - 2, j - 2) && si - 1||f( i - 3, j - 2 ) && si-1 && si-2..
和完全背包的优化方式一样 f(i, j) = f(i, j - 2) ||(si == p && f(i - 1, j)
初始状态f(0,0)=true ;循环枚举 i 从 0 到 n ;j 从 1 到 m 。因为 f(0,j) 有可能是有意义的,需要被转移更新。 最终答案为 f(n,m).
Last updated