vector
类型的结果。循环两次,对于每一个数nums[i]
,寻找其后有没有target-nums[i]
的存在。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int tmp=0;
vector<int> ans;
for(int i=0;i<nums.size()-1;i++)
{
for(int j=i+1;j<nums.size();j++)
{
tmp=nums[i]+nums[j];
if(tmp==target)
{
ans.push_back(i);
ans.push_back(j);
}
}
}
return ans;
}
};
// 官方解答
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int n = nums.size();
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (nums[i] + nums[j] == target) {
return {i, j};
}
}
}
return {};
}
};
ATTENTION:
return {}
是C++11的新特性,他返回的是该函数创建的一个初始对象,在这里这个初始对象的类型就是vector
(即返回值类型),而语句return {i, j};
就是返回含有元素i
、j
的一个vector
。return {};
。为了降低复杂度,考虑优化寻找target-nums[i]
的过程。这里使用哈希表,对于每一个数nums[i]
,在哈希表中查找target-nums[i]
的存在,如不存在,则将nums[i]
插入到哈希表中。
由于哈希表中存的是该数的下标,所以查询的复杂度为O(1),因此这种做法的复杂度为O(n)。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
map<int,int> mp;
mp[nums[0]]=0;
for(int i=1;i<nums.size();i++)
{
int tmp=target-nums[i];
map<int,int>::iterator it=mp.find(tmp);
if(it!=mp.end())
return {it->second,i};
mp[nums[i]]=i;
}
return {};
}
};
//官网解答
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> hashtable;
for (int i = 0; i < nums.size(); ++i) {
auto it = hashtable.find(target - nums[i]);
if (it != hashtable.end()) {
return {it->second, i};
}
hashtable[nums[i]] = i;
}
return {};
}
};
ATTENTION:
由于最后需要返回结果链表,所以在创建结果链表l3
后,再创建一个指针s
指向l3
,通过指针s
进行链表的增长,那么l3
就一直指向结果链表的头结点。
对于链表l1
和l2
,先一起向后遍历,同时记录进位cnt
,直到一者为空,只遍历另一者即可。
由于创建结果链表l3
时,第一个结点为空节点,所以返回的是l3->next
。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* l3=new ListNode();
ListNode* p=l1,*q=l2,*s=l3;
int cnt=0; //进位
while(p!=NULL&&q!=NULL) //注意这里不是->next
{
int tmp=p->val+q->val+cnt;
cnt=0;
if(tmp>=10)
{
cnt=1;
tmp-=10;
}
p=p->next;
q=q->next;
//创建新节点
ListNode* n=new ListNode(tmp);
s->next=n;
s=s->next;
}
while(p!=NULL)
{
int tmp=p->val+cnt;
cnt=0;
if(tmp>=10)
{
cnt=1;
tmp-=10;
}
ListNode* n=new ListNode(tmp);
s->next=n;
s=s->next;
p=p->next;
}
while(q!=NULL)
{
int tmp=q->val+cnt;
cnt=0;
if(tmp>=10)
{
cnt=1;
tmp-=10;
}
ListNode* n=new ListNode(tmp);
s->next=n;
s=s->next;
q=q->next;
}
if(cnt==1)
{
ListNode* n=new ListNode(cnt);
s->next=n;
}
return l3->next;
}
};
通过补零使两个链表长度相同,再进行加法计算。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* p=l1,*q=l2,*l3=new ListNode();
ListNode* s=l3;
while(p->next!=NULL||q->next!=NULL) //注意这里是next
{
if(p->next==NULL)
p->next=new ListNode(0);
if(q->next==NULL)
q->next=new ListNode(0);
p=p->next;
q=q->next;
}
p=l1;q=l2;
int cnt=0;
while(p!=NULL)
{
int tmp=p->val+q->val+cnt;
cnt=0;
if(tmp>=10)
{
tmp-=10;
cnt=1;
}
p=p->next;
q=q->next;
s->next=new ListNode(tmp);
s=s->next;
}
if(cnt==1) //注意最后的进位!!!
s->next=new ListNode(1);
return l3->next;
}
};
//写法2
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* head=NULL,*tail=NULL;
int cnt=0; //进位应该初始化在循环外面
while(l1||l2)
{
//这里实际已经实现了补零操作
int n1=l1?l1->val:0;
int n2=l2?l2->val:0;
int tmp=n1+n2+cnt;
cnt=tmp/10; //进位
if(!head) //头结点未创建
{
head=new ListNode(tmp%10);
tail=head; //tail遍历结果链表
}
else
{
tail->next=new ListNode(tmp%10);
tail=tail->next;
}
if(l1) l1=l1->next;
if(l2) l2=l2->next;
}
if(cnt==1) //注意最后的进位!!!
tail->next=new ListNode(1);
return head;
}
};
//官方解答
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; //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;
}
};
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
int cnt=0;
return add(l1,l2,cnt);
}
ListNode* add(ListNode* l1,ListNode* l2,int cnt)
{
if(l1==NULL&&l2==NULL&&cnt==0) return NULL;
int tmp=0;
if(l1)
{
tmp+=l1->val;
l1=l1->next; //必须在l1有意义时才能next
}
if(l2)
{
tmp+=l2->val;
l2=l2->next; //必须在l2有意义时才能next
}
tmp+=cnt;
cnt=tmp/10;
ListNode* ans=new ListNode(tmp%10);
ans->next=add(l1,l2,cnt); //不能在这里next,因为到了NULL后就没有next了
return ans;
}
};
ATTENTION
new
、delete
)。动态分配,系统收到请求后,会在堆中保存一个指针,这个指针指向真实分配的内存空间。堆是不连续的空间。向上。new
:在对上创建数组或对象,返回指向这个数组或对象的指针。最长的不重复子串,最粗暴的想法,就是遍历每个字符,以其为起点,寻找最长的不重复子串。这里我的标记是通过map
设置的,费空间而且重置也很费时间,官方解答使用的哈希表十分值得学习。
class Solution {
public:
map<char,int> mp;
void mpClear() //清空map,以新一轮的标记
{
for(int i=0;i<26;i++)
{
mp[char('a'+i)]=0;
mp[char('A'+i)]=0;
}
for(int i=0;i<10;i++)
mp[char('0'+i)]=0;
mp[' ']=0;
}
int lengthOfLongestSubstring(string s) {
int ans=0;
for(int i=0;i<s.size();i++)
{
//每一轮遍历,重置子串长度和map
int tmp=0;
mpClear();
for(int j=i;j<s.size();j++)
{
if(mp[s[j]]==0)
{
tmp++;
mp[s[j]]=1;
}
else break; //只要遇到出现过的字符,就结束此次遍历
if(tmp>ans) ans=tmp; //并更新ans 这个更新可以用max函数
}
}
return ans;
}
};
滑动窗口
我们使用两个指针表示字符串中的某个子串(或窗口)的左右边界,其中左指针代表着「枚举子串的起始位置」。
在每一步的操作中,我们会将左指针向右移动一格,表示我们开始枚举下一个字符作为起始位置,然后我们可以不断地向右移动右指针,但需要保证这两个指针对应的子串中没有重复的字符。
在移动结束后,这个子串就对应着以左指针开始的,不包含重复字符的最长子串。我们记录下这个子串的长度;
在枚举结束后,我们找到的最长的子串的长度即为答案。
(官方解释)
判断重复字符
在上面的流程中,我们还需要使用一种数据结构来判断是否有重复的字符,常用的数据结构为哈希集合(即 C++ 中的std::unordered_set,Java 中的 HashSet,Python 中的 set, JavaScript 中的Set)。在左指针向右移动的时候,我们从哈希集合中移除一个字符,在右指针向右移动的时候,我们往哈希集合中添加一个字符。
(官方解释)
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int ans=0;
int l=0,r=-1; //初始化:左指针指向第一个元素,右指针应当在左指针左边
unordered_set<char> st;
for(;l<s.size();l++) //枚举左指针(左指针公要移动n-1次)
{
if(l!=0) //左指针移动,需要从哈希表中删去对应元素
st.erase(s[l-1]);
while((r+1)<s.size()&&st.count(s[r+1])==0)
{
r++;
st.insert(s[r]);
}
ans=max(ans,r-l+1);
}
return ans;
}
};
ATTENTION:
map与unordered_map、set与unordered_set的区别
对于长度小于等于2的子串s[i,j]
,若长度为1,即i==j
,则为回文子串,若长度为2,即j=i+1
,则若s[i]==s[j]
,则s[i,j]
为回文子串。
若s[i,j]
为回文子串,且s[i-1]==s[j+1]
,则s[i-1.j+1]
也为回文子串,因此可以用中心扩展的方法寻找最长回文子串。
class Solution {
public:
string longestPalindrome(string s) {
int n=s.size(),ans=0,ansC=0,ansCnt=0;
bool flag=false;
for(int i=0;i<n;i++)
{
int cnt=1;
//奇数情况
while((i-cnt)>=0&&(i+cnt)<n&&s[i-cnt]==s[i+cnt])
cnt++;
if(ans<(2*cnt-1))
{
ans=2*cnt-1;
ansC=i;
ansCnt=cnt;
flag=false;
}
//偶数情况
cnt=0;
while((i-cnt-1)>=0&&(i+cnt)<n&&s[i-cnt-1]==s[i+cnt])
cnt++;
if(ans<2*cnt)
{
ans=2*cnt;
ansC=i;
ansCnt=cnt;
flag=true;
}
}
if(flag)
return s.substr(ansC-ansCnt,ansCnt*2); //substr(起始idx,长度)
else
return s.substr(ansC-ansCnt+1,ansCnt*2-1); //substr(起始idx,长度)
}
};
对于回文串s[i,j]
,如果它是长度大于2(这个信息很关键!!!)的回文串,那么s[i+1,j-1]
也定是一个回文串,因此,可以尝试使用动态规划解决此题。
**只有先讨论了长度小于等于2的回文串,才能讨论长度大于2的回文串。**对于长度大于2的子串s[i,j]
,如果s[i+1,j-1]
是回文子串,并且s[i]==s[j]
,则s[i,j]
也是回文子串。
必须倒着遍历!!!如下图:
例如判断子串s[0,4]
是否是回文串,则需先判断s[1,3]
是否是回文串,要判断s[1,3]
是否是回文串,则需先判断s[2,2]
是否为回文串,因此i
要从后往前遍历,j
从前往后遍历 (为什么j
从后往前遍历也能ac?) 。
class Solution {
public:
string longestPalindrome(string s) {
int n=s.size();
int ansCnt=1,ansIdx=0;
int dp[n][n]; //怎么初始化
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
dp[i][j]=0;
for(int i=0;i<n;i++)
{
dp[i][i]=1;
if(i!=n-1&&s[i]==s[i+1])
{
dp[i][i+1]=1;
if(ansCnt<2)
{
ansCnt=2;
ansIdx=i;
}
}
}
for(int i=n-1;i>=0;i--)
{
for(int j=i;j<n;j++)
{
if(j-i+1>2)
{
if(s[i]==s[j]&&dp[i+1][j-1]==1)
{
dp[i][j]=1;
if(ansCnt<j-i+1)
{
ansCnt=j-i+1;
ansIdx=i;
}
}
}
}
}
return s.substr(ansIdx,ansCnt);
}
};
·
:可以匹配任意一个字符。*
:可以匹配0次或多次的前一个字符,也就是说,*
可以让前面那个字符消失。*:
可以匹配0个字符或者多个·
为了处理两个空字符串相匹配的情况,将dp
矩阵设为dp[s.size()+1][p.size()+1]
,其中,dp[i][j]
表示s
的前i
个字符与p
的前j
个字符的匹配情况,dp[0][0]
表示了两个空字符串相匹配的情况。(注意字符串的下标从0开始,记得对应)
当p[j]
为字符或者'·'
时,都只需比较s[i]
与p[j]
即可;但是,如果p[j]
为'*'
,这时候就要将p[j-1]
与p[j]
作为一个整体一起考虑。
对于p[j]
为'*'
的情况,要么,将p[j-1]
和p[j]
一起删去,要么,从s
中删去一个字符s[i-1]
。
class Solution {
public:
bool isEqual(int i,int j,string s,string p)
{
//下标到字符串下标的对应,要-1!!!
//中文字符int值太大,无法直接比较 阿西吧,这是个句号
// if(j=='·') return true;
// return i==j;
if(i==0) return false; //
if(p[j-1]=='.') return true;
return s[i-1]==p[j-1];
}
bool isMatch(string s, string p) {
int m=s.size(),n=p.size();
vector<vector<int> > dp(m+1,vector<int>(n+1)); //二维数组初始化
dp[0][0]=1; //空字符串的匹配
for(int i=0;i<m+1;i++)
{
for(int j=1;j<n+1;j++)
{
if(p[j-1]=='*')
{
dp[i][j]=dp[i][j-2]||dp[i][j]; //tmd 官方解答里是异或,所以额外的状态转移不会对结果产生影响!!!
if(isEqual(i,j-1,s,p)) dp[i][j]=dp[i-1][j]||dp[i][j];
}
else
{
if(isEqual(i,j,s,p))
dp[i][j]=dp[i-1][j-1]||dp[i][j];
}
}
}
return dp[m][n];
}
};
ATTENTION:
auto
:当编译器能够在一个变量的声明时候就推断出它的类型,那么你就能够用auto
关键字来作为他们的类型。[captures] (params) {Statments;} //[捕获列表](形参){函数体}
capture:
[] 不截取任何变量
[&] 截取外部作用域中所有变量,并作为引用在函数体中使用
//可以通过auto获取返回值
auto ret = [](auto a, auto b) {return a + b; }(1, 1.5);
cout << "ret = " << ret << endl;
首先考虑暴力穷举,复杂度为O(n2),由于数据的数量级为n5,所以复杂度太高,不能直接暴力穷举。所以要删减穷举的情况,这就用到了双指针法。
考虑两板[i,j]
,其宽度为width=j-i
,而其高度由更短的板决定,即高度为min(height[i],height[j])
,所以水的面积为(j-i)*min(height[i],height[j])
。现在考虑移动板。
假设height[i]
j
向内移动,那么得到的新板j'
有两种情况:①比原来的短板低;②比原来的短板高。如果j'
高于短板i
,水的高度不变;j'
如果低于短板i
,那么水的高度还会减少。所以将长板内移,最后得到的水的面积一定不会增加,因此不需要考虑这些情况。i
向内移动,那么得到的新板i'
如果高于旧短板i
,水的高度就会升高,虽然宽度减少了,但是水的面积不一定会减少,因此此时可能获得更大的水的面积。所以,最后得到的结论就是:一直向内移动短板。这可以大大地减少面积不会增加的情况,同时考虑所有面积可能增加的情况。
双指针法的复杂度为O(n),即双指针一共最多遍历整个数组一遍。
class Solution {
public:
int maxArea(vector<int>& height) {
int ans=0,n=height.size();
int left=0,right=n-1;
while(left<right)
{
int w=right-left,h=min(height[left],height[right]);
if(ans<w*h)
ans=w*h;
if(height[left]<height[right])
left++;
else
right--;
}
return ans;
}
};
nums[]
是可能会出现重复数字的。nums[]
排序,对于不同位置的但是重复的元素,可能会搜索到重复的三元组,如nums[]={-1,-1,1,0,-1}
。首先对于寻找一个和为0的三元组,可以等价于寻找一个和为
-a
的二元组,从而可以将三数之和的问题转换为两数之和。两数之和可以用哈希表或者双指针(元素有序的前提下) 求解。
此题的难点更多是在于要求返回的三元组不重复。由于返回的是vector
类型的对象,所以很难在最后对其搜索去重,因此要在求解三元组的同时,就删去那些重复的三元组。
首先对数组nums[]
进行排序,方便后面操作中不处理重复的数字。然后从左往右遍历数组nums[],对于每一个对象nums[i],只在其后寻找可能产生的二元组(满足
b+c=-a
),从而避免一部分的重复三元组。
接着遍历nums[i]
之后的对象,记为nums[j]
。
对于双指针法,使left
指向j
,right
指向n-1
,寻找二元组(满足
b+c=-a
)。由于数组nums[]
是有序的,所以在寻找时可以非常方便地通过比较b+c
与-a
的大小,进行左右指针的移动。 在寻找到一个答案后,左右指针都要向内移动一格,并且新指向的数字不能与原数字相同,否则会导致重复的答案。
双指针法的复杂度为O(n2)。
vector<vector<int>> threeSum(vector<int>& nums) {
int n=nums.size(),cnt=0;
vector<vector<int> > ans;
//先给vector排序
sort(nums.begin(),nums.end());
//对于每个nums[i],在其之后寻找两个数相加为target=0-nums[i]
for(int i=0;i<n;i++)
{
if(i!=0&&nums[i]==nums[i-1]) //不处理重复的元素
continue;
int target=0-nums[i];
//从nums[i]之后中寻找两个数和为target
//双指针法
int left=i+1,right=n-1;
bool flag=false;
while(left<right) //要有两个数,所以不能取等
{
if(flag)
{
//已找到一个三元组,不处理重复的元素
if(nums[left-1]==nums[left])
{
left++;
continue;
}
if(nums[right+1]==nums[right])
{
right--;
continue;
}
flag=false;
}
if((nums[left]+nums[right])<target) left++;
else if((nums[left]+nums[right])>target) right--;
else
{
//找到一个三元组
vector<int> tmp={nums[i],nums[left],nums[right]};
ans.push_back(tmp); //插入
flag=true;
//左右指针都内移一位,继续寻找新的答案
left++;
right--;
}
}
}
return ans;
}
再看哈希表处理两数之和问题:
因为数组nums[]
是有序的,所以不妨假设二元组(b<=c)
。显然,当nums[j]=b
时,哈希表中不存在可以匹配的c
,只有当nums[j]=c
时,才能在哈希表中找到可以匹配的b
。
对于nums[j]=b
,如果c
不在哈希表中,那么就将num[j]
插入到哈希表中;如果c
在哈希表中,那么就找到了一个解答,将该三元组保存,并且还要将nums[j]
插入到哈希表中(因为这个的
c
可能是下一个的
b
)。
需要注意的是,b、c可能不唯一。
b时,
- 如果存在多个
b
,由于哈希表是通过set
实现的,所以哈希表中不会存在两个b
,所以不唯一的b
不会影响搜寻结果。
- 但是不唯一的
c
不同,比如nums[]={-1,0,1,1}
,如若不做约束,当nums[j]
为第二个1时,就会搜寻到一个重复的三元组<-1,0,1>
(因为此时哈希表中存在-1和0)。所以在搜寻到一个符合的三元组时,需要对接下来遍历的nums[j]
进行判断,保证下一个nums[j]
不能为上一个c
。
b=c
时,相当于b
、c
都不唯一,与b时处理雷同。
PS.如果在搜索到一个二元组后,将
b
和c
都从哈希表中删去是否能避免重复的情况呢?
不能! 例如nums[]={0,0,0,0,0}
,当nums[j]
为第二个0时,搜索到一个符合的二元组<0,0>
,此时将0从哈希表中删去,但是当nums[j]
为第四个0时,哈希表中又存在了一个可以匹配的0,此时,会出现重复的答案。
按理说,哈希表法的复杂度也为O(n2),但是在实际运行中,速度远比双指针法慢,也更加耗空间。
vector<vector<int>> threeSum(vector<int>& nums) {
int n=nums.size(),cnt=0;
vector<vector<int> > ans;
//先给vector排序
sort(nums.begin(),nums.end());
//对于每个nums[i],在其之后寻找两个数相加为target=0-nums[i]
for(int i=0;i<n;i++)
{
if(i!=0&&nums[i]==nums[i-1]) //不处理重复的元素
continue;
int target=0-nums[i];
//从nums[i]之后中寻找两个数和为target
unordered_set<int> st; //哈希表中只会存在nums[i]以后的元素
bool flag=false;
for(int j=i+1;j<n;j++)
{
if(flag) //当搜索到一个答案时,要对下一个nums[j]进行约束,不能是重复的数字
{
if(nums[j]==nums[j-1]) continue;
flag=false; //直到nums[j]!=nums[j-1],重置flag,开始搜索
}
if(st.find(target-nums[j])==st.end())
st.insert(nums[j]); //没找到,插入到哈希表中
else
{
// cout<
vector<int> tmp={nums[i],nums[j],target-nums[j]}; //这个插入应该怎么写
ans.push_back(tmp);
st.erase(target-nums[j]);
if(target-nums[j]!=nums[j])
st.insert(nums[j]); //找到也要插入到哈希表中,因为可能还有别的答案
//st.insert(nums[j]); 上三行改为这一行 不影响结果,也就是说可以不删除哈希表中的b
flag=true;
}
}
}
return ans;
}
ATTENTION
sort
函数头文件:#include
vector > vec
赋值:vector<int> tmp={...};
vec.push_back(tmp);
//不能直接给vec[][]赋值。
digits
的长度,那么可以直接暴力嵌套for
循环,但这道题的难点就在于,digits
的长度不定,所以需要用递归来实现for
循环的嵌套。首先判断digits
的长度,如果digits.size()==0
,那么直接返回空;如果digits.size()!=0
,开始递归。
个人认为,自己写的递归不够精简,因为我把第一个数字给单独拿出来了,只递归了后面n-1
个数字。
对于第一个数字对应的每一个字母tmp[i]
,调用递归函数nextNumber(现在的字符串,下一个数字的下标,digits)
。
在递归函数nextNumber(string s,int n,string digits)
中,如果此时n==digits.size()
,说明此时没有下一个数字需要排列组合了,此时将得到的字符串s
插入到ans
数组中。否则,对于当前的数字digits[n]
,对于它对应的每一个字母,都继续调用递归函数nextNumber
,继续与下一个数字进行排列组合,而此时传入函数nextNumber
的字符串为s+tmp[i]
。
数字与字母的对应我写的很丑,可以学习别人的简洁写法。
class Solution {
public:
vector<string> ans;
//这个match可以优化
vector<string> matchedVector(char c)
{
if(c=='2')
{
vector<string> two={"a","b","c"};
return two;
}
else if(c=='3')
{
vector<string> three={"d","e","f"};
return three;
}
else if(c=='4')
{
vector<string> four={"g","h","i"};
return four;
}
else if(c=='5')
{
vector<string> five={"j","k","l"};
return five;
}
else if(c=='6')
{
vector<string> six={"m","n","o"};
return six;
}
else if(c=='7')
{
vector<string> seven={"p","q","r","s"};
return seven;
}
else if(c=='8')
{
vector<string> eight={"t","u","v"};
return eight;
}
else
{
vector<string> nine={"w","x","y","z"};
return nine;
}
}
void nextNumber(string s,int n,string digits)
{
if(n==digits.size())
{
ans.push_back(s);
return;
}
//遍历每一个字母
vector<string> tmp=matchedVector(digits[n]); //对应的字母
for(int i=0;i<tmp.size();i++)
nextNumber(s+tmp[i],n+1,digits); //return就退出函数了,所以要在遍历完所有情况后再return。
}
vector<string> letterCombinations(string digits) {
if(digits=="") return ans;
//这里的tmp只有在digits不为空字符串时才有意义,否则会指向非法内存。
vector<string> tmp=matchedVector(digits[0]);
string s="";
for(int i=0;i<tmp.size();i++)
nextNumber(s+tmp[i],1,digits);
return ans;
}
};
为什么可以这么简洁…
class Solution {
public:
void backTrack(vector<string>& ans,const unordered_map<char,string>& matched,const string& digits,int idx,string& s)
{
if(idx==digits.size())
{
ans.push_back(s);
return ;
}
else
{
char digit=digits[idx];
const string& letters=matched.at(digit);
for(const char& letter:letters)
{
s.push_back(letter); //string竟然也可以push_back(x)!!!
backTrack(ans,matched,digits,idx+1,s);
s.pop_back(); //string竟然也可以pop_back()!!!
}
}
}
vector<string> letterCombinations(string digits) {
vector<string> ans;
if(digits.size()==0) return ans;
unordered_map<char,string> matched{
{'2',"abc"},
{'3',"def"},
{'4',"ghi"},
{'5',"jkl"},
{'6',"mno"},
{'7',"pqrs"},
{'8',"tuv"},
{'9',"wxyz"}
};
string s;
backTrack(ans,matched,digits,0,s);
return ans;
}
};
ATTENTION
void recursion()
{
if(/*递归结束条件*/)
{
//退出
return;
}
//继续递归
recursion();
}
for
循环中的return
问题nextNumber
函数返回一个string
类型的对象,表示生成的字符串,然后在递归外面插入到ans
数组中去,但是一直出现无法遍历除了第一个数字以外数字代表的字母的问题,后来发现是for
循环中的return
的问题。当在for
循环中遇到return时,函数会直接中断for
循环,不仅如此,子函数会直接结束,退出到调用子函数的函数中。int test()
{
for(int i=0;i<10;i++)
{
cout<<i<<endl;
for(int j=1;j<10;j++)
{
cout<<j<<endl;
return 2;
}
}
}
int main()
{
int a=test();
cout<<"a"<<a<<endl;
}
//输出值:
0
1
a2
string
也可以push_back(x)
和pop_back()
!!!for
循环新用法(本质是借助迭代器)for(auto c:arr)
{...}
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(head->next==NULL) return NULL;
int size=0;
ListNode* p=head;
while(p!=NULL)
{
size++;
p=p->next;
}
if(n==size) //删除第一个结点
head=head->next;
else
{
int m=size-n+1;
ListNode* p=head,*q=head;
for(int i=0;i<m-1;i++)
{
q=p;
p=p->next;
}
q->next=p->next;
}
return head;
}
};
设两个指针,一个快指针,一个慢指针,两者相差n
个结点,那么当快指针指向NULL
的时候,慢指针指向的就是要删除结点的前一个结点。(妙)
官方实现里在在第一个结点前加了一个空结点,从而统一第一个结点和其他结点的处理方式。(链表题的常用技巧:dummpy节点)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* p=head,*q;
while(n!=0)
{
n--;
p=p->next; //快指针
}
if(p==NULL) return head->next; //删除第一个结点
q=head; //慢指针
while(p->next!=NULL)
{
p=p->next;
q=q->next;
}
q->next=q->next->next; //不是p->next!!! p和q不一定是相邻关系
return head;
}
};
class Solution {
public:
bool isValid(string s) {
stack<char> st;
for(int i=0;i<s.size();i++)
{
if(s[i]=='('||s[i]=='['||s[i]=='{')
st.push(s[i]);
else
{ //栈空,失败
if(st.empty()) return false;
//栈顶是否是匹配的左括号
if(s[i]==')')
if(st.top()=='(') st.pop();
else return false;
else if(s[i]==']')
if(st.top()=='[') st.pop();
else return false;
else
if(st.top()=='{') st.pop();
else return false;
}
}
if(!st.empty()) return false;
return true;
}
};
ATTENTION
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* head=new ListNode(); //初始化 leetcode必须初始化
ListNode *p=list1,*q=list2;
ListNode* tail=head;
while(p!=NULL&&q!=NULL)
{
cout<<p->val<<" "<<q->val<<endl;
while(p!=NULL&&q!=NULL&&p->val<=q->val) //NULL判断,外围判断此时不奏效(while嵌套while时需要格外注意)
{
ListNode* newNode=new ListNode(p->val); //new创建的是指针
tail->next=newNode;
tail=tail->next;
p=p->next;
}
while(p!=NULL&&q!=NULL&&p->val>q->val) //NULL判断,外围判断此时不奏效(while嵌套while时需要格外注意)
{
ListNode* newNode=new ListNode(q->val);
tail->next=newNode;
tail=tail->next;
q=q->next;
}
}
while(q!=NULL)
{
ListNode* newNode=new ListNode(q->val);
tail->next=newNode;
tail=tail->next;
q=q->next;
}
while(p!=NULL)
{
ListNode* newNode=new ListNode(p->val);
tail->next=newNode;
tail=tail->next;
p=p->next;
}
return head->next;
}
};
//官方代码,有点玄妙。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
if(list1==NULL) return list2;
else if(list2==NULL) return list1;
else if(list1->val<list2->val)
{
list1->next=mergeTwoLists(list1->next,list2);
return list1;
}
else
{
l2->next=mergeTwoLists(list1,list2->next);
return list2;
}
}
};
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
if((!list1)||(!list2)) return list1?list1:list2;
ListNode head; //新建空头结点,统一所有节点的处理方式 不能用new初始化
ListNode*a=list1,*b=list2,*tail=&head; //指针指向head的地址
while(a&&b)
{
if(a->val<b->val)
{
tail->next=a;
a=a->next;
}
else if(b->val<=a->val) //如果不考虑等于的情况,在a->val和b->val取等时,tail会被赋值为NULL->next,所以会报错。
{
tail->next=b;
b=b->next;
}
tail=tail->next;
}
tail->next=a?a:b;
// while(a!=NULL)
// {
// tail->next=a;
// a=a->next;
// tail=tail->next;
// }
// while(b!=NULL)
// {
// tail->next=b;
// b=b->next;
// tail=tail->next;
// }
return head.next; //指针用->
}
};
ATTENTION
dummy
):链表题常用技巧,可以避免处理空指针的情况,降低代码的复杂性。ListNode
和ListNode*
两种对象类型
ListNode
和ListNode*
两种对象类型的区别。ListNode
是一个节点,实质上是一个有一定结构的结构体,包括了val
部分和next
部分,它通过.
来获取值;ListNode*
是一个指针,实质上只占据了一个指针单位的存储空间,它指向一个ListNode
对象,它通过->
来获取值。ListNode
类型,而不是ListNode*
类型。而如果要新建一个指向链表节点的指针,应当使用ListNode*
类型,并且,为了防止指向非法内存,要通过new
(new只能用来初始化指针)对其初始化或者初始化为NULL
。ListNode*
类型的对象指向一个ListNode
,应当使用&
运算符取节点的地址为其赋值(ListNode* tail=&head;
)。a
和b
两个指针虽然实质上只指向一个链表节点,但是由于链表的特殊性质,a
和b
也相当于指向了一段链表,所以可以直接tail->next=a?a:b;
将剩余的链表连接到tail
的后面。