Leetcode刷题记录(一)

Leetcode刷题记录

  • 1. 两数之和
  • 2. 两数相加
  • 3. 无重复字符的最长子串
  • 5. 最长回文子串
  • 6. Z 字形变换
  • 7. 整数反转
  • 8. 字符串转换整数 (atoi)
  • 9. 回文数
  • 13. 罗马数字转整数
  • 11. 盛最多水的容器

记录我的leetcode刷题过程。始于2020年7月21日。

1. 两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

 

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

哈希表的理解:

  1. 哈希表将输入(啥都能行,字符串、数字、对象等等)通过哈希算法(散列算法,是一种压缩映射,输出空间小于输入空间,所以有碰撞)映射成一个数字,记为k;
  2. 计算存储该输入对象的地址:start + k * size,这仅需要O(1);
  3. 对于一个查找输入,我们同样先把它哈希一下,得到对应的 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

第一次遇到动态规划,在这个题中需要注意:

  1. 学到了dp = [[False] * len(s) for _ in range(len(s))]
  2. 注意此题的动态规划是根据“子串长度”,所以外层循环一定是字串长度而不是起始位置,否则的话在递推的时候,递推式的右边还未被赋值,导致错误的结果。也就是说,此题的过程是:从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)解法

题解里使用双指针:

  • 两个指针所指的两条表示当前正在考虑的两条,可以容易地计算出由这两条所组成的水池的容量。
  • 一开始是最左和最右!!!!!!!!!!
  • 我们发现一个规律,往内移动较大的那个指针,一定一定会不增。因为距离减小了,高度不增。
  • 所以如果要增,只能往里移动较小的那个指针
  • 然后比较、重复即可

你可能感兴趣的:(leetcode)