开始暑假的集中刷题了,时隔两个月没刷算法,今天发现之前获得的手感和技巧都丧失的差不多了,所以萌生了记笔记的想法。记下一些有价值的题目
LeetCode Easy 剑指offer 05 替换空格 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution {public : string replaceSpace (string s) { int length=s.size (); int count=0 ; for (int i=0 ;i<length;i++){ if (s[i]==' ' ) count++; } s.resize (length+2 *count); int old=length-1 ; int n=length+2 *count-1 ; while (old>=0 ){ if (s[old]==' ' ){ s[n--]='0' ; s[n--]='2' ; s[n--]='%' ; } else { s[n--]=s[old]; } old--; } return s; } };
精髓在于,从后向前替换,而不是从前向后替换,后者做法每次替换都要移动后面元素 o(n^2),前者不需要 o(n);因为 从后向前,n走在old后面(相对于前进方向),从前向后n在old的前面,s[n]依靠s[old],因此 n应该在old后面。
35 搜索插入位置 二分 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : int searchInsert (vector<int >& nums, int target) { int length=nums.size (); int left=0 ; int right=length-1 ; int mid=left+(right-left)/2 ; while (left<=right){ int now=nums[mid]; if (now<target) left=mid+1 ; else if (now>target) right=mid-1 ; else return mid; mid=left+(right-left)/2 ; } return right+1 ; } };
这道题目我的犹豫点在于循环的时候target是否会落在获得的区间之外 ,比如[0,100,110,200] target=102,第一次迭代后,nums[left]=110;target其实在left的左边,因此在二分处理的时候一直思考很多限制,一个简单的Easy题目却想了很多。后来想明白了,我的犹豫点是对的,但是这不影响什么。
这道题目,可以找到和target相等的目标值也就罢了(704题),找不到也就是需要插入的时候,要把target插入在两个数字中间的区域,这种情况下 按照从大到小排序,某次迭代后target一定会落在获得的区间旁边的区域,也就是right和right+1之间或者left和left-1之间 ,此后紧挨着target的区间边界left/right不动,另一个边界不断移动,直到 right小于left ,此时迭代终止,而target的位置等于right+1也就是在right和left之间;
二分加暴力 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : int searchInsert (vector<int >& nums, int target) { int length=nums.size (); int left=0 ; int right=length-1 ; int temp=right-left; while (temp>2 ){ int mid=left+temp/2 ; int now=nums[mid]; if (now<target) left=mid+1 ; else if (now>target) right=mid-1 ; else return mid; temp=right-left; } for (int i=left;i<=right;i++){ if (nums[i]>=target) return i; } return right+1 ; } };
仅仅在区间很小的时候采用了暴力,这可能会更高效一点。
27 移除元素 快慢双指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : int removeElement (vector<int >& nums, int val) { int length=nums.size (); int right=0 ; int left=0 ; while (right<length){ if (nums[right]==val) right++; else { nums[left]=nums[right]; left++; right++; } } return left; } };
本质上是,将两个指针看成在两个相同的数组上操作,而不是一个数组,快指针right将自己所在的数组上的所有不等于val的数值移动到慢指针所在的数组上,(0,right)开区间内的点都!=val。
双指针优化 前后双指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution {public : int removeElement (vector<int >& nums, int val) { int length=nums.size (); int right=length-1 ; int left=0 ; while (right>=left){ if (nums[left]==val){ if (nums[right]!=val){ nums[left]=nums[right]; right--; left++; } else right--; } else left++; } return left; } };
用后指针指向的元素替换前指针指向的需要删除的元素,直到两指针相遇后进入最后一次循环。
LeetCode Mid 59 螺旋矩阵|| 便于理解带注释 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class Solution {public : vector<vector<int >> generateMatrix (int n) { vector<vector<int >> res (n, vector <int >(n, 0 )); int startx = 0 , starty = 0 ; int loop_times = n / 2 ; int mid = n / 2 ; int count = 1 ; int offset = 1 ; int i,j; while (loop_times --) { i = startx; j = starty; for (j = starty; j < starty + n - offset; j++) { res[startx][j] = count++; } for (i = startx; i < startx + n - offset; i++) { res[i][j] = count++; } for (; j > starty; j--) { res[i][j] = count++; } for (; i > startx; i--) { res[i][j] = count++; } startx++; starty++; offset += 2 ; } if (n % 2 ) { res[mid][mid] = count; } return res; } };
习惯写法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution {public : vector<vector<int >> generateMatrix (int n) { vector<vector<int >> answer (n,vector <int >(n,0 )); int loop_times=n/2 ; int end=n/2 ; int start_x=0 ; int start_y=0 ; int number=1 ; int cut_length=1 ; int x,y; while (loop_times--){ x=start_x; y=start_y; for (;y<n-cut_length;y++){ answer[x][y]=number++; } for (;x<n-cut_length;x++){ answer[x][y]=number++; } for (;y>cut_length-1 ;y--){ answer[x][y]=number++; } for (;x>cut_length-1 ;x--){ answer[x][y]=number++; } start_x++; start_y++; cut_length++; } if (n%2 ){ answer[end][end]=number; } return answer; } };
这道模拟有点意思,像这种模拟我一直不太在行,打印乘法表这种大一C++作业题我都有点怵,菜鸡本菜了。
142 环形链表|| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution {public : ListNode *detectCycle (ListNode *head) { ListNode* slow=head; ListNode* fast=head; if (head==nullptr ||head->next==nullptr ) return nullptr ; slow=slow->next; fast=fast->next->next; while (fast!=nullptr &&fast->next!=nullptr &&slow!=fast){ slow=slow->next; fast=fast->next->next; } if (fast==nullptr ||fast->next==nullptr ) return nullptr ; slow=head; while (slow!=fast){ slow=slow->next; fast=fast->next; } return fast; } };
经典快慢指针做法,后面找环入口用到简单的数学知识。应该设长度,而不是节点数目。
15 三数之和 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Solution {public : vector<vector<int >> threeSum (vector<int >& nums) { int length=nums.size (); vector<vector<int >> answer; if (length<3 ) return answer; sort (nums.begin (),nums.end ()); for (int cur=0 ;cur<length;cur++){ if (nums[cur]>0 ) return answer; int left=cur+1 ; int right=length-1 ; if (cur>0 &&nums[cur]==nums[cur-1 ]) continue ; while (left<right){ if (nums[cur]+nums[right]+nums[left]>0 ){ right--; while (left<right&&nums[right]==nums[right+1 ]) right--; } else if (nums[cur]+nums[right]+nums[left]<0 ){ left++; while (left<right&&nums[left]==nums[left-1 ]) left++; } else { answer.push_back (vector<int >{nums[cur],nums[left],nums[right]}); while (left<right&&nums[right]==nums[right-1 ]) right--; while (left<right&&nums[left]==nums[left+1 ]) left++; right--; left++; } } } return answer; } };
双指针,去重麻烦。
四数之和 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 class Solution {public : vector<vector<int >> fourSum (vector<int >& nums, int target) { vector<vector<int >> result; sort (nums.begin (), nums.end ()); for (int k = 0 ; k < nums.size (); k++) { if (nums[k] > target && (nums[k] >= 0 || target >= 0 )) { break ; } if (k > 0 && nums[k] == nums[k - 1 ]) { continue ; } for (int i = k + 1 ; i < nums.size (); i++) { if (nums[k] + nums[i] > target && (nums[k] + nums[i] >= 0 || target >= 0 )) { break ; } if (i > k + 1 && nums[i] == nums[i - 1 ]) { continue ; } int left = i + 1 ; int right = nums.size () - 1 ; while (right > left) { if (nums[k] + nums[i] > target - (nums[left] + nums[right])) { right--; while (left < right && nums[right] == nums[right + 1 ]) right--; } else if (nums[k] + nums[i] < target - (nums[left] + nums[right])) { left++; while (left < right && nums[left] == nums[left - 1 ]) left++; } else { result.push_back (vector<int >{nums[k], nums[i], nums[left], nums[right]}); while (right > left && nums[right] == nums[right - 1 ]) right--; while (right > left && nums[left] == nums[left + 1 ]) left++; right--; left++; } } } } return result; } };
经典,需要背诵(×
151 颠倒字符串单词 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Solution {public : void reverse (string& s,int begin,int end) { while (begin<end){ swap (s[begin++],s[end--]); } } string reverseWords (string s) { while (s[0 ]==' ' ){ s.erase (s.begin ()); } while (s[s.size ()-1 ]==' ' ){ s.erase (s.end ()-1 ); } int cur=0 ; int length=s.size (); int slow=0 ; reverse (s,0 ,length-1 ); while (cur<s.size ()){ if (s[cur]==' ' ){ reverse (s,slow,cur-1 ); slow=cur+1 ; } else if (cur==s.size ()-1 ) reverse (s,slow,cur); while (s[slow]==' ' ){ s.erase (s.begin ()+slow); } cur++; } return s; } };
巧妙之处在于,先整体翻转 后 局部反转 ,改变了单词的位置顺序 而 不改变单词内部的字母顺序。
不足之处在于,reverse时间复杂度为o(n),加上外面循环o(n),一共为o(n^2),优化的话可以采用双指针 ,提前清除所有多余空格,时间复杂度为o(n),代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int length=s.size ();int slow=0 ; int fast=0 ;while (fast<length){ if (s[fast]==' ' ){ if (slow!=0 &&s[slow-1 ]==' ' ) slow--; s[slow]=s[fast]; } else { s[slow]=s[fast]; } slow++; fast++; } if (s[slow-1 ]==' ' ) s.resize (slow-1 );else s.resize (slow);if (s[0 ]==' ' ) s.erase (s.beigin ());
清除空格 时间复杂度o(n)