化悲痛为力量——暑假刷题走起
6.21
【思维】接雨水-力扣
题目描述不贴了,在上面的链接里↑
第一遍写的思路过于简单,按每层遍历,还可以递归少写点代码。但时间复杂度O(n*n),导致在复杂数据点超时。
参考了windliang的题解才想到可以按列遍历,每个柱子能储存的雨水只和左边所有柱子的最高lmax,与右边所有最高rmax有关。两者中的较小者min减去该柱子的高度h得到的结果和0相比的较大值就是答案。如果这个差小于等于0,就应该等于0。
进一步还可以用单调栈储存数据,使空间复杂度再次降低。(这里下次好好研究一下)
参考代码:

1 class Solution { 2 public: 3 int trap(vector<int>& height) { 4 int rmax=0,lmax=0,len=height.size(),sum=0; 5 int Max1[100000],Max2[100000]; 6 for(int i=0;i<len;++i){ 7 if(i>0) rmax=max(rmax,height.at(i-1)); 8 Max1[i]=rmax; 9 } 10 for(int i=len-1;i>=0;--i){ 11 if(i+1<len) lmax=max(lmax,height.at(i+1)); 12 Max2[i]=lmax; 13 } 14 for(int i=0;i<len;++i){ 15 int tmp=min(Max1[i],Max2[i]); 16 sum+=max(tmp-height.at(i),0); 17 } 18 return sum; 19 } 20 };
6.22
【思维】模式匹配-力扣
题目描述在上面的链接里↑
一开始想了三个思路。①枚举,搜索匹配串中重复出现的子串。如果有重复的子串可能性多,则效率太低,pass;②从匹配串入手,看能匹配什么模式,再查模式串是否是其中一种情况。实际上与①本质相同,pass;③意识到匹配题最好从模式串入手,结合基本计算,匹配串的可能组合就是有限的,可行。
核心:解不定方程。anum*alen + bnum*blen=vlen => anum和bnum可以从模式串p中数出、作系数,alen和blen相当于未知数
特殊情况:重复子串a_re和b_re不能相等,但可以有一个为空串;对于anum和bnum有一个为0、都为0的情况要提前讨论。
参考代码:

