Leetcode刷题日记:16-20题篇
简介
这个系列将是Leetcode的刷题记录贴,按照题库顺序,每五题为一贴,主要目的做自己的记录、总结之用,但若有同学或同好看到此贴,并有所帮助,也算额外之喜,共勉!
题目:
16. 最接近的三数之和
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
示例:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
题解:
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
int len=nums.size(),min_distance=INT_MAX,ans;
sort(nums.begin(),nums.end());
for(int i=0;i<len;i++){
int head=i+1,tail=len-1;
while(head<tail){
int sum=nums[i]+nums[head]+nums[tail];
int tmp=sum-target;
//双指针的核心代码
if(tmp>0)
tail--;
if(tmp<0)
head++;
if(tmp==0)
return sum;
if(abs(tmp)<abs(min_distance)) {
min_distance=tmp;
ans=sum;
}
}
}
return ans;
}
};
思路:双指针的解法很多题解都已经讲过了,笔者在这里就着重说一下对于双指针这块的核心代码的理解:
//这是双指针的写法
while(head<tail){
if(tmp<0)
head++;
if(tmp>0)
tail--;
if(tmp==0)
//...
}
//这是普通写法
for(int i=0;i<m;i++)
for(int j=0;j<n;j++){
if(tmp==0)
//...
}
看出区别了吗?
双指针将一个两层循环转化成了一层循环,时间复杂度也从n^2变成了n,那么什么时候会想到要用双指针的方法呢?又如何实现将普通写法到双指针写法的转换呢?
一般来讲,当遇到需要对一个数组进行重复遍历时,可以想到使用双指针法
说是指针,其实是设置两个int变量分别赋值为数组的首和尾,在一层循环内,每次只对其中一个指针进行移动,找到判断指针移动的条件是双指针的核心,在该题中,是一个求距离最近的问题,那么当tmp=sum-target>0时,显然说明和的值比target大了,应该让其更小,故对于排序后的数组将尾指针左移,显然,sum和target的大小便是此题中判断指针移动的条件
17. 电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]
题解:
class Solution {
public:
vector<string> letterCombinations(string digits) {
if(digits.size()==0) return {};
unordered_map<char,string> map{
{'2',"abc"},
{'3',"def"},
{'4',"ghi"},
{'5',"jkl"},
{'6',"mno"},
{'7',"pqrs"},
{'8',"tuv"},
{'9',"wxyz"},
};
vector<string> ans;
string tmpans;
f(0,digits,tmpans,ans,map);
return ans;
}
//注意参数必须加&
void f(int layer,string& digits,string& tmpans,vector<string>& ans,unordered_map<char,string>& map){
if(layer==digits.size()){
ans.push_back(tmpans);
}
else{
for(char tmp:map[digits[layer]]){
tmpans.push_back(tmp);
f(layer+1,digits,tmpans,ans,map);
//回溯,将最后一位弹出
tmpans.pop_back();
}
}
}
};
思路:当我们在题目中看到找出所有组合的字眼时,可以考虑使用回溯法
可以将回溯法想象成树的遍历:
可以看到,对树中任意两个节点的操作是完全相同的,都是取其下一层的三个字符分别连接在后面,于是我们可以用递归的思想:
//对第i层某节点a的操作
void f(第i层,其他参数){
节点a.push_back(节点b);
//对第i+1层节点b的操作
f(第i+1层,其他参数);
}
除去递归的思想,在使用回溯法时,还需要注意每次回溯都要将回溯掉的值“弹出”,即需要添上一行:
第i层节点.pop_back();
如何判断何时进行回溯呢?本题中,由于需要找出所有组合,故在到达最后一层,也就是layer==digits.size()时,进行回溯:
if(layer==digits.size())
ans.push_back(底层某结点);
//此时递归进行到最底部,结束,开始回退:
(底层-1)层节点.pop_back();
//将底层的第二个节点连接到对应的(底层-1)层节点的后面:
对应的(底层-1)层节点.push_back(底层的第二个节点);
易错点:
1.注意!使用诸如string等类型为函数形参时,必须加&。不加引用的话,str则被复制一份,函数中对str的操作实质上是对其复制品的操作,所以即使在函数中修改了str,调用层的原str并不会被改变。
2.回溯时未进行弹出操作
18. 四数之和
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] :
- 0 <= a, b, c, d < n
- a、b、c 和 d 互不相同
- nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
示例:
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
题解:
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> ans;
sort(nums.begin(),nums.end());
int len=nums.size();
for(int i=0;i<len-3;i++)
for(int j=i+1;j<len-2;j++){
int head=j+1,tail=len-1;
while(head<tail){
//注意这里的和可能会溢出int的范围,必须在左右侧都添加longlong
long long sum=(long long)nums[i]+nums[j]+nums[head]+nums[tail];
if(sum<target)
head++;
if(sum>target)
tail--;
if(sum==target){
bool ifrepeat=false;
for(int k=0;k<ans.size();k++){
if(ans[k][0]==nums[i] && ans[k][1]==nums[j] && ans[k][2]==nums[head] && ans[k][3]==nums[tail])
ifrepeat=true;
}
if(ifrepeat==true)
head++;
else{
//注意这里的括号要用{}
ans.push_back( {nums[i],nums[j],nums[head],nums[tail]} );
head++;
}
}
}
}
return ans;
}
};
思路:
使用双指针法的思路不再赘述,这里写两个易错点:
1.求和那一步,笔者一开始左边的类型使用int sum,运行报错,提示integer overflow,超出整数范围,于是改写成long long sum,依然报错,这是因为等号左边的类型不会影响等号右边,右边由两个int相加,得到的类型还是int,但是数值却超出了int的范围,故需要将右边的至少任一个数的类型改为long long
2.在对一个vector<vector>.pusu_back时,使用{}而非[]
19. 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
题解:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* h=head,*t=head;
for(int i=0;i<n;i++){
t=t->next;
}
if(t == nullptr) return head->next;
while(t->next != nullptr){
h=h->next;
t=t->next;
}
ListNode* tmp=h->next;
h->next=tmp->next;
return head;
}
};
思路:
快慢指针的解法,在这里讲一个易错点,就是容易对空指针进行操作,从而报错:
for(int i=0;i<n;i++){
t=t->next;
}
while(t->next != nullptr){}
这里,当输入数组的长度和倒数的n相等时,t会在for循环的最后被赋值为末尾的空指针,再进入while循环时对空指针求->next,会报错,所以需要在中间加一句:
if(t == nullptr) return head->next;
20. 有效的括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
示例:
输入:s = “()[]{}”
输出:true
题解:
class Solution {
public:
bool isValid(string s) {
stack<char> sta;
for(int i=0;i<s.size();i++){
if(!sta.empty() && ( (s[i]==')'&&sta.top()=='(') || (s[i]==']'&&sta.top()=='[') || (s[i]=='}'&&sta.top()=='{') ) )
sta.pop();
else sta.push(s[i]);
}
return sta.empty()?true:false;
}
};
思路:
使用栈,正好满足题目要求,这里提一个易错点:
stack.top()函数不能对空栈使用,否则会报错,故第一个if语句里先写了!sta.empty()
注
题目来源:力扣(LeetCode)
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
博客著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。