2021/08/02
-
- Add Two Numbers
- 解析: 非常简单的列表相加问题,一开始弄错为反向列表相加了,绕了点路子
- key points:
- 每次相加之后要把进位保留下来
- 考虑corner case,即最后一个进位为0时需要删除最后一个节点,不为零时需要保留最后一个节点
- 活用3元表达式缩减代码
- my answer
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* l1_r =l1, * l2_r =l2 ;
ListNode* cur_l1_r=l1_r, * cur_l2_r=l2_r;
int carry_bit=0;
ListNode* l_sum = new ListNode(carry_bit);
ListNode* cur_l_sum = l_sum, * pre_l_sum = NULL;
while(cur_l1_r!=NULL || cur_l2_r !=NULL){
int d1=0,d2=0;
if(cur_l1_r!=NULL){
d1 = cur_l1_r->val;
cur_l1_r = cur_l1_r->next;
}
if(cur_l2_r!=NULL){
d2 = cur_l2_r->val;
cur_l2_r = cur_l2_r->next;
}
int digit_sum = d1+d2+cur_l_sum->val;
carry_bit = digit_sum/10;
cur_l_sum->val = digit_sum%10;
cur_l_sum->next = new ListNode(carry_bit);
pre_l_sum = cur_l_sum;
cur_l_sum = cur_l_sum->next;
}
if(cur_l_sum->val == 0){
pre_l_sum->next =NULL;
delete cur_l_sum;
}
return l_sum;
}
};
- official answer:
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode *head = nullptr, *tail = nullptr;
int carry = 0;
while (l1 || l2) {
int n1 = l1 ? l1->val: 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 = l1->next;
}
if (l2) {
l2 = l2->next;
}
}
if (carry > 0) {
tail->next = new ListNode(carry);
}
return head;
}
};
-
解析: 遍历所有substring,判断是否重复,利用hashtable 加速查询
-
key points:
- 如何遍历所有substring
- 使用hashtable加速算法
- 利用滑动窗思想(下一次不重复的查找起点可以跨越之前已经检查过的位置,类似KMP算法)[x]
-
my answer (slow):
class Solution { public: int lengthOfLongestSubstring(string s) { int max_len = 0; int s_len = s.size(); if(s_len == 0) return 0; max_len = 1; std::unordered_set<char> c; for(int i=0;i<s_len;i++){ for(int j=i;j>=0;j--){ if(c.find(s[j])==c.end()) c.insert(s[j]); else break; } if(c.size()>max_len) max_len = c.size(); c.clear(); } return max_len; } };
-
official anwser:
class Solution { public: int lengthOfLongestSubstring(string s) { // 哈希集合,记录每个字符是否出现过 unordered_set<char> occ; int n = s.size(); // 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动 int rk = -1, ans = 0; // 枚举左指针的位置,初始值隐性地表示为 -1 for (int i = 0; i < n; ++i) { if (i != 0) { // 左指针向右移动一格,移除一个字符 occ.erase(s[i - 1]); } while (rk + 1 < n && !occ.count(s[rk + 1])) { // 不断地移动右指针 occ.insert(s[rk + 1]); ++rk; } // 第 i 到 rk 个字符是一个极长的无重复字符子串 ans = max(ans, rk - i + 1); } return ans; } };
2021/08/03
-
Median of Two Sorted Arrays[X]
-
解析:需要满足对数时间复杂度,问题在于如何实现双有序队列的二分查找,这一思想可以扩展到多有序队列
-
keypoints:
- 查找中位数的问题建模,如何每次去掉一半的数
- 编程技巧 [need re-make]
-
my answer : None
-
official answer:
class Solution { public: int getKthElement(const vector<int>& nums1, const vector<int>& nums2, int k) { /* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较 * 这里的 "/" 表示整除 * nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个 * nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个 * 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个 * 这样 pivot 本身最大也只能是第 k-1 小的元素 * 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组 * 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组 * 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数 */ int m = nums1.size(); int n = nums2.size(); int index1 = 0, index2 = 0; while (true) { // 边界情况 if (index1 == m) { return nums2[index2 + k - 1]; } if (index2 == n) { return nums1[index1 + k - 1]; } if (k == 1) { return min(nums1[index1], nums2[index2]); } // 正常情况 int newIndex1 = min(index1 + k / 2 - 1, m - 1); int newIndex2 = min(index2 + k / 2 - 1, n - 1); int pivot1 = nums1[newIndex1]; int pivot2 = nums2[newIndex2]; if (pivot1 <= pivot2) { k -= newIndex1 - index1 + 1; index1 = newIndex1 + 1; } else { k -= newIndex2 - index2 + 1; index2 = newIndex2 + 1; } } } double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { int totalLength = nums1.size() + nums2.size(); if (totalLength % 2 == 1) { return getKthElement(nums1, nums2, (totalLength + 1) / 2); } else { return (getKthElement(nums1, nums2, totalLength / 2) + getKthElement(nums1, nums2, totalLength / 2 + 1)) / 2.0; } } };
-
2021/08/06
- 解析:动态规划,核心是建立状态与找到状态转移方程
- keypoints:
- 状态转移方程的分析与建立
- my code:None
- official code: None
2021/08/07
-
-
解析:贪心算法,利用双指针求解
-
keypoints:
- 分析贪心合理性,每次尝试改善容器最弱边界,证明该步骤合理即可
- 使用双指针实现代码
-
my code:
class Solution { public: int maxArea(vector<int>& height) { int left_ptr= 0, right_ptr = height.size()-1; int cur_left = height[left_ptr], cur_right = height[right_ptr]; int most_water = (right_ptr-left_ptr)*min(cur_left,cur_right); for(;left_ptr<right_ptr;){ if(height[left_ptr]< height[right_ptr]){ while(left_ptr<=right_ptr && height[++left_ptr]<cur_left){} cur_left=height[left_ptr]; }else{ while(left_ptr<=right_ptr && height[--right_ptr]<cur_right){} cur_right = height[right_ptr]; } int cur_water = (right_ptr-left_ptr)*min(cur_right,cur_left); if(cur_water>most_water) most_water=cur_water; } return most_water; } };
-
-
12. Integer to Roman
-
解析:储存好digits number即可,解析完特殊情况后合成字符串。
-
my code:
class Solution { public: string intToRoman(int num) { int base[7]={1,5,10,50,100,500,1000}; int digits[7]; int subtraction[7]={0}; char roman_base[7]={'I','V','X','L','C','D','M'}; string result=""; for(int i=6;i>=0;i--){ digits[i] = num/base[i]; num = num%base[i]; if(i<6 && i%2==0){ if(digits[i]==4){ if(digits[i+1]>=1){ digits[i+1]--; subtraction[i+1]=1; }else{ subtraction[i]=1; } } } } for(int i=6;i>=0;i--){ if(subtraction[i]>0){ result.append(1,roman_base[(i>>1)<<1]); result.append(1,roman_base[i+1]); i=((i>>1)<<1); }else{ if(digits[i]>0) result.append(digits[i],roman_base[i]); } } return result; } };
-
2021/08/08
-
-
3Sum[x]
- 解析:三数之和,关键在于发现排序后循环的意义
- keypoints:
- 排序,发现了排序的第一重作用(加快搜寻),没有发现排序的第二重作用(使得检出的三元组天然有序)
- 排序后多重循环时,后两重循环实际上可以变为一重,因为是有序的
-
2021/08/09
-
-
解析:排序+双指针,和3SUM一样
-
keypoints:
- 一些小的优化技巧,target==0 时可以直接返回,枚举a,b,c时,对于重复元素可以直接跳过枚举过程。
-
my code:
class Solution { public: int threeSumClosest(vector<int>& nums, int target) { sort(nums.begin(),nums.end()); int sum = nums[0]+nums[1]+nums[2]-target; int a,b,c,tmp_sum; for(int i=0;i<nums.size();i++){ a = nums[i]; int left=i+1, right = nums.size()-1; while(left<right){ b = nums[left]; c = nums[right]; tmp_sum = a+b+c-target; if(abs(tmp_sum)<abs(sum)) sum = tmp_sum; if(tmp_sum<0) left++; else right--; } } return sum+target; } };
-
-
-
解析:就是树的深度优先搜索,基础不牢固
-
keypoints:
- 递归,也可以使用非递归解法,但是要自己定义stack来代替递归栈
-
2021/08/10
-
-
解析:与3Sum 类似,解法一样,但是需要一些trick来加速操作
-
keypoints:
- 需要剪枝操作来加快速度,例如排序后,若前四个数和大于target则可直接返回[x]
-
2021/08/11
-
- 解析:链表基础操作,
- keypoints:
- 处理链表时一定要考虑头尾,格外注意每一步程序的意思;
- 使用双指针可以避免计算列表长度,即为此right-left=n,直到right==NULL,此时left为对应的删除节点[x]