1 class Solution { 2 public: 3 bool patternMatching(string pattern, string value) { 4 string p=pattern, v=value; 5 int plen=p.length(), vlen=v.length(), tmp=0, anum=0, bnum=0; 6 for(int i=0;i<plen;++i){ 7 if(p[i]=='a') anum++; 8 else bnum++; 9 } 10 //根据a和b的数量以及目标串的长度,解二元一次方程 11 if(anum+bnum==0){ 12 if(v.length()!=0) return false; 13 return true; 14 } 15 if(anum==0||bnum==0){ 16 int num=anum+bnum; 17 if( vlen%num !=0) return false; 18 string tmp,re; 19 re.assign(v, 0, vlen/num); 20 for(int i=0;i<vlen;i+=vlen/num){ 21 tmp.assign(v, i, vlen/num); 22 if(tmp!=re) return false; 23 } 24 return true; 25 } 26 for(int i=0; i<=(vlen/anum); ++i){ 27 if( (vlen-i*anum)%bnum==0 ){ 28 int alen=i, blen=(vlen-i*anum)/bnum; 29 string tmp, re_a, re_b; 30 int ptr=0;//比对指针 31 bool a_occur=false, b_occur=false, find=true; 32 for(int j=0;j<plen;++j){ 33 if(a_occur && b_occur && re_a==re_b) break; 34 if(p[j]=='a'){ 35 tmp.assign(v,ptr,alen); 36 ptr+=alen; 37 if(!a_occur){ 38 re_a=tmp; 39 a_occur=1; 40 } 41 else{ 42 if(tmp!=re_a){ 43 find=false; 44 break; 45 } 46 } 47 } 48 else{ 49 tmp.assign(v,ptr,blen); 50 ptr+=blen; 51 if(!b_occur){ 52 re_b=tmp; 53 b_occur=1; 54 } 55 else{ 56 if(tmp!=re_b){ 57 find=false; 58 break; 59 } 60 } 61 } 62 } 63 if(re_a!=re_b && find) return true; 64 } 65 } 66 return false; 67 } 68 };
6.23
【手感-进制计算】二进制求和-力扣
今天忙着投实习,刷题太水了(这个借口真烂x)
这道是简单题,一遍读懂题意很重要。
参考代码:

1 class Solution { 2 public: 3 string addBinary(string a, string b) { 4 string ans=""; 5 int alen=a.length(), blen=b.length(); 6 int p1=alen-1, p2=blen-1, res=0; 7 while(p1>=0 || p2>=0){ 8 if(p1>=0) res+=a[p1]-'0'; 9 if(p2>=0) res+=b[p2]-'0'; 10 string tmp="x"; 11 tmp[0]=res%2+'0'; 12 ans=tmp+ans; 13 res/=2; 14 p1--; 15 p2--; 16 } 17 while(res>0){ 18 string tmp="x"; 19 tmp[0]=res%2+'0'; 20 ans=tmp+ans; 21 res/=2; 22 } 23 int cnt=0; 24 if(ans[0]=='0'){ 25 for(int i=0; i<ans.length(); ++i){ 26 if(i==ans.length()-1){ 27 string tmp="0"; 28 tmp[0]=ans[ans.length()-1]; 29 return tmp; 30 } 31 if(ans[i]!='0') break; 32 else cnt++; 33 } 34 } 35 ans.assign(ans, cnt, ans.length()-cnt); 36 return ans; 37 } 38 };
6.24
【双指针】最接近的三数之和-力扣
因为是三数之和、且不是求相等而是相近,用二重循环+二分排序查找似乎比双指针慢很多,但两者都要注意边界情况( |pb-pc| ==1时以及到达此状态前一刻的情况),可以多建几个临时变量储存结果。
参考代码:

1 //①排序+二分查找法 2 class Solution { 3 public: 4 int threeSumClosest(vector<int>& nums, int target) { 5 sort(nums.begin(),nums.end()); 6 int a, b, c, vlen=nums.size(); 7 int minloc[3]={0}; 8 for(int i=vlen-1;i>=0;--i){ 9 for(int j=i-1;j>=0;--j){ 10 a=nums.at(i); 11 b=nums.at(j); 12 c=target-a-b; 13 int r=0, l=j-1; 14 if(l<0) break; 15 while(l-r>1){ 16 if( abs( nums.at(r)-c ) > abs( nums.at(l)-c )){ 17 r=(r+l)/2; 18 } 19 else{ 20 l=(r+l)/2; 21 } 22 } 23 if( abs( nums.at(r)-c ) > abs( nums.at(l)-c )) r=l; 24 if((i==vlen-1 && j==vlen-2) || abs(nums.at(minloc[0])+nums.at(minloc[1])+nums.at(minloc[2])-target)>abs(nums.at(i)+nums.at(j)+nums.at(r)-target)){ 25 minloc[2]=r; 26 minloc[0]=i; 27 minloc[1]=j; 28 } 29 } 30 } 31 return nums.at(minloc[0])+nums.at(minloc[1])+nums.at(minloc[2]); 32 } 33 }; 34 35 //②排序+双指针法 36 class Solution { 37 public: 38 int threeSumClosest(vector<int>& nums, int target) { 39 sort(nums.begin(),nums.end()); 40 int vlen=nums.size(), pb, pc, sum, ans=1<<30; 41 for(int i=vlen-1;i>=0;--i){ 42 pb=i-1;pc=0; 43 if(pb<=0) break; 44 while(pb-pc>1){ 45 sum=nums.at(i)+nums.at(pb)+nums.at(pc); 46 if( sum==target ) return target; 47 if( sum>target ) pb--; 48 else pc++; 49 } 50 int sum2=nums.at(i)+nums.at(pb)+nums.at(pc); 51 if( i>2 && abs(sum2-target)<abs(sum-target) ) sum=sum2; 52 else if(i<=2) sum=sum2; 53 if( abs(sum-target)<abs(ans-target) ) ans=sum; 54 } 55 return ans; 56 } 57 };
6.25
【动规】单词拆分-力扣
这道题有点意思,题意很简单,在字典中查找、匹配,问是否能用字典词组成目标串。一开始的思路是对字典排序+二分查找起始点(首字母与当前待匹配字符串首字母相同的字典词)+递归,但超时了。然后动规,建一个set,只储存匹配不到的子字符串(能匹配到的一定是答案,肯定已经return true了),每次递归前先查是否该字符串在set里,是则直接return false。
另外,有个局部优化是要先贪心匹配最长的字典单词。试了一下,将二分查找到的位置记为r。
①未优化的写法是从r开始向两边±i(i=0,1,2,3……)地搜索,往下递归,两边都越界或都不再匹配则退出循环;
②优化后,从r开始往后找(sort函数默认按字典序排序,头部相同,尾部更长的单词在后面),找到最后一个首字母能匹配上的位置maxloc,然后再从maxloc往前遍历,每一层向下递归。
参考代码中写了个小函数bool HeadOf(string A, string B)来判断是否A是B的头部。
参考代码:

1 //①动规-未优化方法 2 class Solution { 3 public: 4 set<string> fail; 5 bool HeadOf(string A, string B){ 6 if(A.length()>B.length()) return false; 7 for(int i=0;i<A.length();++i){ 8 if(A[i]!=B[i]) return false; 9 } 10 return true; 11 } 12 bool wordBreak(string s, vector<string>& wordDict) { 13 auto p=fail.find(s); 14 if(p!=fail.end()) return false; 15 if(s=="") return true; 16 sort(wordDict.begin(), wordDict.end()); 17 int wlen=wordDict.size(), slen=s.length(); 18 if(wlen==0){ 19 fail.insert(s); 20 return false; 21 } 22 int r=0, l=wlen-1, maxloc=-1; 23 while(l-r>1){ 24 if( wordDict.at( (r+l)/2 )[0] < s[0]) r=(r+l)/2; 25 else l=(r+l)/2; 26 } 27 if( wordDict.at(r)[0]==s[0] || wordDict.at(l)[0]==s[0]){ 28 for(int i=0;;i++){ 29 if(r+i>=wlen && r-i<0) break; 30 if(r+i<wlen && wordDict.at(r+i)[0]==s[0] && HeadOf(wordDict.at(r+i),s) ){ 31 string tmp; 32 tmp.assign(s,wordDict.at(r+i).length(),slen-wordDict.at(r+i).length()); 33 if(wordBreak(tmp,wordDict)) return true; 34 } 35 if(i!=0 && r-i>=0 && wordDict.at(r-i)[0]==s[0] && HeadOf(wordDict.at(r-i),s)){ 36 string tmp; 37 tmp.assign(s,wordDict.at(r-i).length(),slen-wordDict.at(r-i).length()); 38 if(wordBreak(tmp,wordDict)) return true; 39 } 40 } 41 } 42 fail.insert(s); 43 return false; 44 } 45 }; 46 47 //②动规-优化方法 48 class Solution { 49 public: 50 set<string> fail; 51 bool HeadOf(string A, string B){ 52 if(A.length()>B.length()) return false; 53 for(int i=0;i<A.length();++i){ 54 if(A[i]!=B[i]) return false; 55 } 56 return true; 57 } 58 bool wordBreak(string s, vector<string>& wordDict) { 59 auto p=fail.find(s); 60 if(p!=fail.end()) return false; 61 if(s=="") return true; 62 sort(wordDict.begin(), wordDict.end()); 63 int wlen=wordDict.size(), slen=s.length(); 64 if(wlen==0){ 65 fail.insert(s); 66 return false; 67 } 68 int r=0, l=wlen-1, maxloc=-1; 69 while(l-r>1){ 70 if( wordDict.at( (r+l)/2 )[0] < s[0]) r=(r+l)/2; 71 else l=(r+l)/2; 72 } 73 for(int i=r;i<wlen;++i){ 74 if(wordDict.at(i)[0]==s[0]) maxloc=i; 75 } 76 if(maxloc==-1){ 77 fail.insert(s); 78 return false; 79 } 80 for(int i=maxloc;i>=r;i--){ 81 if(wordDict.at(i)[0]==s[0] && HeadOf(wordDict.at(i),s) ){ 82 string tmp; 83 tmp.assign(s,wordDict.at(i).length(),slen-wordDict.at(i).length()); 84 if(wordBreak(tmp,wordDict)) return true; 85 } 86 } 87 fail.insert(s); 88 return false; 89 } 90 };
!且慢——刚刚参考了dalao的代码,发现动规也被我做复杂了orz。可以从另一面考虑,用dp[i]==true/false记录原始待匹配单词的前i个字符是否恰好能被字典词匹配上,这样不用递归都行。另外,dalao还对其代码做了进一步优化,即找到字典中单词的最大长度maxWordLength,在循环计算时,对前i个单词的分割就可以从i-maxWordLength开始,如果这个数<0,就从0开始。
神奇版代码:

1 bool wordBreak(string s, vector<string>& wordDict) { 2 vector<bool> dp(s.size()+1, false); 3 unordered_set<string> m(wordDict.begin(), wordDict.end()); 4 dp[0] = true; 5 //获取最长字符串长度 6 int maxWordLength = 0; 7 for (int i = 0; i < wordDict.size(); ++i){ 8 maxWordLength = std::max(maxWordLength, (int)wordDict[i].size()); 9 } 10 for (int i = 1; i <= s.size(); ++i){ 11 for (int j = std::max(i-maxWordLength, 0); j < i; ++j){ 12 if (dp[j] && m.find(s.substr(j, i-j)) != m.end()){ 13 dp[i] = true; 14 break; 15 } 16 } 17 } 18 return dp[s.size()]; 19 }
后记:以上三种方法的速度差异还是蛮大的,分别是48ms--16ms--0ms,orz……
6.26
【手感-链表操作】移除重复节点-力扣
两种思路,①使用额外内存,用set储存到目前为止出现过的元素,一次遍历,删除出现过的节点;②不使用额外内存,两个指针逐个遍历每个未删除的节点。简单题,只写了第二种方法。
参考代码:

1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode* removeDuplicateNodes(ListNode* head) { 12 ListNode* p1=head; 13 ListNode* p2=head; 14 if(p1==NULL) return head; 15 while((*p1).next!=NULL){ 16 if(p2==NULL || (*p2).next==NULL){ 17 p1=(*p1).next; 18 p2=p1; 19 } 20 else if( (*(*p2).next).val==(*p1).val ){ 21 (*p2).next= (*(*p2).next).next; 22 } 23 else{ 24 p2=(*p2).next; 25 } 26 } 27 return head; 28 } 29 };
6.27
【手感-链表操作】反转链表-力扣
题不难,可以学习三种翻转方法。①三个指针交替翻转,其中一个是临时存储用的;②递归,如果当前节点或其下一个节点等于NULL,就直接返回当前节点。否则,新建一个res节点,保存下一层递归的返回值,下一层递归的参数的是当前头节点的下一个节点。在每一层中让当前节点的next节点的next指向自己,之后当前节点的next节点指向NULL,返回res节点;③神奇版双指针法。从头节点开始,向后遍历。把每个节点的next指向节点保存在头节点指针中,然后该节点的next指向前一个节点。这其中用两个指针保存即可(头节点充当了①方法中的第三个临时节点)。
参考代码(注:②③的思路和代码都是借鉴huwt的):

1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 10 //法① 11 class Solution { 12 public: 13 ListNode* reverseList(ListNode* head) { 14 if(head==NULL) return NULL; 15 if(head->next==NULL) return head; 16 ListNode* pre=NULL; 17 ListNode* cur=head; 18 ListNode* tmp=cur->next; 19 while(cur!=NULL){ 20 tmp=cur->next; 21 cur->next=pre; 22 pre=cur; 23 cur=tmp; 24 } 25 return pre; 26 } 27 }; 28 29 //法② 30 class Solution { 31 public: 32 ListNode* reverseList(ListNode* head) { 33 if (head == NULL || head->next == NULL) { 34 return head; 35 } 36 ListNode* ret = reverseList(head->next); 37 head->next->next = head; 38 head->next = NULL; 39 return ret; 40 } 41 }; 42 43 //法③ 44 class Solution { 45 public: 46 ListNode* reverseList(ListNode* head) { 47 if (head == NULL) { return NULL; } 48 ListNode* cur = head; 49 while (head->next != NULL) { 50 ListNode* t = head->next->next; 51 head->next->next = cur; 52 cur = head->next; 53 head->next = t; 54 } 55 return cur; 56 } 57 };
6.28
【思维】判断路径是否相交-力扣
很有意思的题,难在思路,实现很简单,过目不忘。
参考代码:

1 //大佬解答,妙! 2 class Solution { 3 public: 4 bool isPathCrossing(string path) { 5 set<pair<int, int> > st; 6 st.insert(make_pair(0, 0)); 7 int x = 0, y = 0; 8 for(char c : path) { 9 if(c == 'N') { 10 x++; 11 } 12 else if(c == 'S') x--; 13 else if(c == 'E') y++; 14 else y--; 15 auto tmp = make_pair(x, y); 16 if(st.count(tmp)) return true;//每点状态唯一,储存状态到set,只需判断是否遇到过该状态 17 st.insert(tmp); 18 } 19 return false; 20 } 21 };
【双指针】长度最小的子数组-力扣
三种做法(为何没有一下看破!面壁去)。①极端的暴力循环法/前缀和暴力循环法;②前缀和+二分查找法;③双指针法。
看到求“xx最小的子问题”的题就以为是动规,结果自己跳坑了。
参考代码:

1 //①自己用了前缀和暴力循环法过的 2 class Solution { 3 public: 4 int minSubArrayLen(int s, vector<int>& nums) { 5 int n=nums.size(); 6 vector<int> ori=nums; 7 int sum[33200]={0}; 8 for(int i=0;i<n;++i){ 9 if(i>0) sum[i]=sum[i-1]+nums[i]; 10 else sum[0]=nums[0]; 11 } 12 sort(nums.begin(),nums.end()); 13 int tmp=0, mmin=0, mmax=0; 14 for(int i=0;i<n;++i){ 15 tmp+=nums[i]; 16 if(tmp>=s){ 17 mmax=i+1; 18 break; 19 } 20 } 21 tmp=0; 22 for(int i=n-1;i>=0;--i){ 23 tmp+=nums[i]; 24 if(tmp>=s){ 25 mmin=n-i; 26 break; 27 } 28 } 29 if(mmin==mmax) return mmin; 30 for(int k=mmin;k<=mmax;++k){ 31 for(int i=0;i<n;++i){ 32 if(i+k-1>=n) break; 33 if(sum[i+k-1]-sum[i]+ori[i]>=s) return k; 34 } 35 } 36 return 0; 37 } 38 }; 39 40 //②前缀和+二分查找法 41 class Solution { 42 public: 43 int minSubArrayLen(int s, vector<int>& nums) { 44 int n = nums.size(); 45 if (n == 0) { 46 return 0; 47 } 48 int ans = INT_MAX; 49 vector<int> sums(n + 1, 0); //可以动态指定vector长度 50 for (int i = 1; i <= n; i++) { 51 sums[i] = sums[i - 1] + nums[i - 1]; 52 } 53 for (int i = 1; i <= n; i++) { 54 int target = s + sums[i - 1]; 55 auto bound = lower_bound(sums.begin(), sums.end(), target);//啊别忘二分查找 56 if (bound != sums.end()) { 57 ans = min(ans, static_cast<int>((bound - sums.begin()) - (i - 1))); 58 } 59 } 60 return ans == INT_MAX ? 0 : ans; 61 } 62 }; 63 64 //③双指针法 65 class Solution {//双指针法。一开始想到要用动规储存,但实际上不用储存,用双指针即可。 66 public: 67 int minSubArrayLen(int s, vector<int>& nums) { 68 int n = nums.size(); 69 if (n == 0) { 70 return 0; 71 } 72 int ans = INT_MAX; 73 int start = 0, end = 0; 74 int sum = 0; 75 while (end < n) { 76 sum += nums[end]; 77 while (sum >= s) {//满足条件就更新答案 78 ans = min(ans, end - start + 1); 79 sum -= nums[start]; 80 start++; 81 } 82 end++;//每次迭代end往右移 83 } 84 return ans == INT_MAX ? 0 : ans; 85 } 86 };
6.29
【思维-哈希表】罗马数字转整数-力扣
有、意思的题,不要想复杂了。
参考代码:

1 class Solution { 2 public: 3 int romanToInt(string s) { 4 int len=s.length(), ans=0; 5 int v[22]={0};//类似于哈希的存储方式 6 v[(int)'I'-67]=1; 7 v[(int)'V'-67]=5; 8 v[(int)'X'-67]=10; 9 v[(int)'L'-67]=50; 10 v[(int)'C'-67]=100; 11 v[(int)'D'-67]=500; 12 v[(int)'M'-67]=1000; 13 for(int i=0;i<len;++i){ 14 if(i==len-1) ans+=v[(int)s[i]-67]; 15 else{ 16 int a=v[(int)s[i]-67]; 17 int b=v[(int)s[i+1]-67]; 18 if(a<b){ 19 ans+=b-a; 20 i++; 21 } 22 else ans+=a; 23 } 24 } 25 return ans; 26 } 27 };
此题的姊妹篇是 整数转罗马数字-力扣
虽然这两道题在LeetCode上分别被标为简单、中等难度,我私以为后一道题更容易通过找规律,只需求解一个“类”进制转换问题。
参考代码:

1 class Solution { 2 public: 3 string digit(int x, string one, string five, string ten){ 4 string d=""; 5 if(x<4){ 6 for(int i=0;i<x;++i){ 7 d+=one; 8 } 9 return d; 10 } 11 else if(x==4) return one+five; 12 else if(x==9) return one+ten; 13 else{ 14 d=five; 15 for(int i=0;i<x-5;++i){ 16 d+=one; 17 } 18 return d; 19 } 20 return "error"; 21 } 22 string intToRoman(int num) { 23 string d1, d2, d3, d4; 24 d1=digit(num%10,"I","V","X"); 25 num/=10; 26 d2=digit(num%10,"X","L","C"); 27 num/=10; 28 d3=digit(num%10,"C","D","M"); 29 num/=10; 30 d4=digit(num%10,"M","Error1","Error2"); 31 return d4+d3+d2+d1; 32 } 33 };
6.30
【复习-STL】用两个栈实现队列-力扣
程设作业似乎做过一道类似的,复习一下也好。
参考代码:

1 class CQueue { 2 public: 3 stack<int> pos; 4 stack<int> rev; 5 CQueue() {//栈的函数有push top pop 6 } 7 void appendTail(int value) { 8 rev.push(value); 9 } 10 int deleteHead() { 11 if(!pos.empty()){ 12 int tmp=pos.top(); 13 pos.pop(); 14 return tmp; 15 } 16 while(!rev.empty()){ 17 int tmp=rev.top(); 18 pos.push(tmp); 19 rev.pop(); 20 } 21 if(!pos.empty()){ 22 int tmp=pos.top(); 23 pos.pop(); 24 return tmp; 25 } 26 return -1; 27 } 28 }; 29 30 /** 31 * Your CQueue object will be instantiated and called as such: 32 * CQueue* obj = new CQueue(); 33 * obj->appendTail(value); 34 * int param_2 = obj->deleteHead(); 35 */
7.1
【复习-动规】最长重复子数组-力扣
这题与“最长公共子序列”有点相似,但暗含有连续数组的意思,某种程度上简化了状态转移方程。节省内存可以用一维数组,对数组剩下的那一维度逆序遍历,省略的维度顺序遍历即可。
参考代码:

1 class Solution { 2 public: 3 int findLength(vector<int>& A, vector<int>& B) { 4 int la=A.size(); 5 int lb=B.size(); 6 int dp[1100], ans=0; 7 //dp[i][j]表示A的前i个元素形成的数组与B的前j个元素的数组的最长重复子数组个数,简化为一维dp[j] 8 memset(dp,0,sizeof(dp)); 9 for(int i=1;i<=la;++i){ 10 for(int j=lb;j>=1;--j){ 11 if(A[i-1]==B[j-1]) dp[j]=dp[j-1]+1; 12 else dp[j]=0; 13 ans=max(ans,dp[j]);//因为只有连续才有值,故答案是所有值中的最大者,而不是最后者 14 } 15 } 16 return ans; 17 } 18 };
7.2
【强行广搜】有序矩阵中第K小的元素-力扣
一眼就能看出这是道排序题,且数据是部分有序的,正常做法是暴力排序、归并排序、二分查找。写题的时候傻了,居然每层循环sort了一次,生生给stl排序的时间复杂度乘了个n[扶额]……当时一拍脑袋,莫非这题不是在考排序查找?于是反手写了一个广搜,把元素从左上角依次加入优先队列,虽然慢点,但过了。有被自己的脑回路笑到,罢,就当复习广搜算了。
参考代码:

1 //正常暴力排序(其他复杂度较小的排序用别的题练吧,此题不写了) 2 class Solution { 3 public: 4 int kthSmallest(vector<vector<int>>& matrix, int k) { 5 int n=matrix.size(); 6 vector<int> my; 7 for(int i=0;i<n;++i){ 8 for(int j=0;j<n;++j){ 9 my.push_back(matrix[i][j]); 10 } 11 } 12 sort(my.begin(),my.end()); 13 return my[k-1]; 14 } 15 }; 16 17 //非常规做法—广搜 18 class Solution { 19 public: 20 struct Node{ 21 int x, y, v; 22 Node(){} 23 Node(int i, int j, int vv):x(i),y(j),v(vv){} 24 bool operator < (Node b) const { 25 return v>b.v;//变成小顶堆 26 } 27 }; 28 bool vis[300][300]; 29 int kthSmallest(vector<vector<int>>& matrix, int k) { 30 int n=matrix.size(), cnt=0; 31 priority_queue<Node> my; 32 memset(vis, 0, sizeof(vis)); 33 vis[0][0]=1; 34 my.push(Node(0,0,matrix[0][0])); 35 while(!my.empty()){ 36 cnt++; 37 Node tmp=my.top(); 38 if(cnt==k) return tmp.v; 39 int tx=tmp.x, ty=tmp.y; 40 my.pop(); 41 if(tx+1<n && !vis[tx+1][ty]){ 42 my.push( Node(tx+1, ty, matrix[tx+1][ty]) ); 43 vis[tx+1][ty]=1; 44 } 45 if(ty+1<n && !vis[tx][ty+1]){ 46 my.push( Node(tx, ty+1, matrix[tx][ty+1]) ); 47 vis[tx][ty+1]=1; 48 } 49 } 50 return -1; 51 } 52 };
刚刚看到Sun君的优先队列解法很棒,mark一下。神奇版代码:

1 class Solution { 2 public: 3 int kthSmallest(vector<vector<int>>& matrix, int k) { 4 priority_queue<int> pq; 5 for (int i = 0; i < matrix.size(); ++i) { 6 for (int j = 0; j < matrix[0].size(); ++j) { 7 pq.push(matrix[i][j]); 8 if (pq.size() > k) pq.pop(); 9 } 10 } 11 return pq.top(); 12 } 13 };
7.3
今天事有点多,终于把小组分工定了[呼],明天补上!
7.4
【栈】有效的括号-力扣
思路:A.倒序遍历,先找到最右边的左括号,再二次遍历其右边的紧邻是否匹配;B.遍历,对左括号入栈,遇到匹配的右括号就弹出栈顶元素;栈顶不匹配或未匹配完时栈已经为空,返回false。优化:长度为0直接返回true,长度为奇数直接返回false。
B思路的参考代码:

1 class Solution { 2 public: 3 bool isValid(string s) { 4 int n=s.length(); 5 if(n%2) return false; 6 if(!n) return true; 7 stack<int> my; 8 for(int i=0;i<n;++i){ 9 if(s[i]=='('|| s[i]=='['|| s[i]=='{') my.push(s[i]); 10 else{ 11 if(my.empty()) return false; 12 int tmp=(int)s[i] +(int)my.top(); 13 if(tmp==81||tmp==184||tmp==248) my.pop();//为了简便损失了可读性,这里实际上就是判断栈顶元素是否是当前元素的左括号 14 else return false; 15 } 16 } 17 if(!my.empty()) return false; 18 return true; 19 } 20 };
【动规】最长有效括号-力扣
dalao的三种思路,太妙了!①动规,状态转移方程极妙;②栈,先入栈虚拟头元素-1;③正序逆序两次遍历,动态储存左右括号的数量与最新答案
动规思路的参考代码:

1 class Solution { 2 public: 3 int dp[21000];//记录结尾是右括号的 前i个字符中最长有效括号位数 4 int ans=0; 5 int longestValidParentheses(string s) { 6 int n=s.length(); 7 if(n==0) return 0; 8 memset(dp,0,sizeof(dp)); 9 if(s[0]=='(' && s[1]==')') dp[1]=2; 10 for(int i=2;i<n;++i){ 11 if(s[i]=='(') continue;//结尾是左括号的直接=0,实际上也阻断了前面连续有效括号对后面计数的影响,这个0值起到了分隔的作用 12 if(s[i-1]=='(') dp[i]=dp[i-2]+2;//最右边恰好是一对括号 13 else if(i-1-dp[i-1]>=0 && s[i-1-dp[i-1]]=='('){//最右边是两个右括号,向前找靠右侧的右括号对应的左括号,能找到则分几段计数(理解重点) 14 dp[i]=dp[i-1]+2; 15 if(i-2-dp[i-1]>=0) dp[i]+=dp[i-2-dp[i-1]]; 16 } 17 } 18 for(int i=0;i<n;++i){ 19 ans=max(ans,dp[i]); 20 } 21 return ans; 22 } 23 };
【递归回溯】括号生成-力扣
参考代码:

1 class Solution { 2 public: 3 vector<string> my; 4 int Max=-1;//设一个全局变量记录需要的最大左(右)括号数 5 void gen(string s, int k, int l, int r){//k是剩余递归层数,l和r分别记录当前左右括号个数。基本原则是在每层递归,Max>=左括号数>=右括号数 6 if(k==0){ 7 my.push_back(s); 8 return; 9 } 10 if(l==r){ 11 gen(s+"(", k-1, l+1, r); 12 } 13 else{ 14 if(l<Max) gen(s+"(", k-1, l+1, r); 15 gen(s+")", k-1, l, r+1); 16 } 17 return; 18 } 19 vector<string> generateParenthesis(int n) { 20 Max=n; 21 gen("", 2*n, 0, 0); 22 return my; 23 } 24 };
7.5
【匹配】通配符匹配-力扣
这题写的真心心累……13次提交终于通过,真的是面向测试样例编程了hhh 歇够了再去看dalao更好的题解。
自己的两种思路:①递归/去除连续的 '*' 之后的递归,对具体字母和 '?' 匹配一个字符,不匹配return false,对 '*' 递归匹配。但这两种写法都会超时;
②对模式串中的具体字符块搜索匹配。先忽略模式串中的'*',对字母和?搜索最近的匹配,不能匹配的/匹配串未匹配完的return false。但对特殊情况的处理超级麻烦,列举几个特殊情况:a.模式串尾部有*,但匹配串未匹配完,不能返回false;b.模式串头部无*,其余部分都能匹配上,但匹配串头部有未匹配部分,要返回false……另外,要非常小心访问越界。
参考代码:

1 class Solution { 2 public: 3 int myRuntime=0;//用来记录是否是第一个匹配块,处理模式串头部无*,匹配串头部未匹配的特殊情况 4 int myfind(int from, string ss, string pp){ 5 myRuntime++; 6 int lst_loc=-1, lss=ss.length(); 7 for(int i=from;i<lss;++i){ 8 for(int j=0;j<pp.length();++j){ 9 if(i+j>=lss) return -1; 10 if(ss[i+j]!=pp[j] && pp[j]!='?') break; 11 if(j==pp.length()-1){ 12 lst_loc=i+j; 13 return lst_loc; 14 } 15 } 16 } 17 return -1; 18 } 19 bool isMatch(string s, string p) { 20 int ls=s.length(), lp=p.length(); 21 if(ls==0){ 22 for(int i=0;i<lp;++i){ 23 if(p[i]!='*') return false; 24 } 25 return true; 26 } 27 if(lp==0) return false; 28 int ptr=0, lstres=0; 29 string ptmp=""; 30 for(int i=0;i<lp;++i){ 31 while(p[i]!='*' && i<lp){ 32 ptmp+=p.substr(i,1); 33 i++; 34 } 35 if(ptmp!=""){ 36 int itmp=myfind(ptr, s, ptmp); 37 if(myRuntime==1 && p[0]!='*' && itmp+1-ptmp.length()!=0) return false; 38 if(itmp==-1) return false; 39 else ptr=itmp+1; 40 } 41 ptmp=""; 42 } 43 if(ptr!=ls && p[lp-1]!='*'){ 44 string ptmp2=""; 45 int lpp=ptmp2.length(), i2=0; 46 for(i2=lp-1;i2>=0;--i2){ 47 if(p[i2]=='*') break; 48 while(i2>=0 && p[i2]!='*'){ 49 ptmp2=p.substr(i2,1)+ptmp2; 50 i2--; 51 } 52 if(i2<0 || p[i2]=='*') break; 53 } 54 lpp=ptmp2.length(); 55 for(int j=0;j<lpp;++j){ 56 if(ls-1-j<0 || s[ls-1-j]!=ptmp2[lpp-1-j] && ptmp2[lpp-1-j]!='?') return false; 57 } 58 if(i2<0 || p[i2]!='*') return false; 59 } 60 return true; 61 } 62 };
7.6
7.7
7.8
【动规-水题】三步问题-力扣
优化存储空间!
参考代码:

1 class Solution { 2 public: 3 int waysToStep(int n) { 4 int a=1, b=2, c=4, ans=a+b+c; 5 if(n<3) return n; 6 if(n==3) return 4; 7 n=n-3; 8 while(n--){ 9 ans=((a+b)%1000000007+c)%1000000007; 10 a=b; 11 b=c; 12 c=ans; 13 } 14 return ans; 15 } 16 };
7.9
【动规】恢复空格-力扣
①动规的思路比较常规,关键是剪枝:1.判断有效的单词长度(直接用set存储所有效的长度即可,也可以再加长度上下界的判断);2.已为0的dp[i]直接continue
②另一种思路是“字典树”,稍后整理。
参考代码:

1 //法1:动规,根据有效的单词长度进行状态转移 2 class Solution { 3 public: 4 int min_res[1000];//对前下标<=k的k个字符形成的串断句,能达到的最小未识别字符数 5 int sml=2000, big=0; 6 int min(int a, int b){ 7 if(a<=b) return a; 8 return b; 9 } 10 int max(int a, int b){ 11 if(a>=b) return a; 12 return b; 13 } 14 int respace(vector<string>& dictionary, string sentence) { 15 int slen=sentence.length(), dlen=dictionary.size(); 16 if(dlen==0 || slen==0) return slen; 17 set<string> newdic; 18 set<int> mylen; 19 for(string str : dictionary){ 20 newdic.insert(str); 21 mylen.insert(str.length()); 22 } 23 for(int i=0;i<dlen;++i){ 24 sml=min(sml,dictionary[i].length()); 25 big=max(big,dictionary[i].length()); 26 newdic.insert(dictionary[i]); 27 } 28 for(int k=0;k<slen;++k){ 29 string tmp; 30 tmp.assign(sentence, 0, k+1); 31 if(newdic.find(tmp)!=newdic.end()) min_res[k]=0; 32 else min_res[k]=k+1; 33 for(int i=sml;i<=k && i<=big;++i){ 34 if(mylen.find(i)==mylen.end()) continue; 35 tmp.assign(sentence, k-i+1, i); 36 if(newdic.find(tmp)!=newdic.end()) min_res[k]=min(min_res[k], min_res[k-i]); 37 } 38 for(int i=1;;++i){ 39 if(k-i<0) break; 40 if(i>=min_res[k]-min_res[k-i]) break; 41 min_res[k]=min(min_res[k], min_res[k-i]+i); 42 } 43 } 44 return min_res[slen-1]; 45 } 46 };
……(这几天都有做题,参加了两场周赛,有点疲于总结了,不好不好。赶快补上啊)
7.13
【二叉树】重建二叉树-力扣
复习二叉树构建和遍历的好题。①自己的方法,额外开了内存进行递归,慢且笨拙;②dalao的方法-自己复刻了一遍,巧用STL的find函数,用指针传参也很有魅力hhh
参考代码:

1 /** 2 * Definition for a binary tree node. 3 * struct TreeNode { 4 * int val; 5 * TreeNode *left; 6 * TreeNode *right; 7 * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 8 * }; 9 */ 10 //法① 11 class Solution { 12 public: 13 TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) { 14 int n=preorder.size(); 15 TreeNode* root= new TreeNode; 16 root->right=new TreeNode; 17 root->left =new TreeNode; 18 if(n<=1){ 19 if(n==0) root=NULL; 20 else{ 21 root->val=preorder[0]; 22 root->right=NULL; 23 root->left=NULL; 24 } 25 return root; 26 } 27 root->val=preorder[0]; 28 vector<int> pretmp1; 29 vector<int> pretmp2; 30 vector<int> intmp1; 31 vector<int> intmp2; 32 bool find=false; 33 for(int i=0;i<n;++i){ 34 if(inorder[i]==preorder[0]){ 35 find=true; 36 if(i>0) pretmp1.push_back(preorder[i]); 37 } 38 else if(!find){ 39 if(i>0) pretmp1.push_back(preorder[i]); 40 intmp1.push_back(inorder[i]); 41 } 42 else{ 43 pretmp2.push_back(preorder[i]); 44 intmp2.push_back(inorder[i]); 45 } 46 } 47 root->left =buildTree(pretmp1, intmp1); 48 root->right=buildTree(pretmp2, intmp2); 49 return root; 50 } 51 }; 52 53 //法② 54 /* 55 *注:学习了TheoWu的做法,自己重写的 56 *链接:https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/solution/cjian-dan-shi-xian-di-gui-by-theowu/ 57 */ 58 class Solution { 59 public: 60 TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) { 61 return Build(preorder.begin(),preorder.end(),inorder.begin(),inorder.end()); 62 } 63 TreeNode* Build(vector<int>::iterator pre_s, vector<int>::iterator pre_e, vector<int>::iterator in_s, vector<int>::iterator in_e){ 64 if(pre_s==pre_e) return NULL; 65 TreeNode* cur=new TreeNode(*pre_s); 66 auto root=find(in_s, in_e, *pre_s); 67 cur->left =Build(pre_s+1, pre_s+1+(root-in_s), in_s, root); 68 cur->right=Build(pre_s+1+(root-in_s), pre_e, root+1, in_e); 69 return cur; 70 } 71 };
7.14
【位运算复习】二进制中1的个数-力扣
水题,练手感……今天没做难题,在找实习了555
参考代码:

1 class Solution { 2 public: 3 int hammingWeight(uint32_t n) { 4 int cnt=0; 5 for(int i=0;i<=31;++i){ 6 if(n&(1<<i)) cnt++; 7 } 8 return cnt; 9 } 10 };
7.15
【动规-二叉搜索树】不同的二叉搜索树-力扣
初步解锁二叉搜索树!此题递归方法的思路自己想出来了,可以引申学习一下卡塔兰数。
参考代码:

1 /* 2 二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树) 3 它或者是一棵空树,或者是具有下列性质的二叉树: 4 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 5 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 6 它的左、右子树也分别为二叉查找树。 7 */ 8 //法① 动规 9 class Solution { 10 public: 11 int Max=0; 12 int dp[1000]; 13 int f(int k){ 14 if(dp[k]>0 || k==0) return dp[k]; 15 dp[k]=f(k-1)*2; 16 for(int i=1;i<=k-1;++i){ 17 dp[k]+=f(k-i-1)*f(i); 18 } 19 return dp[k]; 20 } 21 int numTrees(int n) { 22 memset(dp,0,sizeof(dp)); 23 dp[1]=1; 24 dp[2]=2; 25 dp[3]=5; 26 Max=n; 27 return f(n); 28 } 29 }; 30 31 //法② 卡塔兰数计算(力扣提供) 32 33 class Solution { 34 public: 35 int numTrees(int n) { 36 long long C = 1; 37 for (int i = 0; i < n; ++i) { 38 C = C * 2 * (2 * i + 1) / (i + 2); 39 } 40 return (int)C; 41 } 42 };
7.16
【深搜】判断二分图-力扣
啊我解锁二分图了!顺带学了点并查集的概念(好机智啊这种数据结构)。此题的两种思路是①深搜/广搜;②并查集。
匈牙利算法(判断是否图里还有交错的路径)
参考代码:

1 //深搜C++代码 2 class Solution { 3 public: 4 bool vis[1000]; 5 int col[1000]; 6 int n; 7 bool ans=true; 8 void dfs(int k, int c, vector<vector<int>>& graph){ 9 if(!ans) return; 10 if(graph[k].size()==0) return; 11 for(int j=0;j<graph[k].size();++j){ 12 int i=graph[k][j]; 13 if(i==k) continue; 14 if(vis[i]==1){ 15 if(col[i]==1-c) continue; 16 ans=false; 17 return; 18 } 19 vis[i]=1; 20 col[i]=1-c; 21 dfs(i, 1-c, graph); 22 } 23 return; 24 } 25 bool isBipartite(vector<vector<int>>& graph) { 26 memset(vis, 0, sizeof(vis)); 27 memset(col, -1, sizeof(col)); 28 n=graph.size(); 29 for(int i=0;i<n;++i){ 30 for(int j=0;j<graph[i].size();++j){ 31 if(vis[graph[i][j]]) continue; 32 dfs(graph[i][j], 0, graph); 33 } 34 } 35 return ans; 36 } 37 };

1 //dalao的Java代码,下次补上自己的C++/Python代码 2 class Solution { 3 public boolean isBipartite(int[][] graph) { 4 // 初始化并查集 5 UnionFind uf = new UnionFind(graph.length); 6 // 遍历每个顶点,将当前顶点的所有邻接点进行合并 7 for (int i = 0; i < graph.length; i++) { 8 int[] adjs = graph[i]; 9 for (int w: adjs) { 10 // 若某个邻接点与当前顶点已经在一个集合中了,说明不是二分图,返回 false。 11 if (uf.isConnected(i, w)) { 12 return false; 13 } 14 uf.union(adjs[0], w); 15 } 16 } 17 return true; 18 } 19 } 20 21 // 并查集 22 class UnionFind { 23 int[] roots; 24 public UnionFind(int n) { 25 roots = new int[n]; 26 for (int i = 0; i < n; i++) { 27 roots[i] = i; 28 } 29 } 30 31 public int find(int i) { 32 if (roots[i] == i) { 33 return i; 34 } 35 return roots[i] = find(roots[i]); 36 } 37 38 // 判断 p 和 q 是否在同一个集合中 39 public boolean isConnected(int p, int q) { 40 return find(q) == find(p); 41 } 42 43 // 合并 p 和 q 到一个集合中 44 public void union(int p, int q) { 45 roots[find(p)] = find(q); 46 } 47 }
7.17
【复习-二分】第一个错误的版本-力扣
等hr(!后来发现是老板)开完会打面试电话ing……刷会题好了。这道题的测试点比较强,所以二分的条件一定要卡好,另外注意一下大整数相加除以2防止溢出的问题
参考代码:

1 // The API isBadVersion is defined for you. 2 // bool isBadVersion(int version); 3 4 class Solution { 5 public: 6 int firstBadVersion(int n) { 7 int l=0, r=n, m; 8 while(l<r){ 9 m=l/2+r/2+1; 10 if(isBadVersion(m)) r=m-1; 11 else l=m; 12 } 13 if(isBadVersion(l)) return l; 14 return l+1; 15 } 16 };
7.18
7.19
7.20
7.21
7.22
【二分】旋转数组的最小值-力扣
这几天有收到好消息诶。加油!此题是为了考二分而考二分,不许偷偷排序。总体思路是,被“简单洗牌”的数据,前半部分一定大于等于后半部分。
值得注意的点是,重复元素会非常干扰二分查找边界,可以在遇到时先去重,或对区间再二分搜索判断。
参考代码:

1 //没有去重,对重复点的区间查找 2 class Solution {//类似于简单洗牌 3 public: 4 int minArray(vector<int>& numbers) { 5 int n=numbers.size(); 6 int s=numbers[0], l=0, r=n-1; 7 while(r-l>2){ 8 int t=l/2+r/2; 9 if(numbers[t]<s) r=t; 10 else if(numbers[t]>s) l=t; 11 else{//重复!查找该点到结尾是否为包含答案的区间,注意1个ans+(N-1)个x的特殊情况 12 int p=t; 13 while(numbers[p]==s){ 14 p++; 15 if(p==n-1){ 16 if(numbers[n-1]==s) r=t-1; 17 else l=t+1; 18 break; 19 } 20 } 21 if(p!=n-1) l=t+1; 22 } 23 } 24 for(int i=l-3;i<=l+3;++i){ 25 if(i<0||i>=n) continue; 26 if(numbers[i]<s) return numbers[i]; 27 } 28 return s; 29 } 30 }
8.11
【二叉树】二叉树的层序遍历-力扣
今天开始要尽快做完二叉树tag!
这道题看了提示做出来的,多开一个变量num记录下一层放入的节点个数,每次在队列里处理前num个节点。
参考代码:

1 /** 2 * Definition for a binary tree node. 3 * struct TreeNode { 4 * int val; 5 * TreeNode *left; 6 * TreeNode *right; 7 * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 8 * }; 9 */ 10 class Solution { 11 public: 12 vector<vector<int> > levelOrder(TreeNode* root) { 13 vector<vector<int> > ans; 14 if(!root) return ans; 15 16 queue<TreeNode*> q; 17 q.push(root); 18 int num=1; 19 20 while(!q.empty()){ 21 int ntmp=0; 22 vector<int> vtmp; 23 for(int i=0;i<num;++i){ 24 TreeNode* k=q.front(); 25 q.pop(); 26 vtmp.push_back(k->val); 27 if(k->left){ 28 q.push(k->left); 29 ntmp++; 30 } 31 if(k->right){ 32 q.push(k->right); 33 ntmp++; 34 } 35 } 36 ans.push_back(vtmp); 37 num=ntmp; 38 } 39 40 return ans; 41 } 42 };
8.12
【深搜/图】克隆图-力扣
这道题迫使人用哈希表标记访问过的点,因此创建了一个unordered_map<Node*, Node*>,还蛮有意思的一题。刚开始没做出来,看了解答后自己又敲了三遍代码↓
参考代码:

1 /* 2 // Definition for a Node. 3 class Node { 4 public: 5 int val; 6 vector<Node*> neighbors; 7 8 Node() { 9 val = 0; 10 neighbors = vector<Node*>(); 11 } 12 13 Node(int _val) { 14 val = _val; 15 neighbors = vector<Node*>(); 16 } 17 18 Node(int _val, vector<Node*> _neighbors) { 19 val = _val; 20 neighbors = _neighbors; 21 } 22 }; 23 */ 24 25 class Solution { 26 public: 27 unordered_map<Node*, Node*> vis; 28 Node* cloneGraph(Node* node) { 29 if(!node) return NULL; 30 if(vis.find(node)!= vis.end()) return vis[node];//先返回这种情况会比较快 31 32 Node* Cnode = new Node(node->val);//别忘了new 33 vis[node] = Cnode;//!! 34 35 for(auto x = node->neighbors.begin(); x!=node->neighbors.end(); ++x){ 36 Cnode->neighbors.push_back(cloneGraph(*x)); 37 } 38 return Cnode; 39 } 40 };
8.13——今天是左撇子日喔!祝左撇子朋友们节日快乐啊[跳跳]
【字符串】字符串相乘-力扣
复习大整数乘法。注:对两个大整数,每两位直接相乘,先将整数存储到答案数组对应的位置上、再逐位进制会较快,另外要多预留首一位,在不需要进一位时通过判断去掉。
参考代码:

1 class Solution { 2 public: 3 string Int2String(int x){ 4 string s = "?"; 5 s[0] = '0'+x; 6 return s; 7 } 8 string multiply(string num1, string num2) { 9 if (num1 == "0" || num2 == "0") { 10 return "0"; 11 } 12 int m = num1.size(), n = num2.size(); 13 vector<int> ansArr( m+n, 0 );//声明vector时预设长度的一种方法,长度为m+n,值均为0(0可以换成其他值) 14 for(int i = m - 1; i >= 0; --i){ 15 for(int j = n - 1; j >= 0; --j){ 16 ansArr[i+j+1] += (num1[i]-'0') * (num2[j]-'0'); 17 } 18 } 19 for(int i = n + m - 1; i > 0; --i){ 20 ansArr[i - 1] += ansArr[i]/10; 21 ansArr[i] %= 10; 22 } 23 int p = 0; 24 string ans = ""; 25 if (ansArr[0]==0) p+=1; 26 while(p < n+m){ 27 ans = ans + Int2String(ansArr[p]); 28 p++; 29 } 30 return ans; 31 } 32 };
【二叉树】填充每个节点的下一个右侧节点指针 II-力扣
题目要求用常数空间O(1)解答,使用的栈空间不计。故不能用广搜/队列(为啥我用了就超时了555),可以用递归。此题的关键是找到next节点,使用bro临时节点在函数中保存并传参。next指针本身就带有方向,所以和队列存储的原理是一致的。
参考代码:

1 /* 2 // Definition for a Node. 3 class Node { 4 public: 5 int val; 6 Node* left; 7 Node* right; 8 Node* next; 9 10 Node() : val(0), left(NULL), right(NULL), next(NULL) {} 11 Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {} 12 Node(int _val, Node* _left, Node* _right, Node* _next) 13 : val(_val), left(_left), right(_right), next(_next) {} 14 }; 15 */ 16 17 class Solution { 18 public: 19 void Run(Node* root, Node* cousin){ 20 if(!root) return; 21 Node* bro = cousin;//临时变量bro用来记录和root子节点同一层(即root的下一层)的右边最靠近的节点 22 if(root->right){ 23 while(bro!=NULL){//while循环的意义是往右走,往下走,直到找到下一层第一个节点 24 if(bro->left){ 25 bro = bro->left; 26 break; 27 } 28 if(bro->right){ 29 bro = bro->right; 30 break; 31 } 32 bro = bro->next; 33 } 34 (root->right)->next = bro; 35 if(root->left) (root->left)->next = (root->right); 36 Run(root->right, bro); 37 Run(root->left, root->right); 38 } 39 else if(root->left){ 40 while(bro!=NULL){ 41 if(bro->left){ 42 bro = bro->left; 43 break; 44 } 45 if(bro->right){ 46 bro = bro->right; 47 break; 48 } 49 bro = bro->next; 50 } 51 if(root->right){ 52 (root->right)->next = bro; 53 (root->left)->next = (root->right); 54 } 55 else{ 56 (root->left)->next = bro; 57 } 58 Run(root->left, (root->left)->next); 59 Run(root->right, bro); 60 } 61 return ; 62 } 63 Node* connect(Node* root) { 64 Run(root, NULL); 65 return root; 66 } 67 };
【二叉树】二叉树的最近公共祖先-力扣
参考代码:

1 /** 2 * Definition for a binary tree node. 3 * struct TreeNode { 4 * int val; 5 * TreeNode *left; 6 * TreeNode *right; 7 * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 8 * }; 9 */ 10 class Solution { 11 public: 12 TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { 13 if(!root || root == p || root == q) return root; 14 TreeNode* l = lowestCommonAncestor(root->left, p, q); 15 TreeNode* r = lowestCommonAncestor(root->right, p, q); 16 //当l和r都不为NULL时,只有一种可能,说明p, q与root直接相连,在其左右两侧,应返回root; 17 //l和r不可能都为NULL(跟丢了p和q) 18 //故,当r、l其中一个为NULL,应返回另一个值 19 if(!l) return r; 20 if(!r) return l; 21 return root; 22 } 23 };
【二叉树】二叉树的序列化与反序列化-力扣
这题也太难了呜呜……下次应该先看看题目难度再做。参考了forgetthing大佬的代码,又加了点注释↓
参考代码:

1 /** 2 * Definition for a binary tree node. 3 * struct TreeNode { 4 * int val; 5 * TreeNode *left; 6 * TreeNode *right; 7 * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 8 * }; 9 */ 10 class Codec { 11 public: 12 13 // Encodes a tree to a single string. 14 string serialize(TreeNode* root) { 15 string res; 16 dfs_s(root, res); 17 return res; 18 } 19 20 // 前序遍历序列转化为字符串 //注:在字符串中加null和空格,就只需要一种遍历方式 21 void dfs_s(TreeNode* root, string& res) { 22 if (!root) { 23 res += "null "; 24 return; 25 } 26 res += to_string(root->val) + ' '; 27 dfs_s(root->left, res); 28 dfs_s(root->right, res); 29 } 30 31 // Decodes your encoded data to tree. 32 TreeNode* deserialize(string data) { 33 // 开始遍历索引 34 int u = 0; 35 return dfs_d(data, u); 36 } 37 38 TreeNode* dfs_d(string& data, int& u) { //还要考虑多位整数的计算 39 if (u >= data.size()) return NULL; 40 if (data[u] == 'n') { 41 u = u + 5; 42 return NULL; 43 } 44 int val = 0, sign = 1; 45 if (data[u] == '-') sign = -1, u ++ ; 46 while(data[u] != ' '){val = val * 10 + data[u] - '0'; u++;} 47 val *= sign; 48 u = u + 1 ; 49 50 auto root = new TreeNode(val); 51 root->left = dfs_d(data, u); 52 root->right = dfs_d(data, u); 53 54 return root; 55 } 56 }; 57 58 // Your Codec object will be instantiated and called as such: 59 // Codec codec; 60 // codec.deserialize(codec.serialize(root));
8.14
今天也非常特殊,因为今天力扣的每日一题终于是一道做过的题了,而且今天进入新的tag啦,开启"二叉搜索树"!以后会有越来越多的题被做过啊
【二叉搜索树】验证二叉搜索树-力扣
这道题算是二叉搜索树入门级的练习,注意整数的边界一定要取满,是2147483648(1<<31)和-2147483648
参考代码:

1 /** 2 * Definition for a binary tree node. 3 * struct TreeNode { 4 * int val; 5 * TreeNode *left; 6 * TreeNode *right; 7 * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 8 * }; 9 */ 10 class Solution { 11 public: 12 bool judge(TreeNode* root, int up, int down, int upValid, int downValid){ 13 if(!root) return true; 14 int ov = root->val; 15 if(root->left){ 16 int lv = root->left->val; 17 if( lv >= ov || (downValid && lv <= down) || (upValid && lv >= up) ) return false; 18 if(!judge(root->left, min(ov, up), down, true, downValid) ) return false; 19 } 20 if(root->right){ 21 int rv = root->right->val; 22 if( rv <= ov || (downValid && rv <= down) || (upValid && rv >= up) ) return false; 23 if(!judge(root->right, up, max(ov, down), upValid, true) ) return false; 24 } 25 return true; 26 } 27 bool isValidBST(TreeNode* root) { 28 if(!root) return true; 29 return judge(root, 2147483647, -2147483648, false, false); 30 } 31 };
【二叉搜索树】二叉搜索树迭代器-力扣
这题不难,主要考中序遍历。AC后看别人的题解,一大半都是用栈模拟,用vector的人好少hhh

1 /** 2 * Definition for a binary tree node. 3 * struct TreeNode { 4 * int val; 5 * TreeNode *left; 6 * TreeNode *right; 7 * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 8 * }; 9 */ 10 class BSTIterator { 11 public: 12 vector<int> vals; 13 int p; 14 int len; 15 BSTIterator(TreeNode* root){ 16 Inorder(root); 17 p = 0; 18 len = vals.size(); 19 } 20 void Inorder(TreeNode* root){ 21 if(!root) return; 22 if(root->left){ 23 Inorder(root->left); 24 } 25 vals.push_back(root->val); 26 if(root->right){ 27 Inorder(root->right); 28 } 29 return; 30 } 31 /** @return the next smallest number */ 32 int next() { 33 return vals[p++]; 34 } 35 36 /** @return whether we have a next smallest number */ 37 bool hasNext() { 38 return p < len; 39 } 40 }; 41 42 /** 43 * Your BSTIterator object will be instantiated and called as such: 44 * BSTIterator* obj = new BSTIterator(root); 45 * int param_1 = obj->next(); 46 * bool param_2 = obj->hasNext(); 47 */
8.17
【二叉树】平衡二叉树-力扣
优化递归效率:自顶而下=>自底而上
参考代码:

1 /** 2 * Definition for a binary tree node. 3 * struct TreeNode { 4 * int val; 5 * TreeNode *left; 6 * TreeNode *right; 7 * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 8 * }; 9 */ 10 11 //法一:自顶而下 12 class Solution { 13 public: 14 void cal(TreeNode* rt, int depth, int& ans){ 15 if(!rt) return; 16 if( !(rt->left) && !(rt->right)){ 17 ans = max(ans, depth); 18 return; 19 } 20 if(rt->left) cal(rt->left, depth+1, ans); 21 if(rt->right) cal(rt->right, depth+1, ans); 22 return; 23 } 24 int maxDepth(TreeNode* root) { 25 int ans = 0; 26 if(!root) return 0; 27 cal(root, 1, ans); 28 return ans; 29 } 30 bool isBalanced(TreeNode* root) { 31 if(!root) return true; 32 int t = maxDepth(root->left) - maxDepth(root->right); 33 if(t<-1 || t>1) return false; 34 if(!isBalanced(root->left)) return false; 35 if(!isBalanced(root->right)) return false; 36 return true; 37 } 38 }; 39 40 //法二:自底而上 41 class Solution { 42 public: 43 int cal_h(TreeNode* root){ 44 if(!root) return 0; 45 int rh = cal_h(root->right); 46 int lh = cal_h(root->left); 47 if(rh == -1 || lh == -1 || abs(rh-lh)>1 ) return -1; //返回值非常巧妙! 48 return max(1+rh, 1+lh); 49 } 50 bool isBalanced(TreeNode* root) { 51 return cal_h(root)>=0; 52 } 53 };
【二叉树】删除二叉搜索树中的节点-力扣
这题还真是有那么点意思。分三种情况,①删除叶子节点,直接删;②存在右子树(也就存在中序后继节点),用中序后继节点的值赋值(或交换,注意一下后面待删除节点的值),然后在右子树中递归删除中序后继节点;③存在左子树(也就存在中序前缀节点),用中序前缀节点的值赋值,然后在左子树中递归删除中序前缀节点
另外,卡住半天的地方是递归删除时,参数应该传root的左(右)子树的根节点,并把返回值赋给左(右)子树的根节点。
参考代码:

1 /** 2 * Definition for a binary tree node. 3 * struct TreeNode { 4 * int val; 5 * TreeNode *left; 6 * TreeNode *right; 7 * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 8 * }; 9 */ 10 class Solution { 11 public: 12 TreeNode* findPre(TreeNode* root){//找中序前缀节点 13 root = root->left; 14 while(root->right){ 15 root = root->right; 16 } 17 return root; 18 } 19 TreeNode* findNext(TreeNode* root){//找到中序后继节点 20 root = root->right; 21 while(root->left){ 22 root = root->left; 23 } 24 return root; 25 } 26 TreeNode* deleteNode(TreeNode* root, int key) { 27 if(!root) return NULL; 28 if(root->val == key){ 29 if(!root->left && !root->right){//要删除的是叶子节点 30 root = NULL; 31 return root; 32 } 33 else if(root->right){//要删除的节点存在右孩子,用后继节点的值补充 34 //右子树向下递归删除该后继节点,后继节点一定没有右孩子 35 TreeNode* tmp = findNext(root); 36 root->val = tmp->val; 37 root->right = deleteNode(root->right, tmp->val); 38 return root; 39 } 40 else if(root->left){//要删除的节点存在左孩子,用前缀节点的值补充 41 //左子树向下递归删除该前缀节点,前缀节点一定没有左孩子 42 TreeNode* tmp = findPre(root); 43 root->val = tmp->val; 44 root->left = deleteNode(root->left, tmp->val); 45 return root; 46 } 47 } 48 else if(root->val < key){ 49 root->right = deleteNode(root->right, key); 50 return root; 51 } 52 else{ 53 root->left = deleteNode(root->left, key); 54 return root; 55 } 56 return root; 57 } 58 };
8.18
【二叉树】有序链表转换二叉树-力扣
这道题与二叉平衡树的构造密切相关,注意构造的方式不唯一。用类似于二分的方法构造平衡二叉树,其合理性和效率可以得到证明。
参考代码:

1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode() : val(0), next(nullptr) {} 7 * ListNode(int x) : val(x), next(nullptr) {} 8 * ListNode(int x, ListNode *next) : val(x), next(next) {} 9 * }; 10 */ 11 /** 12 * Definition for a binary tree node. 13 * struct TreeNode { 14 * int val; 15 * TreeNode *left; 16 * TreeNode *right; 17 * TreeNode() : val(0), left(nullptr), right(nullptr) {} 18 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} 19 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} 20 * }; 21 */ 22 class Solution { 23 public: 24 TreeNode* helper(vector<int>& nums, int left, int right) { 25 if (left > right) { 26 return nullptr; 27 } 28 // 总是选择中间位置左边的数字作为根节点 29 int mid = (left + right) / 2; 30 TreeNode* root = new TreeNode(nums[mid]); 31 root->left = helper(nums, left, mid - 1); 32 root->right = helper(nums, mid + 1, right); 33 return root; 34 } 35 TreeNode* sortedListToBST(ListNode* head) { 36 vector<int> nums; 37 while(head){ 38 nums.push_back(head->val); 39 head = head->next; 40 } 41 return helper(nums, 0, nums.size() - 1); 42 } 43 };
8.21
【二叉树】数据流中的第K大元素-力扣
第一次用二叉搜索树的形式完成这种题hh,耗时还真是……一言难尽啊。贴一下自己和大佬YouLookDeliciousC 的代码,大佬在构建树时就已经开始计数了,更快,没有对比没有伤害诶
参考代码:

1 /** 2 * Your KthLargest object will be instantiated and called as such: 3 * KthLargest* obj = new KthLargest(k, nums); 4 * int param_1 = obj->add(val); 5 */ 6 7 //1.自己的代码,很笨拙,为了处理特殊情况不得不增加全局变量 8 class KthLargest { 9 public: 10 class MyTree{ 11 public: 12 int val; 13 MyTree* left = NULL; 14 MyTree* right = NULL; 15 MyTree(int x):val(x){} 16 MyTree(){} 17 }; 18 MyTree* build(MyTree* root, vector<int>& nums, int s, int e){ 19 if(s>=e) return NULL; 20 int t = (e+s)/2; 21 if(root==NULL){ 22 root = new MyTree(nums[t]); 23 } 24 else root->val = nums[t]; 25 root->left = build(root->left, nums, s, t); 26 root->right = build(root->right, nums, t+1, e); 27 return root; 28 } 29 30 int cnt = 0, ans = -1, key = -1, len = -1; 31 MyTree* Myroot = new MyTree(-1); 32 bool empty = true; 33 34 void help(MyTree* root, int val){ 35 if(!root){ 36 root = new MyTree(val); 37 return; 38 } 39 if(root->val < val){ 40 if(!root->right){ 41 root->right = new MyTree(val); 42 return; 43 } 44 help(root->right, val); 45 return; 46 } 47 else{ 48 if(!root->left){ 49 root->left = new MyTree(val); 50 return; 51 } 52 help(root->left, val); 53 return; 54 } 55 return; 56 } 57 58 void Scan(MyTree* s){ 59 if(!s) return; 60 Scan(s->right); 61 cnt++; 62 if(cnt == key){ 63 ans = s->val; 64 return; 65 } 66 else if (cnt>key){ 67 return; 68 } 69 Scan(s->left); 70 return; 71 } 72 73 KthLargest(int k, vector<int>& nums) { 74 key = k; 75 len = nums.size(); 76 if(len>0) empty = false; 77 sort(nums.begin(), nums.end()); 78 Myroot = build(Myroot, nums, 0, len ); 79 } 80 81 int add(int val) { 82 cnt = 0; 83 ans = -1; 84 if(empty){ 85 Myroot = new MyTree(val); 86 empty = false; 87 } 88 else help(Myroot, val); 89 Scan(Myroot); 90 return ans; 91 } 92 }; 93 94 //2.大佬的代码,简洁! 95 class KthLargest { 96 struct TreeNode{ 97 int val; 98 int count; // 用以记录以包括当前节点,左右子树在内。有多少个节点。 99 TreeNode* left; 100 TreeNode* right; 101 TreeNode(int x): val(x), left(NULL), right(NULL), count(1) {} 102 }; 103 public: 104 105 int K; 106 TreeNode* root; 107 TreeNode* addNode(TreeNode* root, int val){ // 构建二叉搜索树 108 if(!root) return new TreeNode(val); 109 if(val <= root -> val){ //如果子树的点加一,该节点的count值加一 110 root -> count += 1; 111 root -> left = addNode(root -> left, val); 112 }else{ 113 root -> count += 1; 114 root -> right = addNode(root -> right, val); 115 } 116 return root; 117 } 118 int findNum(TreeNode* treeRoot, int k){ 119 int m = 1; 120 if(treeRoot -> right) m = treeRoot -> right -> count + 1; //m是root加上右子树的所有节点的数量 121 if(k == m) return treeRoot -> val; 122 if(k < m) { // 右子树的节点大于k,目标点在右子树 123 return findNum(treeRoot -> right, k); 124 } 125 else{ 126 return findNum(treeRoot -> left, k - m); //右子树的节点加上根节点小于k个,目标点在左子树 127 } 128 } 129 130 131 KthLargest(int k, vector<int>& nums): K(k), root(NULL) { 132 for(auto i : nums){ //创建二叉搜索树 133 root = addNode(root, i); 134 } 135 } 136 137 int add(int val) { 138 root = addNode(root, val); //更新子树 139 return findNum(root, K); 140 } 141 };
9.16
【二分法&快慢指针】寻找重复数-力扣
挺神奇的一道题,在限制了大小为n+1的数组中只含有1~n的数后,有很多解法。挑两种总结一下下:
①二分法(有点强行二分的意思)
“我们定义cnt[i] 表示nums[] 数组中小于等于 i 的数有多少个,假设我们重复的数是 target,那么[1,target−1]里的所有数满足 cnt[i]≤i,[target,n] 里的所有数满足cnt[i]>i,具有单调性。” ——力扣
②快慢指针(Floyd 判圈算法)
此方法真的惊艳。使用条件为,图中存在环。
操作:
“我们先设置慢指针slow 和快指针 fast ,慢指针每次走一步,快指针每次走两步,根据「Floyd 判圈算法」两个指针在有环的情况下一定会相遇,此时我们再将slow 放置起点 0,两个指针每次同时移动一步,相遇的点就是答案。” ——力扣
原理:
“假设环长为 L,从起点到环的入口的步数是 a,从环的入口继续走 b 步到达相遇位置,从相遇位置继续走 c 步回到环的入口,则有 b+c=L,其中 L、a、b、c 都是正整数。根据上述定义,慢指针走了 a+b 步,快指针走了 2(a+b) 步。从另一个角度考虑,在相遇位置,快指针比慢指针多走了若干圈,因此快指针走的步数还可以表示成 a+b+kL,其中 k 表示快指针在环上走的圈数。联立等式,可以得到
2(a+b)=a+b+kL
解得 a=kL−b,整理可得
a=(k-1)L+(L-b)=(k-1)L+c
从上述等式可知,如果慢指针从起点出发,快指针从相遇位置出发,每次两个指针都移动一步,则慢指针走了 a 步之后到达环的入口,快指针在环里走了 k−1 圈之后又走了 c 步,由于从相遇位置继续走 c 步即可回到环的入口,因此快指针也到达环的入口。两个指针在环的入口相遇,相遇点就是答案。”
——力扣
快慢指针法参考代码:

1 class Solution { 2 public: 3 int findDuplicate(vector<int>& nums) { 4 int slow = 0, fast = 0; 5 do { 6 slow = nums[slow]; 7 fast = nums[nums[fast]]; 8 } while (slow != fast); 9 slow = 0; 10 while (slow != fast) { 11 slow = nums[slow]; 12 fast = nums[fast]; 13 } 14 return slow; 15 } 16 };
-------------1个月100道题的flag倒了,抽空继续完成。现在在准备几天后基于HackerRank的1h笔试网测--------------------
9.26
【数组&排序】New Year Chaos -HackerRank
容易超时,需要精确优化。自己的两种思路,仅供参考:①逆序对的数量就是答案,但是用两层循环计数、一半测试点会超时;②原题可看做“冒泡打乱”,就用冒泡排序还原,记录交换位置的次数。注意要不断优化冒泡排序的区间。
参考代码:

1 #include <bits/stdc++.h> 2 using namespace std; 3 vector<string> split_string(string); 4 5 // Complete the minimumBribes function below.//只需完成此函数代码 6 void minimumBribes(vector<int> q){ 7 int ans = 0, len = q.size(); 8 vector<int> pre(len, 0); 9 for(int i = 0; i < len; ++i){ 10 if(q[i] - (i+1) > 2){ 11 cout<<"Too chaotic"<<endl; 12 return; 13 } 14 } 15 int s = 0, e = len - 1; 16 while(s<e){ 17 while(s < e && q[s] == s+1){ 18 s++; 19 } 20 if(s >= e) break; 21 for(int i = s; i < e; ++i){ 22 if(q[i] > q[i+1]){ 23 ans += 1; 24 swap(q[i], q[i+1]); 25 } 26 } 27 } 28 cout<<ans<<endl; 29 return; 30 } 31 32 int main() 33 { 34 int t; 35 cin >> t; 36 cin.ignore(numeric_limits<streamsize>::max(), '\n'); 37 38 for (int t_itr = 0; t_itr < t; t_itr++) { 39 int n; 40 cin >> n; 41 cin.ignore(numeric_limits<streamsize>::max(), '\n'); 42 43 string q_temp_temp; 44 getline(cin, q_temp_temp); 45 46 vector<string> q_temp = split_string(q_temp_temp); 47 48 vector<int> q(n); 49 50 for (int i = 0; i < n; i++) { 51 int q_item = stoi(q_temp[i]); 52 53 q[i] = q_item; 54 } 55 56 minimumBribes(q); 57 } 58 59 return 0; 60 } 61 62 vector<string> split_string(string input_string) { 63 string::iterator new_end = unique(input_string.begin(), input_string.end(), [] (const char &x, const char &y) { 64 return x == y and x == ' '; 65 }); 66 67 input_string.erase(new_end, input_string.end()); 68 69 while (input_string[input_string.length() - 1] == ' ') { 70 input_string.pop_back(); 71 } 72 73 vector<string> splits; 74 char delimiter = ' '; 75 76 size_t i = 0; 77 size_t pos = input_string.find(delimiter); 78 79 while (pos != string::npos) { 80 splits.push_back(input_string.substr(i, pos - i)); 81 82 i = pos + 1; 83 pos = input_string.find(delimiter, i); 84 } 85 86 splits.push_back(input_string.substr(i, min(pos, input_string.length()) - i + 1)); 87 88 return splits; 89 }
posted on 2020-06-21 23:54 Mju_halcyon 阅读(170) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构