目录
1.学习目标
1.争取在2021年2月底刷完力扣10%题目。
2.从零开始学习C,C++,python。
3.用多种方法写出解题思路,以及理解代码。
2.意外收获
看到一位前辈总结的一些资料,感觉很有帮助,所以分享给大家。
网址地址:https://github.com/youngyangyang04/leetcode-master
3.学习内容:
3.1 leetcode18
(1)题目
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
(2)思考
方法还是排序+双指针,以后无论多少之数之和都可以用这个方法,两数之和的话哈希法最快,但双指针也是可行的。
首先排序,然后确定i,j的位置,在i,j的右端开始遍历。遍历思路还是一个L,一个R分别指向max(i,j)+1,和最右端,再往根据target-sum的差值往中心取值。因为(i,j)的遍历时间复杂度为O(n^2) ,而(L,R)的遍历时间复杂度为O(n),所以总体的时间复杂度就为O(n^3)。
往深处思考,这类题目都是相同的算法,区别在于对基本值的遍历,假设是n数之和,则外轮有n-2次循环,把除确认(L,R)的所有可能都计算一遍。虽然我觉得可能还有更优秀的算法,但那需要更严谨的数学思考,但对于现阶段的我来说就算了。
3.2 leetcode19
(1)题目
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
(2)思考
换知识点了,这是对链表操作的考察。还是使用双指针吧,设置两个指针first和second,其中first=head,second->next=head,为了方便返回头节点,存储下当前的second。接着使用first遍历n次,second也随着遍历,当first指向第n个节点的时候,second指向n-1个节点,这时只要second->next=second->next->next即可删除,然后再把first节点free掉,返回之前存储的头节点即可。
第一次做的时候看错题目了,以为是删除顺序第n个节点,结果没注意到是倒数第n个。不过还是用双指针法,但是对初始条件的first和second做处理,first指向头节点,second指向first后的第n个节点,如果为NULL,则直接返回错误。然后两个指针一起遍历移动,直到second->next=NULL,则删除first这个节点。
但是要注意的是,不要弄错了first和second的相对位置,否则容易删错位置。
(2/25订正)
此外,这道题我看到一个大佬写的牛逼递归算法,当时还想了下可行性。然后真的是牛逼。
(3)递归算法代码
/**
* 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:
int cur=0;
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(!head) return NULL;
head->next = removeNthFromEnd(head->next,n);
cur++;
if(n==cur) return head->next;
return head;
}
};
3.3 leetcode 20
(1)题目
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
(2)思考
CSP和面试常考题目,方法就是使用栈,对于C语言选手来说,只能写个数组,然后自己编写下入栈,出栈操作。假设检测字符为(或{则入栈,遇到),}则出栈,如果出栈时判断不匹配,则无效字符串。假设,字符串已经为空,但是检测栈中元素不为0,则字符串无效。
3.4 leetcode 21
(1)题目
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
(2)思考
通常作为选择题来进行出题,考察时间复杂度和空间复杂度。我的思路也很简单,因为本身就是升序链表,因此只需要依次遍历即可,每次遍历小的则加入到新的链表当中,然后指针后移,直到链表为空。这种简单算法的时间复杂度和空间复杂度均为O(m+n),m,n分别是两个链表的长度。
3.5 leetcode 22
(1)题目
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
(2)思考
感觉思路还是很清晰,但是代码动手起来有点不知所措,考点应该时回溯法。根据我的经验,C语言用递归很容易奔溃,只要层数大起来,就会栈溢出。
首先输入只有"(“和”)",我们按从左到右的顺序输入,因为只有两个选择,所以脑海里可以很容易的想到二叉树的图形。
那么我们简单以二叉树为例,左子树表示输入左括号,右子树输入右括号。且制定一个规则来结束不规则的括号组合,当右括号数量大于左括号时停止进行剪枝;另一种情况就是当左括号数量大于n时停止剪枝。
回溯法也是重点考察的地方,还要注意和递归的区别之处,我感觉这也是我之后学习的地方之一。
这是我看到的一个最容易懂的代码,很有借鉴意义。
class Solution(object):
def generateParenthesis(self, n):
"""
:type n: int
:rtype: List[str]
"""
res = []
self.dfs(res, n, n, '')
return res
def dfs(self, res, left, right, path):
if left == 0 and right == 0:
res.append(path)
return
if left > 0:
self.dfs(res, left - 1, right, path + '(')
if left < right:
self.dfs(res, left, right - 1, path + ')')
3.6 leetcode 23
(1)题目
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
(2)官方解答
照搬下官方的思路,因为这道题我也曾在面试的时候被问及过,感觉十分有纪念价值。
在解决「合并K个排序链表」这个问题之前,我们先来看一个更简单的问题:如何合并两个有序链表?假设链表 aa 和 bb 的长度都是 nn,如何在 O(n)O(n) 的时间代价以及 O(1)O(1) 的空间代价完成合并? 这个问题在面试中常常出现,为了达到空间代价是 O(1)O(1),我们的宗旨是「原地调整链表元素的 next 指针完成合并」。
先我们需要一个变量 head 来保存合并之后链表的头部,你可以把 head 设置为一个虚拟的头(也就是 head 的 val 属性不保存任何值),这是为了方便代码的书写,在整个链表合并完之后,返回它的下一位置即可。
我们需要一个指针 tail 来记录下一个插入位置的前一个位置,以及两个指针 aPtr 和 bPtr 来记录 aa 和 bb 未合并部分的第一位。注意这里的描述,tail 不是下一个插入的位置,aPtr 和 bPtr 所指向的元素处于「待合并」的状态,也就是说它们还没有合并入最终的链表。 当然你也可以给他们赋予其他的定义,但是定义不同实现就会不同。
当 aPtr 和 bPtr 都不为空的时候,取 val 熟悉较小的合并;如果 aPtr 为空,则把整个 bPtr 以及后面的元素全部合并;bPtr 为空时同理。
在合并的时候,应该先调整 tail 的 next 属性,再后移 tail 和 *Ptr(aPtr 或者 bPtr)。那么这里 tail 和 *Ptr 是否存在先后顺序呢?它们谁先动谁后动都是一样的,不会改变任何元素的 next 指针。
4.感想
之前做题没有方向感觉收效不大,今天找到大佬的学习总结,感觉可以借鉴下学习顺序。另外,因为最近还要看相关深度学习和面试技巧,打算之后的力扣学习以专题为主。