这篇博客记录第4天刷题的思路。
16. 最接近的三数之和
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
示例:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
提示:
3 <= nums.length <= 10^3
-10^3 <= nums[i] <= 10^3
-10^4 <= target <= 10^4
分析:这道题和15. 三数之和非常接近,一个是和刚好等于target的三元数,一个是最接近target的三数之和。大致思路依然是先排序,然后将三重暴力搜索转换为双指针问题。注意:这里我是对三数之和与target的绝对值之差(即残差res)进行判断,看内部每次指针移动时残差res是否减小。因此要恢复原有的三数之和,需要额外定义一个Ifbig变量,记录残差res减小时三数之和与target相比更大还是更小。也可以直接对残差res判断后将本轮的三数之和赋值变量,最后输出遍历完成后的结果。
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
int sum = 0;
if (nums.size() < 3) {
for (int i = 0; i < nums.size(); i++) {
sum += nums.at(i);
}
return sum;
}
sort(nums.begin(), nums.end());
int res = 1e4, Ifbig = 1;
bool go_on = true;
for (int i = 0; i < nums.size() - 2; i++) {
int left = i + 1, right = nums.size() - 1;
while (left < right) {
sum = nums[i] + nums[left] + nums[right];
res = min(res, abs(target - sum));
if (abs(target - sum) < res && sum < target)
Ifbig = -1;
if (abs(target - sum) < res && sum > target)
Ifbig = 1;
if (sum < target) left++;
else if (sum > target) right--;
else {
go_on = false;
break;
}
}
if (!go_on) break;
}
return (target + Ifbig * res);
}
};
20.有效的括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: “()”
输出: true
示例 2:
输入: “()[]{}”
输出: true
示例 3:
输入: “(]”
输出: false
示例 4:
输入: “([)]”
输出: false
示例 5:
输入: “{[]}”
输出: true
分析:这道题我是完全照搬评论区大佬的思路和代码的。。。用栈的FIFO特性对字符串元素遍历:若当前为左括号元素则入栈,若当前为右括号元素,则正确格式下必定与栈顶的左括号元素对应,否则直接判错。正确格式下,经历了上面的比较后栈顶元素应出栈,不影响后面的入栈与比较。这里使用unordered map作为左右括号对照表,其中key为左括号类型,比较巧妙,详细C++代码如下:
class Solution {
public:
bool isValid(string s) {
while (s != "") {
unordered_map<char, char> m = {
{
'[', ']'}, {
'{', '}'}, {
'(', ')'}}; //key值为左括号类型
if (m.find(s[0]) == m.end() || s.size() == 1) //s开头元素错误或仅有1个元素
return false;
stack<char> left; //C++ stack类,存左括号
for (int i = 0; i < s.size(); i++) {
if(m.find(s[i]) != m.end()) left.push(s[i]); //左括号元素,入栈
else {
//若 s[i] 非左括号,则它之前必有左括号在栈中且栈顶与它对应,若栈空直接判错
if(!left.empty()) {
//栈非空,正确格式下栈顶元素必与此时右边类型的括号 s[i] 对应,否则直接判错
if(m[left.top()] != s[i])
return false;
else
left.pop(); //正确格式,则栈顶左括号元素出栈
}
else
return false;
}
}
//一趟遍历结束,若s 格式正确,则经历了上面 比较后栈顶元素出栈 的操作后,栈应空,否则直接判错
if(left.empty())
break;
else
return false;
}
return true;
}
};
21. 合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = [] 输出:[]
示例 3:
输入:l1 = [], l2 = [0] 输出:[0]
提示:
两个链表的节点数目范围是 [0, 50] -100 <= Node.val <= 100 l1 和 l2 均按 非递减顺序 排列
这道题和02.两数之和一样,都是根据输入的两链表进行判断求新链表。我的方法太中规中规,对每次两链表节点的value进行大小排序,然后新建链表节点。代码如下:
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode head(0), *p = &head;
while (l1 || l2) {
int lvalmin = 0, lvalmax = 0;
bool valmax = true;
if (l1 != nullptr && l2 != nullptr && l1->val != l2->val) {
lvalmin = min(l1->val, l2->val);
lvalmax = max(l1->val, l2->val);
}
else if (l1 != nullptr && l2 != nullptr && l1->val == l2->val) {
lvalmin = l1->val; lvalmax = l2->val;
}
else if (l1 != nullptr && l2 == nullptr) {
lvalmin = l1->val;
valmax = false;
}
else if (l1 == nullptr && l2 != nullptr){
lvalmin = l2->val;
valmax = false;
}
ListNode* next = new ListNode(lvalmin);
next->val = lvalmin;
p->next = next;
p = p->next;
if(valmax) {
ListNode* next = new ListNode(lvalmax);
next->val = lvalmax;
p->next = next;
p = p->next;
}
l1 = l1 ? l1->next : nullptr;
l2 = l2 ? l2->next : nullptr;
}
return head.next;
}
};
但是当某个链表节点value比较大的情况,该方法是无法通过的,例如输入为:
[5]
[1,2,4]
该方法输出:
[1,5,2,4]
看到评论区一个解法,非常优雅,利用到了输入链表有序的特点,在对两输入链表节点value比较大小并交换节点排序的过程中,只移动始终指向较小节点的指针到下一个。测试如下:
输入:
[5,7]
[1,2,4,6]
输出:
[1,2,4,5,6,7]
该方法有效!
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode head(0), *p = &head;
//链表有序,只移动指向较小节点的指针
while(l1 && l2) {
if(l1->val > l2->val) swap(l1, l2); //地址交换
p->next = l1;
l1 = l1->next;
p = p->next;
}
p->next = l1 ? l1 : l2;
return head.next;
}
};