引言
有人相爱,有人夜里开车看海,有人LeetCode第一题就做不出来。没错,那个人就是我,从今天开始,我将开启算法专栏,记录自己的刷题日常,本人纯算法小白,希望大家多多包涵。
1、两数之和
先附上题目链接1. 两数之和 - 力扣(LeetCode)。我将通过分析题目,讲解思路,代码实操三个部分来讲解。
首先,分析题目,题目要求我们在一个数组中找两个相加能等于目标值的两个元素的下标,十分简单,数值范围在10^-9到10^9之间,直接用int就可以满足题目要求。
接下来,来解析思路,先讲讲我的错误思路吧,刚开始的时候我读题不仔细,没看见取值范围是包括负数的,于是我的一开始的思路是:同时取数组的头和尾元素,记作prev和last,因为元素不可能大于target值,所以如果prev或last位置的值大于target就移动,如果都小于,就只把prev向前移动,这样迟早能找到两数之和等于目标值,而且只用遍历一遍数组。最开始确实和我想的一样,测试用例也过了,但是但我自信提交时,发现取值范围是包括负数的,我的思路就行不通了。接下来来讲解一下正确思路。
思路一:暴力枚举,因为题目本身没有要求时间复杂度,我们可以简单的将每一个元素与它之后的每一个元素都相加一遍,直到找到满足题目需求的两个下标为止。代码出自力扣官方题解。
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 {};
}
};
思路二:进阶思路,哈希表。求两数之和等于目标值,换个说法就是我们需要找到一个等于target - nums[i] 的值,所以我们只需要遍历一遍数组,将每一个元素都进行一次target - nums[i]的计算,并在哈希表内查找是否有满足条件的元素,有就返回两元素的下标;如果没找到,就将该元素的值和下标插入哈希表中,这样就能避免元素与自己匹配。代码出自力扣官方题解。
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 {};
}
};
2、两数相加
分析题目,这个题目有两个需要注意的地方,第一个是链表所表示的数字是逆序存储的,意思就是当我们一次获取它的数字拼成一个整数后,还需要进行一次逆序才能获取正确的数字,并进行相加。还有一个要注意的点是,这个链表的长度范围为0 ~ 100,这意味着这两个数最大可能会达到100位数,远远大于了longlong能存储的数值范围,所以本质上它还涉及到大数相加的问题。
讲解思路,本题还是先来讲解一下我的错误思路。我一开始写这道题时,并没有注意到这道题的取值范围,于是很理所当然的以为,只要将列表中的每一位数提出来,并且逆置就可以得到两个完整的数,再进行相加,将相加后的值每一位一次尾插进新链表中就行了。至于我怎么解决逆序的问题,我是这么想的:定义一个time来确定此时为第n位数,然后进行一次for循环,将当前的数字乘等于10,循环n - 1次,然后再加等到总数中,然后在循环第二个链表前将time赋值为0,重复上述过程。输出就简单了,i1和i2相加得到sum,然后每次对sum取余,尾插进新链表,最后返回新链表的头就行了,以下是我当时的完整代码。
/**
* 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 i1 = 0;
int i2 = 0;
int time = 0;
while(l1)
{
int tmp = l1->val;
for (int i = 0; i < time; i++)
{
tmp *= 10;
}
time++;
i1 += tmp;
l1 = l1->next;
}
time = 0;
while(l2)
{
int tmp = l2->val;
for (int i = 0; i < time; i++)
{
tmp *= 10;
}
time++;
i2 += tmp;
l2 = l2->next;
}
int sum = i1 + i2;
if (!sum)
{
ListNode* head = new ListNode(0);
return head;
}
ListNode* head = nullptr;
ListNode* tail = nullptr;
while (sum)
{
ListNode* cur = new ListNode(sum % 10);
if (head == nullptr)
{
head = tail = cur;
}
else
{
tail->next = new ListNode(sum % 10);
tail = tail->next;
}
sum /= 10;
}
return head;
}
};
这套思路在大部分情况下确实是可行的,通过了绝大部分测试用例但当我把它提交时,只剩最后四个用例时报了错,我才发现这道题的数值范围远远超过了整形能表示的最大值,于是我只能放弃自己的写法。
正确写法:当l1和l2任意一个链表不为空时进入循环,获取两个链表当前节点的数据n1和n2,若当前节点为空就记作0,设计一个carry值,初始为零,用于获取相加后进位的值,然后将n1、n2和carry值三个值加起来,得到sum值,将sum对10取余,将结果尾插进新链表,然后对sum整除,得到是否有进位的信息,存入carry内,如果l1或l2不为空,则走到下一个节点。最后退出循环时,如果carry值不为0,说明还需要进一位,再将carry值尾插,最后返回头节点就行了。
不得不说,官方的解题思路确实很巧妙,不是对整个数进行相加,而是对每一位一次相加并记录是否进位,这样就避免了大数的问题。其实本题思路上并不算很难,只要注意到数值范围的问题就能避开陷阱。下附完整代码:
/**
* 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* head = nullptr;
ListNode* tail = nullptr;
int carry = 0;
while (l1 || l2)
{
int i1 = l1 ? l1->val : 0;//如果l1不为空就为val,否则为0
int i2 = l2 ? l2->val : 0;
int sum = i1 + i2 + 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;
}
};
结语
今天的算法题分享到此结束,对于今天这两道基础题,我也收益良多,今后我还会继续坚持刷题,并分享刷题经历,希望我们能一起进步。

3388

被折叠的 条评论
为什么被折叠?



