繼續我們的刷題之旅
十一 盛最多水的容器
十二 整數轉羅馬數字
十三 羅馬數字轉整數
十五 三數之和
十六 最接近的三數之和
十七 電話號碼的字母組合
十八 四數之和
十九 刪除鏈表的倒數第N個節點
二十 有效的括號
十一 盛最多水的容器
這題我見過還做過!首先是O(n^2)的暴力方法,我就不說了,然後我想了想,嗯,想不起來了。。。
看了題解,名曰雙指針法(來自力扣官方題解):
“這種方法背後的思路在於,兩線段之間形成的區域總是會受到其中較短那條長度的限制。此外,兩線段距離越遠,得到的面積就越大。我們在由線段長度構成的數組中使用兩個指針,一個放在開始,一個置於末尾。 此外,我們會使用變量 maxarea 來持續存儲到目前爲止所獲得的最大面積。 在每一步中,我們會找出指針所指向的兩條線段形成的區域,更新 maxarea,並將指向較短線段的指針向較長線段那端移動一步。”
舉個例子:
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--)。
建議這題一定要畫着圖自己好好理解想想。
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就可以了
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”這種情況,我們將這兩個當作一個整體來處理即可。
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了
代碼如下:
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--,這中間的去重過程和上一題是相同的
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建立數字與字母的對應關係,然後我就懵逼了,循環是不可能循環的,所以只能遞歸,提到遞歸我就傻眼了,想了半天沒想出來該怎麼遞歸。。。遞歸是個大弱項!
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,去重思路和之前幾個都是一樣的
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個節點爲第一個節點時,需要加判斷。。
/**
* 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,也不會出現我那樣的在頭上的尷尬情況。
題解:
/**
* 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!
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;
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;
}
};