Leetcode刷题记录
记录我的leetcode刷题过程。始于2020年7月21日。
1. 两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
哈希表的理解:
- 哈希表将输入(啥都能行,字符串、数字、对象等等)通过哈希算法(散列算法,是一种压缩映射,输出空间小于输入空间,所以有碰撞)映射成一个数字,记为k;
- 计算存储该输入对象的地址:start + k * size,这仅需要O(1);
- 对于一个查找输入,我们同样先把它哈希一下,得到对应的 k ′ k' k′,然后计算start + k ′ k' k′ * size,有则有无则无,同样只需要O(1)。
python实现哈希,用字典即可。事实上字典dict的本身实现就是用的哈希算法。
对于list,可以使用for index, num in enumerate(list)
对于dict,可以使用dict.get(num)
获得索引
对于list,有函数list.count(num)
返回list中值为num的个数,有函数list.index(num, start_pos)
返回从start_pos开始的索引,还可用if a in list
来判断是否存在。
注意这种边存边查找的方式!存哈希表的过程中就可以在已存储的哈希表中查找,如果查到了就停止,这肯定比全部存完了再查找要好。
注意,上面这段话其实对应了一个对称性的道理。通常我在写暴力查找的时候是这样的写的:
for i in range(len):
for j in range(i+1, len):
……
默认从i的后面开始查。事实上,对称来说,我也可以只查i的前面:
for i in range(len):
for j in range(i):
……
另外注意无解时要返回一个空[]
2. 两数相加
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
注意,python中直接 11 / 10就可以得到 1,不用%然后再相减了。
题解中的一个启发:同时遍历两个链表的时候可以用双指针p和q一起,我总是习惯单指针。当一个到头之后,可以设置p=0或者q=0即可,不用再分情况讨论谁到头了然后怎么加之类的,这样写的统一。
我原来的其实是错的,我直接上来p = l1
然后最后返回l1,这里会有一个问题,就是当l1为空链表[]
时,就挂了。所以应该按照题解的方法上来写一个假头指向l1,然后最后返回假头的next。
拓展:如果链表正序的话?
解:使用栈来将链表翻转:
stack = []
while l1 != None:
stack.append(l1.val)
l1 = l1.next
然后stack就变成了一个逆序的存储,然后对stack进行之前的操作即可。
这里学到了,栈在python中就是list实现,append()
和pop()
函数就是进和出。
拓展:如果不让翻转链表咋办?
解:网上说可以直接先不进位的加,比如4+8 = 12,就先把12放在val位置,然后while循环,如果有大于等于10的,就模10然后前面加一。
3. 无重复字符的最长子串
定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
我的解其实是更优的。
答案的解答是每次左指针向右移动一个位置,事实上我们可以记录出当右边出现重复字符时,是和当前子串中哪个重复了,然后直接从该位置的下一个开始就行了,不用每次只向右一个。这样更快
学到了如何找和哪个位置的重复。用字典当哈希:
occ = {}
occ.get()获得索引
in occ 获得bool是否在
另外,用index, value = enumerate(list)来同时获得下标和值
5. 最长回文子串
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
感觉我一开始写的也是时间复杂度
O
(
n
2
)
O(n^2)
O(n2),空间复杂度
O
(
n
)
O(n)
O(n),但是不知为何过不去……
我写的是对于每个s[i],从末尾倒着找和他一样的,然后一旦发现一样,就逐个往前比较,有不同则挂,比较完了则ok
奥对我这个是
O
(
n
3
)
O(n^3)
O(n3),因为“对于每个”有个n,从后往前有个n,然后一旦发现之后又是n
第一次遇到动态规划,在这个题中需要注意:
- 学到了
dp = [[False] * len(s) for _ in range(len(s))]
- 注意此题的动态规划是根据“子串长度”,所以外层循环一定是字串长度而不是起始位置,否则的话在递推的时候,递推式的右边还未被赋值,导致错误的结果。也就是说,此题的过程是:从1开始的长度为1的子串、从2开始的长度为1的子串、从3开始的长度为1的子串……从1开始的长度为2的子串……,而不是从1开始的长度为1的子串、从1开始的长度为2的子串,因为“从1开始的长度为2的子串”需要“从2开始的长度为1的子串”作为递推右侧,也就是你上一个“递推”的变量(字串长度)
第二种解答不错,expand方法,可以看看
6. Z 字形变换
将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下:
L C I R
E T O E S I I G
E D H N
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"LCIRETOESIIGEDHN"。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
示例 1:
输入: s = "LEETCODEISHIRING", numRows = 3
输出: "LCIRETOESIIGEDHN"
示例 2:
输入: s = "LEETCODEISHIRING", numRows = 4
输出: "LDREOEIIECIHNTSG"
解释:
L D R
E O E I I
E C I H N
T S G
我一开始做对了,注意那个+1的时候的写法,一定要想清楚物理意义再写
题解的第一种写法很有趣,开Numrows个列表,然后遍历s,把每个字符放到对应列表中,然后串起来就行了,属实牛逼。
7. 整数反转
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
示例 1:
输入: 123
输出: 321
示例 2:
输入: -123
输出: -321
示例 3:
输入: 120
输出: 21
注意:
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−$2^{31}$, $2^{31}$ − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
很简单,但是注意最后的溢出要求。题目的意思是说要求用32位的空间来翻转32位,不能超,所以翻转完成之后再判断结果的方法是不对的,因为已经超了。
所以应该像官方题解那样,每一次准备乘10的时候判断一下当前数字和 I N T M A X 10 \frac{INTMAX}{10} 10INTMAX谁大,如果当前大则乘10之后肯定溢出,就直接return 0就行了。
有个评论说得好,官方解答中的7、-8的意思是说,2147483647已经知道了,所以最后一位肯定小于等于7。如果当前数字等于 I N T M A X 10 \frac{INTMAX}{10} 10INTMAX的时候需要比较末位是否小于等于7。事实上不用,因为被翻转的数字一定小于等于2147483647,所以最后一位一定是1或者2,所以不用比,也就是说出现当前数字等于 I N T M A X 10 \frac{INTMAX}{10} 10INTMAX的时候肯定不会溢出。
8. 字符串转换整数 (atoi)
请你来实现一个 atoi 函数,使其能将字符串转换成整数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。接下来的转化规则如下:
如果第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字字符组合起来,形成一个有符号整数。
假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成一个整数。
该字符串在有效的整数部分之后也可能会存在多余的字符,那么这些字符可以被忽略,它们对函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换,即无法进行有效转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0 。
提示:
本题中的空白字符只包括空格字符 ' ' 。
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。
示例 1:
输入: "42"
输出: 42
示例 2:
输入: " -42"
输出: -42
解释: 第一个非空白字符为 '-', 它是一个负号。
我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。
示例 3:
输入: "4193 with words"
输出: 4193
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。
示例 4:
输入: "words and 987"
输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。
因此无法执行有效的转换。
示例 5:
输入: "-91283472332"
输出: -2147483648
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。
因此返回 INT_MIN (−231) 。
这题第一次学了有限状态机的使用。有限状态机用的时候的要点是一定一定要自己现在纸上画清楚各种转移状态啥的。
然后就是遇到intmax之类的问题,直接把最终输出比较一下intmax就行了,不用除以10之类的。
一行写c++里面的?:这样写:sth if sth else sth
学到了判断字符串中是否只有数字:str.isdigit()
9. 回文数
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
示例 1:
输入: 121
输出: true
示例 2:
输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。
进阶:
你能不将整数转为字符串来解决这个问题吗?
学到了将数字转化为字符串:str()即可,字符串转数字:int()即可
特例注意,末位为0的大于零的数是不可能的,因为0翻转过去肯定不行
记住:取末位:%10
,去掉末位,x // 10
这个高级解法不错,翻转后一半:每次取出末位,乘10+末位,然后比较结果和剩下的前一半是否相等即可。
13. 罗马数字转整数
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。
示例 1:
输入: "III"
输出: 3
示例 2:
输入: "IV"
输出: 4
示例 3:
输入: "IX"
输出: 9
示例 4:
输入: "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.
示例 5:
输入: "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
学到了str的replace函数,可以把一个字符串替换为另一个:str.replace(astr, bstr)
,注意它并不更改原字符串,所以要s=s.replace()
才能真正替换。
那个两行的解法不错,但是无通用知识,学到了为了避免遍历到-1,可以max(sth, 0)来做。
11. 盛最多水的容器
给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器,且 n 的值至少为 2。
示例:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
据说是经典面试题,只想到了暴力的 O ( N 2 ) O(N^2) O(N2)解法
题解里使用双指针:
- 两个指针所指的两条表示当前正在考虑的两条,可以容易地计算出由这两条所组成的水池的容量。
- 一开始是最左和最右!!!!!!!!!!
- 我们发现一个规律,往内移动较大的那个指针,一定一定会不增。因为距离减小了,高度不增。
- 所以如果要增,只能往里移动较小的那个指针
- 然后比较、重复即可
14. 最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。
示例 1:
输入: ["flower","flow","flight"]
输出: "fl"
示例 2:
输入: ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀。
说明:
所有输入只包含小写字母 a-z 。
一次过,ok!
15. 三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
这题启发:
从一列数中做某些操作的时候,一要想到排序,二要想到双指针左右夹逼。
双指针带来的好处:把暴力的双重循环变成了
O
(
N
)
O(N)
O(N),相当于左右总共一起动了
N
N
N
后来想了想,其实有一点是很重要很精妙的算法,就是每一层的前后两次的数字不能一样,否则重复!!!这里需要好好理解,理解的关键就是,前一次的循环一定一定会把相同的东西找出来,所以后一次如果该层还一样,则一定也会重复找到这个。
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
和上一题一样,排序+双指针即可
19. 删除链表的倒数第N个节点
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
双指针即可。
答案有个哑结点的用法,放在最前面,用来排除一些特殊情况,如删除头、长度为一等。
22. 括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例:
输入:n = 3
输出:[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
回溯!
虽然一遍写对了,但是注意回溯的写法:
- 写一个单步的函数
- 单步的函数内部直接进行下一步的递归,也即对传入的参数加减一然后进行下一步
- 如果有多种情况,一个情况下搞完之后要清空这次的,然后到下一种情况。(答案的pop函数)
24. 两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.
很简单,一遍过。就是提醒一下迭代都想一下递归,但是递归不一定好,它的空间复杂度反而高,因为开了函数空间,一般O(N),而迭代一般就是O(1)
27. 移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
确实简单,注意答案中的两种双指针写法:
- 快指针和慢指针
- 左右指针
二者正好处理的是相反的正反数据两种东西。第一种检测好数据并往前挪,第二种检测坏数据并替换
29. 两数相除
给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。
返回被除数 dividend 除以除数 divisor 得到的商。
整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2
示例 1:
输入: dividend = 10, divisor = 3
输出: 3
解释: 10/3 = truncate(3.33333..) = truncate(3) = 3
示例 2:
输入: dividend = 7, divisor = -3
输出: -2
解释: 7/-3 = truncate(-2.33333..) = -2
提示:
被除数和除数均为 32 位有符号整数。
除数不为 0。
假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−231, 231 − 1]。本题中,如果除法结果溢出,则返回 231 − 1。
感觉咋做都行,这里试了一下递归,ok了
31. 下一个排列
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
这题真是。。。我竟然都想到了解法,但是代码功底太辣鸡,还有就是没注意到后半部分已经是逆序了,要利用
学到了:
- 遍历的时候如果有需要在内部比较的对象,不需要设置一个
smt = None
之类的,直接初始值少来一个,如倒序从n-2开始,每次比较i+1和i即可 - 逆序一个数组的写法:
i = len(nums)
j = 0
while i > j:
swap(nums[i], nums[j])
i -= 1
j += 1
- 冒泡排序:
def bubbleSort(nums):
n = len(nums)
for i in range(n):
for j in range(n-i-1):
if nums[j] > nums[j+1]:
nums[j], nums[j+1] = nums[j+1], nums[j]
33. 搜索旋转排序数组
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
挺简单,学到了二分查找几个细节:
head = 0
last = len(nums) - 1
mid = (head +last) // 2
while head <= last:
if target == nums[mid]:
return mid
elif ……:
head = mid + 1
elif ……:
last = mid - 1
mid = (head +last) // 2
return -1
注意加减一和<=
38. 外观数列
给定一个正整数 n(1 ≤ n ≤ 30),输出外观数列的第 n 项。
注意:整数序列中的每一项将表示为一个字符串。
「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。前五项如下:
1. 1
2. 11
3. 21
4. 1211
5. 111221
第一项是数字 1
描述前一项,这个数是 1 即 “一个 1 ”,记作 11
描述前一项,这个数是 11 即 “两个 1 ” ,记作 21
描述前一项,这个数是 21 即 “一个 2 一个 1 ” ,记作 1211
描述前一项,这个数是 1211 即 “一个 1 一个 2 两个 1 ” ,记作 111221
简单,确实简单。