LeetCode刷题之路(二)(11~20)
继续我们的刷题之旅
十一 盛最多水的容器
十二 整数转罗马数字
十三 罗马数字转整数
十五 三数之和
十六 最接近的三数之和
十七 电话号码的字母组合
十八 四数之和
十九 删除链表的倒数第N个节点
二十 有效的括号
十一 盛最多水的容器
这题我见过还做过!首先是O(n^2)的暴力方法,我就不说了,然后我想了想,嗯,想不起来了。。。
看了题解,名曰双指针法(来自力扣官方题解):
“这种方法背后的思路在于,两线段之间形成的区域总是会受到其中较短那条长度的限制。此外,两线段距离越远,得到的面积就越大。我们在由线段长度构成的数组中使用两个指针,一个放在开始,一个置于末尾。 此外,我们会使用变量 maxarea 来持续存储到目前为止所获得的最大面积。 在每一步中,我们会找出指针所指向的两条线段形成的区域,更新 maxarea,并将指向较短线段的指针向较长线段那端移动一步。”
举个例子:
[code]1 8 6 2 5 4 8 3 7
开始时两指针一个放在开始处的1处(i),一个放在末端的7处(j),然后此时maxarea=8,然后因为1<7,所以位于1端的指针会向7移动,即i++,然后此时maxarea=49,因为i>j,所以此时j向i移动,即j--,重复此操作,直到i>=j结束
哇!这么神奇的操作!我们来想一想为什么这样不会漏掉最大值,力扣官方解释:“最初我们考虑由最外围两条线段构成的区域。现在,为了使面积最大化,我们需要考虑更长的两条线段之间的区域。如果我们试图将指向较长线段的指针向内侧移动,矩形区域的面积将受限于较短的线段而不会获得任何增加。但是,在同样的条件下,移动指向较短线段的指针尽管造成了矩形宽度的减小,但却可能会有助于面积的增大。因为移动较短线段的指针会得到一条相对较长的线段,这可以克服由宽度减小而引起的面积减小。”
嗯有点儿清楚了,下面评论中有位热心网友的见解也是很好的:“以低指针为其中一条边的所有情况里面,一开始高指针没移动之前,面积是最大的,因为他宽最大”。在这里要注意的是,求取的区域是连在一起的,是一个整块,所以从左右两端开始往里缩是合理的。
或许有人会问“若此时指针指向了两个相同高的该怎么移动?”答案是随便(既可以i++,也可以j--)。
建议这题一定要画着图自己好好理解想想。
[code]class Solution { public: int maxArea(vector<int>& height) { int ans=0; int i=0; int j=height.size()-1; while(i<j){ if(height[i]>height[j]){ ans=max(ans,height[j]*(j-i)); j--; }else{ ans=max(ans,height[i]*(j-i)); i++; } } return ans; } };
十二 整数转罗马数字
示例 1:输入: 3
输出: "III"
示例 2:输入: 4
输出: "IV"
示例 3:输入: 9
输出: "IX"
示例 4:输入: 58
输出: "LVIII"
解释: L = 50, V = 5, III = 3.
这题我就先将对应的数字与罗马数字存在结构体数组中,然后也是贪心的思想吧,从大数开始分解
比如1994中包含一个1000(除以1000便可知有多少个1000,然后减去这多少个1000,后面同理),一个900,一个90,一个4就可以了
[code]struct jiegou{ int shuzi; string zifuchuan; }; class Solution { public: string intToRoman(int num) { jiegou arr[13]; arr[0].shuzi=1; arr[0].zifuchuan="I"; arr[1].shuzi=4; arr[1].zifuchuan="IV"; arr[2].shuzi=5; arr[2].zifuchuan="V"; arr[3].shuzi=9; arr[3].zifuchuan="IX"; arr[4].shuzi=10; arr[4].zifuchuan="X"; arr[5].shuzi=40; arr[5].zifuchuan="XL"; arr[6].shuzi=50; arr[6].zifuchuan="L"; arr[7].shuzi=90; arr[7].zifuchuan="XC"; arr[8].shuzi=100; arr[8].zifuchuan="C"; arr[9].shuzi=400; arr[9].zifuchuan="CD"; arr[10].shuzi=500; arr[10].zifuchuan="D"; arr[11].shuzi=900; arr[11].zifuchuan="CM"; arr[12].shuzi=1000; arr[12].zifuchuan="M"; string ans=""; for(int i=12;i>=0;i--){ int count=num/arr[i].shuzi; for(int j=0;j<count;j++){ ans+=arr[i].zifuchuan; num=num-arr[i].shuzi; } } return ans; } };
十三 罗马数字转整数
这题和上一题是反过来的,我们需要注意的就是类似于“IV”这种的,而如果不出现这种的话,位于前面的字母表示的数都是大于位于后面的字母所表示的数据,所以我们只需要判断相邻的两个字符中,若前一个字符所代表的数小于后一个字符所代表的数的话就是出现了“IV”这种情况,我们将这两个当作一个整体来处理即可。
[code]class Solution { public: int romanToInt(string s) { int ans=0; map<char,int>mapTest; mapTest['I']=1; mapTest['V']=5; mapTest['X']=10; mapTest['L']=50; mapTest['C']=100; mapTest['D']=500; mapTest['M']=1000; for(int i=0;i<s.length();){ if(mapTest[s[i]]<mapTest[s[i+1]]){ ans+=(mapTest[s[i+1]]-mapTest[s[i]]); i+=2; }else{ ans+=mapTest[s[i]]; i+=1; } } return ans; } };
十五 三数之和
第一题是两数之和就把我看懵了,这来了个三数之和,不行我得好好看看!我们想一下三个数之和为0,会有几种情况呢?
两正+一负(负的很大)
两负+一正(正的很大)
一负+一正+0
三0
应该就这几种情况,而如果数组杂乱无章的话,是不好正负关系来找的,所以我们首先想到要对数组排个序
负的在前,正的在后
然后就呈现一种这样的情形:
- - - - - (0) + + + +
一开始的想法:
头放一个指针(专指负值),尾放一个指针(专指正值),根据头与尾指针处正负间的关系,来放第三个指针,是靠近头指针呢?还是靠近尾指针呢?然后进行各种移动。。一开始写了一段程序,然后提交,然后改正,然后让我认清了现实,这种方法不对!因为头尾指针移动会导致很多情况考虑不到,而且重复的情况还不好解决(为了解决重复,我居然调用了vector的unique函数来对结果去重。。详情见https://blog.csdn.net/weixin_42412973/article/details/99291499)
然后我懵了,开始看评论与题解:
1、暴力找三个数(不提了);
2、类似于两数之和那道题(a+b+c=0等价于a+b=-c,找两个数让其和等于-c,这就转到了第一道题(map));
3、和我的一开始是一样的,先排序,然后也是三指针法哦!我就靠着这种方法,把这题过了:
以[-4 -2 -2 0 1 2 2 3 3 4 4 6 6]为例:
大概是个这么个过程,我们可以看见这个是考虑了所有的情况的,因为i是遍历完整个负数和0的,而且对于每个i又遍历了所有的之前未遍历的情况(k和j的移动)
在这里说一下考虑重复的情况
while(j<k&&nums[j]==nums[j+1]){
j++;
}
代码中有这么一段,这是在nums[i]+nums[j]+nums[k]==0的情况下也是在nums[i]此时是不变的情况下,要改变nums[j]的值,才能找到对应的改变的nums[k]
这一段对i的判断也是这样,可能会有人问,为啥要是这个条件而不是nums[i]==nums[i+1]就可以了呢?
因为nums[i+1]很可能就会是nums[j],考虑一种情况(-2,-2,0,4)如果一开始跳过了开头的-2,那么就少了一种情况,换句话说就是在考虑第一个-2的时候,是包括了自己和第二个-2的情况,所以在第二个-2时就不需要考虑前面的-2了
代码如下:
[code]class Solution { public: vector< vector<int> > threeSum(vector<int>& nums) { vector< vector<int> > ans; //vector的排序 sort(nums.begin(),nums.end()); for(int i=0;i<nums.size();i++){ if(nums[i]>0){ break; } //去重复 if(i>0&&nums[i]==nums[i-1]){ continue; } int j=i+1; int k=nums.size()-1; while(j<k){ if((nums[i]+nums[k]+nums[j])==0){ vector<int>temp; temp.push_back(nums[i]); temp.push_back(nums[j]); temp.push_back(nums[k]); ans.push_back(temp); //去重复 while(j<k&&nums[j]==nums[j+1]){ j++; } while(j<k&&nums[k]==nums[k-1]){ k--; } j++; k--; }else if((nums[i]+nums[k]+nums[j])<0){ j++; }else{ k--; } } } return ans; } };
十六 最接近的三数之和
我们发现这道题和上面那道三数之和有着异曲同工之秒,大体思路是差不多的,先排个序,还是三个指针,唯一不一样的是另外两个指针的移动上,在每次移动指针后,我们都将其与一直记录的(其和与target之差)比较大小,若小了,我们更新
而j、k指针的移动是这样的:
当nums[i]+nums[j]+nums[k]<target,j++,k不动,相反j不动,k--,这中间的去重过程和上一题是相同的
[code]class Solution { public: int threeSumClosest(vector<int>& nums, int target) { int temp=INT_MAX; int ans=0; sort(nums.begin(),nums.end()); for(int i=0;i<nums.size();i++){ if(i>0&&nums[i]==nums[i-1]){ continue; } int j=i+1;; int k=nums.size()-1; while(j<k){ if((nums[i]+nums[k]+nums[j]-target)==0){ ans=target; return ans; }else if(nums[i]+nums[k]+nums[j]<target){ if(abs(nums[i]+nums[j]+nums[k]-target)<temp){ temp=abs(nums[i]+nums[j]+nums[k]-target); ans=nums[i]+nums[j]+nums[k]; } while(j<k&&nums[j]==nums[j+1]){ j++; } j++; }else{ if(abs(nums[i]+nums[j]+nums[k]-target)<temp){ temp=abs(nums[i]+nums[j]+nums[k]-target); ans=nums[i]+nums[j]+nums[k]; } while(j<k&&nums[k]==nums[k-1]){ k--; } k--; } } } return ans; } };
十七 电话号码的字母组合
这题先map建立数字与字母的对应关系,然后我就懵逼了,循环是不可能循环的,所以只能递归,提到递归我就傻眼了,想了半天没想出来该怎么递归。。。递归是个大弱项!
[code] class Solution { public: vector<string>ans; map<string,string>mapTest; void dfs(string comb,string next){ if(next.length()==0){ ans.push_back(comb); }else{ string digit=next.substr(0,1); string letters=mapTest[digit]; for(int i=0;i<letters.length();i++){ string letter=letters.substr(i,1); dfs(comb+letter,next.substr(1)); } } } vector<string> letterCombinations(string digits) { mapTest["2"]="abc"; mapTest["3"]="def"; mapTest["4"]="ghi"; mapTest["5"]="jkl"; mapTest["6"]="mno"; mapTest["7"]="pqrs"; mapTest["8"]="tuv"; mapTest["9"]="wxyz"; if(digits.length()!=0){ dfs("",digits); } return ans; } };
这题递归没写出来的原因,主要是因为我是这样考虑的:
将最后一层的结果逐层向上层返。。而应该是逐层累加到最后一层输出!
在这里科普一下C++的substr()函数:
substr(start,length(可选)) 方法返回一个从指定位置开始,并具有指定长度的子字符串。
start:必选参数,所需的子字符串的起始位置,从0开始。
length:可选参数,所需字符串的长度。若 length 为 0 或负数,将返回一个空字符串;如果没有length这一项,则默认为到字符串结尾。
十八 四数之和
这几道题都特别的类似
这个四数之和也是先排序,然后四个指针i、h、k、j,i从开头开始遍历,在每一遍的遍历过程中,j都从末尾开始,h从i+1开始,k从j-1开始,根据与target的关系来调整h、k,去重思路和之前几个都是一样的
[code]class Solution { public: vector<vector<int>> fourSum(vector<int>& nums, int target) { vector<vector<int>> ans; sort(nums.begin(),nums.end()); for(int i=0;i<nums.size();i++){ if(i>0&&nums[i]==nums[i-1]){ continue; } if(i>=nums.size()-3){ break; } int j=nums.size()-1; while(i<j-2){ int h=i+1; int k=j-1; while(h<k){ if(nums[i]+nums[h]+nums[k]+nums[j]==target){ vector<int>temp; temp.push_back(nums[i]); temp.push_back(nums[h]); temp.push_back(nums[k]); temp.push_back(nums[j]); ans.push_back(temp); while(h<k&&nums[h]==nums[h+1]){ h++; } while(h<k&&nums[k]==nums[k-1]){ k--; } h++; k--; }else if(nums[i]+nums[h]+nums[k]+nums[j]<target){ h++; }else{ k--; } } while(j>0&&nums[j]==nums[j-1]){ j--; } j--; } } return ans; } };
十九 删除链表的倒数第N个节点
是链表题哇!题意中示意我们尝试使用一趟遍历,好,想了一下,我的想法是使用三个指针:
第一个指针正常扫,然后记录扫过的节点数,当节点数等于题目中n时,第二个节点出来,等第一个节点扫到末尾时,第二个节点就是这第倒数第n个节点,然后第三个节点的作用就是第二个节点的上一个节点,删除时用
然后写了一发,wa各种情况。。最需要注意的就是当倒数第n个节点为第一个节点时,需要加判断。。
[code]/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode* removeNthFromEnd(ListNode* head, int n) { ListNode *temp=head; ListNode *temp2=head; ListNode *temp3=head; int count=0; if(temp->next==NULL){ head=NULL; }else{ while(temp!=NULL){ count++; if(count==n){ temp2=head; } if(count==(n+1)){ temp3=head; } if(count>n){ temp2=temp2->next; } if(count>n+1){ temp3=temp3->next; } temp=temp->next; } if(count==n){ head=head->next; }else{ //进行删除操作 temp3->next=temp2->next; } } return head; } };
写的比较复杂,看了下题解,大体思路差不多,可人家的实现方式就很强!只需两个指针,而且第二个指针是个空指针!而不像我那样都赋成head,也不会出现我那样的在头上的尴尬情况。
题解:
[code]/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode* removeNthFromEnd(ListNode* head, int n) { ListNode *temp3=new ListNode(0); ListNode *temp=temp3; ListNode *temp2=temp3; temp3->next=head; for(int i=1;i<=n+1;i++){ temp=temp->next; } while(temp!=NULL){ temp=temp->next; temp2=temp2->next; } //temp2是删除节点的前一个节点 temp2->next=temp2->next->next; return temp3->next; } };
二十 有效的括号
括号匹配,当然是用栈了!然后构造匹配时我用了map!
[code]class Solution { public: bool isValid(string s) { bool ans=false; stack<char>st; if(s==""){ ans=true; }else{ st.push(s[0]); map<char,char>mapTest 8000 ; mapTest['(']=')'; mapTest['[']=']'; mapTest['{']='}'; for(int i=1;i<s.length();i++){ if(!st.empty()&&mapTest[st.top()]==s[i]){ st.pop(); }else{ st.push(s[i]); } } if(st.empty()){ ans=true; } } return ans; } };
- leetcode 刷题之路 20 Binary Tree Inorder Traversal
- leetcode11-20
- leetcode 刷题之路 11 Reverse Nodes in k-Group
- 一名前端工程师的自学之路!Js篇(11-20更新)
- Daily Scrum M2 11-20
- LeetCode(20)-- Valid Parentheses
- LeetCode题解11:爬楼梯
- leetcode 20题 32题题解报告
- 习题11-5 指定位置输出字符串(20 分)
- 【leetcode】20. Valid Parentheses(Python & C++)
- LeetCode-20-Valid Parentheses
- 【LeetCode】20. Valid Parentheses
- leetcode: 20. Valid Parentheses
- test_11-20
- 170道Java工程师面试题-解答11-20
- [leetcode-20]Valid Parentheses(java)
- leetcode题解-20.有效的括号
- 分支11 —— 分支17、分支19、分支20
- Leetcode-11:Container With Most Water
- leetcode 20. Valid Parentheses