[刷题] Leetcode算法 (2020-2-25)
1.两数之和
题目:
给定一个整数数组 nums
和一个目标值 target
,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9 因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]
代码:
class Solution: def twoSum(self, nums, target): # 使用字典来保存 数字:索引 hashmap = {} # 创建 数字:索引 键值对 for ind, num in enumerate(nums): hashmap[num] = ind # 循环,从中获取target-num对应的index,但是index不能和i相等,因为一个数不能使用两次 for i, num in enumerate(nums): j = hashmap.get(target - num) if j is not None and i != j: return [i, j] if __name__ == '__main__': s = Solution() print(s.twoSum([3, 3, 4, 3], 6)) # 返回[0,3],而不是[0,1]
总结:
# 该题可以使用暴力法来解答,即双层循环一个一个试,直到找到2个数加起来等于target,但是效率很低下 # 使用enumerate将数组转化为字典,利用字典的get方法,可以很快判断字典中是否存在想要的值,大大提高运行效率,但是会浪费内存空间
2.整数反转
题目:
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
示例:
输入123,输出321 输入-123,输出-321 输入120,输出20
代码1:
class Solution: def reverse(self, x: int): # 使用flag记录是正还是负,正为True,负为False flag = True # 先将x转换为正数,用flag记录符号 if x < 0: flag = False x = -x # 数字转换为字符串 num_str = str(x) # 将字符串翻转 new_str = "" for c in num_str: new_str = c + new_str # 将翻转后的字符串转回整数 new_num = int(new_str) # 恢复符号 if not flag: new_num = -new_num # 判断边界 if new_num > 2 ** 31 - 1 or new_num < -2 ** 31: return 0 return new_num if __name__ == '__main__': s = Solution() s.reverse(-321)
这是自己第一版的low代码,字符串翻转用了for循环。。
总结:
# 使用重新拼接字符串的形式进行字符串翻转,浪费了内存空间,比较low # 使用了简单if else,比较low。完全可以使用三目来代替
代码2:
class Solution: def reverse(self, x: int) -> int: # 如果是个位数,直接返回 if -10 < x < 10: return x # 整数转换为字符串 str_x = str(x) # 判断第一个字符是否为'-' if str_x[0] != "-": # 如果不是负数,则直接翻转字符串 str_x = str_x[::-1] # 转回数字 x = int(str_x) else: # 如果是负数,排除符号进行翻转 str_x = str_x[:0:-1] # 转换为正整数 x = int(str_x) # 加上符号 x = -x # 使用三目运算符 return x if -2147483648 < x < 2147483647 else 0
参考别人的代码,原理也是使用字符串来翻转,但比我自己写的高级,使用切片操作的负步长来翻转。
总结:
# 字符串翻转可以使用 切片操作 负步长来进行,比较简洁,效率高 # 使用三目运算代替简单if else
代码3:
class Solution: def reverse(self, x: int): # y为x的绝对值,res初始化为0 y, res = abs(x), 0 # 则其数值范围为 [−2^31, 2^31 − 1],这里先不管符号,x为正时boundary=2**31-1,x为负时,boundary=2**31 boundry = (1 << 31) - 1 if x > 0 else 1 << 31 # 循环翻转,从最后一位数开始 while y != 0: # res是逐位翻转后的结果,每次乘以十再加上新的一位翻转数,y%10取余得到的是当前最后一位数 res = res * 10 + y % 10 if res > boundry: return 0 # 循环一次,y整除10(即砍掉最后已经翻转的数字) y //= 10 # 恢复符号 return res if x > 0 else -res if __name__ == '__main__': s = Solution() print(s.reverse(-321))
使用逐位翻转的方式,没翻转一位就和边界比较。
总结:
# 1<<31就是2**31 # 使用逐位翻转,直接从一个整数计算出翻转后的整数,运算速度快,节省空间。
3.回文数
题目:
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
示例:
输入: 121 输出: true 输入: -121 输出: false 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。 输入: 10 输出: false 解释: 从右向左读, 为 01 。因此它不是一个回文数。
代码1:
class Solution: def isPalindrome(self, x: int) -> bool: # 如果是负数,则一定不是回文数 if x<0: return False # 转换为字符串 str_num = str(x) # 字符串长度 length = len(str_num) # 砍半 harf = length//2 # 默认为True flag = True # 循环,判断对称位置的字符是否相同 for i in range(harf): # 如果不同,则flag=Flase,并停止循环 if str_num[i] != str_num[length-1-i]: flag = False break # 返回flag return flag
第一个实现的版本,将数字转换为字符串来对比对称位置的字符是否相等来判断。比较low的思路。
代码2:
class Solution: def isPalindrome(self, x: int) -> bool: # 负数直接返回false if x<0: return False # 转换为字符串 str_num = str(x) # 翻转字符串 str_num_re = str_num[::-1] # 比较字符串,返回结果 return str_num == str_num_re
直接翻转字符串比较是否相同。
代码3:
class Solution: def isPalindrome(self, x: int) -> bool: # x小于0,则返回false if x < 0 : return False # 定义数字翻转函数 def reverse(y:int): res = 0 while True: res = res*10 + y%10 if y//10 == 0: break else: y = y//10 return res # 翻转后的数字如果等于原数字,则是回文数 return True if reverse(x)==x else False
将数字翻转,比较翻转后的数字和原数字是否相等来判断是否为回文数。效率也不高。
总结:
# 以上三种代码,原理都很简单,一个是比较对称位置数是否相同,二是比较翻转后的数字或字符串是否相同
4.罗马数字转整数
题目:
罗马数字包含以下七种字符: 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 的范围内。
示例:
输入: "III" 输出: 3 输入: "LVIII" 输出: 58 解释: L = 50, V= 5, III = 3. 输入: "MCMXCIV" 输出: 1994 解释: M = 1000, CM = 900, XC = 90, IV = 4.
代码1:
class Solution: def romanToInt(self, s: str) -> int: # 所有罗马数字 roma_num = {'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000} # 特殊组合 special_num = {'IV':4,'IX':9,'XL':40,'XC':90,'CD':400,'CM':900} # res用于记录总数 res = 0 # i为index i = 0 while i<len(s): # 当为最后一个字符时,不会再出现特殊组合,直接加其值即可 if i == len(s)-1: res += roma_num.get(s[-1]) break # 判断i位置和i+1位置的两个字符组合起来是不是特殊组合 spe_num = special_num.get(s[i:i+2]) # 如果是特殊组合,则从special_num中获取值,i+=2 if spe_num != None: i += 2 res += spe_num # 如果不是特殊组合,则从roma_num中获取i位字符的值,i+=1 else: res += roma_num.get(s[i]) i+=1 return res
最简单粗暴的做法,穷举每一位字符,并且检查其与右边字符组合是否为特殊组合。分别取值相加即可。
代码2:
class Solution: def romanToInt(self, s: str) -> int: # 所有罗马数字 roma_num = {'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000} res = 0 for idx in range(len(s)): # 获取当前idx位置的值 curr_num = roma_num.get(s[idx]) # 如果后面还有字符 if idx < len(s)-1: # 获取idx+1位置的值 next_num = roma_num.get(s[idx+1]) # 如果idx位置的值小于idx+1位置的值,则为负。例如IV中的I<V,则I=-1,IV=5-1=4 if curr_num < next_num: res -= curr_num else: # 如果idx位置的值大于等于idx+1位置的值,则为正 res += curr_num else: # 最后一位字符,直接获取值加到总值中 res += curr_num return res
找到罗马数字中的规律,即IV、IX、XL、XC、CD、CM都是左边罗马字对应的值小于右边罗马字对应的值,例如I小于V,X小于L。只要出现左边小于右边的情况,左边的数就为负。
总结:
# 这种字符转数字的题目,一般都会用到字段来映射。 # 还有就是要注意找到特别的规律
5.最长公共前缀
题目:
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""
。
示例:
输入: ["flower","flow","flight"] 输出: "fl" 输入: ["dog","racecar","car"] 输出: "" 解释: 输入不存在公共前缀
代码1:
class Solution: def longestCommonPrefix(self, strs: List[str]) -> str: res = "" # 如果strs为空,直接返回"" if len(strs) == 0: return "" # 获取所有字符串的长度,取最小长度 lens = [] for s in strs: lens.append(len(s)) min_len = min(lens) # 循环获取所有字符串的每一位字符,每次都存放到一个set中(自动去重) i = 0 curr = set() while i < min_len: for s in strs: curr.add(s[i]) i+=1 # 如果set中只有一个字符,说明该位字符属于公共前缀 if len(curr) == 1: # 获取该公共前缀字符拼接到结果中 res += curr.pop() else: # 如果set中的字符大于1,则说明最长公共前缀已结束 break return res
这里利用了set的自动去重功能,只需要检查set中元素个数,为1表示大家相等,大于1表示有异数。
代码2:
class Solution: def longestCommonPrefix(self, strs: List[str]) -> str: s = "" # 使用zip将所有字符串的每一位组合成元组(默认按最短字符串长度) # 例如["alex","alenfj","ale123"] ---> [('a','a','a'),('l','l','l'),('e','e','e'),('x','n','1')] for i in zip(*strs): # i就为其中一个元组 # 将元组转换为set,看其种元素个数,如果为1,则元组中元组都相同 if len(set(i)) == 1: s += i[0] else: break return s
利用zip函数来直接将字符串按列拼接成元组。更加简洁。
6.有效的括号
题目:
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
示例:
输入: "()" 输出: true 输入: "()[]{}" 输出: true 输入: "([)]" 输出: false 输入: "{[]}" 输出: true
代码:
class Solution: def isValid(self, s: str) -> bool: # 左右括号映射 map_dict={'(':')','[':']','{':'}'} # 使用一个list模拟栈(先进后出) stack = [] # 循环每一个括号 for c in s: # 看栈中是否为空,如果不为空,则将c与stack中最后一个元素比对,看是否成对 if len(stack) != 0: last = stack[-1] # 配对成功,则弹出stack中的左括号 if c == map_dict.get(last): stack.pop() else: # 配对失败,则将c压入stack stack.append(c) else: # 栈中为空,则无法比对,直接将c压入栈 stack.append(c) # 当所有的括号都配对成功,即stack最终为空时,返回True,否则返回False return True if len(stack)==0 else False
这种解法,主要是利用栈的先进后出特性,匹配相邻两个括号是否配对,如果配对,则弹出。一层一层配对完毕,stack应该为空。
7.合并两个有序链表
题目:
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
代码1:
class Solution : def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: # 先定义一个头结点,返回结果时不需要该头结点 prehead = ListNode(-1) # 使用一个临时变量来作为指针 prev = prehead # 将l1 and l2作为while循环条件,只要有一方为空,就不用再比较大小了,直接可以将还有元素的一方加入到链表最后 while l1 and l2: # 谁小谁加入prev.next if l1.val <= l2.val: prev.next = l1 l1 = l1.next else: prev.next = l2 l2 = l2.next # 每次加一个元素,prev也要向后移一位 prev = prev.next # 如果l2为空,则把l1加到prev后面,否则将l2加到prev后 prev.next = l1 if not l2 else l2 # 返回除去头结点的结果链表 return prehead.next;
这是循环迭代解法,利用循环,直到某个链表元素被取完。然后将还有元素的链表直接挂到最后。
总结:
# 这里使用l1 and l2作为while循环的条件,将需要比较大小的部分全部放在循环中执行
代码2:
class Solution: def mergeTwoLists(self, l1, l2): # 递归结束条件1,l1为空时,返回l2 if l1 is None: return l2 # 递归结束条件2,l2为空时,返回l1 elif l2 is None: return l1 # 如果两个都还有元素,则比较大小,递归的将每次比较的最小值接到当前节点后面 elif l1.val < l2.val: l1.next = self.mergeTwoLists(l1.next, l2) return l1 else: l2.next = self.mergeTwoLists(l1, l2.next) return l2
递归解法,最难理解的就是黄色部分。其实就是在l1和l2都还有元素的时候,两两比较大小,取小的那个放到链表的后面,经过递归的过程,慢慢串成结果链表。
8.删除排序数组中的重复项
题目:
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例:
给定 nums = [0,0,1,1,1,2,2,3,3,4], 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 你不需要考虑数组中超出新长度后面的元素。
代码:
class Solution: def removeDuplicates(self, nums: List[int]) -> int: # 从一个元素开始 i = 1 # 获取初始nums的长度 length = len(nums) # 从1开始循环元素 while i < length: # 如果当前元素和前一个元素相等,则删除当前元素,然后总长度length-=1 if nums[i] == nums[i-1]: del nums[i] length -= 1 # 如果不相等,则循环下一个元素 else: i += 1 # 返回最终list长度 return len(nums)
在当前list空间直接相邻做对比,如果和前一个元素一样,则删除当前元素,记得总长度要调整。
9.移除元素
题目:
给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例:
给定 nums = [0,1,2,2,3,0,4,2], val = 2, 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。 注意这五个元素可为任意顺序。 你不需要考虑数组中超出新长度后面的元素。
代码1:
class Solution: def removeElement(self, nums, val): # 如果nums为空,则返回0 if len(nums) == 0: return 0 # idx为从左到右的索引 idx = 0 # idx_re为从右到左的索引 idx_re = len(nums) - 1 # 总长度 length = len(nums) while idx < length: # 如果元素等于val if nums[idx] == val: # 开始从后面循环,找到一个不等于val的位置 while True: # 如果idx和idx_re碰头了,则已经交换完毕 if idx == idx_re: # 返回需要的长度 return idx # 如果从右边开始的元素等于val,则向前移动一位,再次检测 if nums[idx_re] == val: idx_re -= 1 continue # 当找到一个不等于val的位置,交换idx和idx_re位置的元素 else: nums[idx], nums[idx_re] = nums[idx_re], nums[idx] break # 循环下一个元素 idx += 1
比较笨的办法,将检测到的等于val的元素交换到最后,前面留下全部需要的元素。这种方法不破坏整个数组的元素个数(没这个要求)。
代码2:
class Solution: def removeElement(self, nums: List[int], val: int) -> int: flag = 0 # 定义一个指针标志 for num in nums: # 当元素不等于val的时候,直接从前面开始覆盖(不会影响数据的正确性) if num != val: nums[flag] = num # 覆盖数组,得到「题目要求的数组」 flag += 1 # 指针后移 return flag # 返回「题目要求的数组」的长度
比较巧妙的方法,不考虑数据是否会被破坏,因为破坏的都是不需要的数据。直接从前面开始覆盖即可。
总结:
# 当题目要求,不需要考虑不需要元素的时候,可以采取比较粗暴的覆盖办法。这种办法往往比较简便清晰
10.实现strStr()
题目:
实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
对于本题而言,当 needle
是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。
示例:
输入: haystack = "hello", needle = "ll" 输出: 2 输入: haystack = "aaaaa", needle = "bba" 输出: -1
代码1:
class Solution: def strStr(self, haystack: str, needle: str) -> int: # 先获取两个字符串的长度 hay_len = len(haystack) nee_len = len(needle) # 当needle长度大于kaystack长度,则直接返回-1 if nee_len > hay_len: return -1 # 当needle长度为0,返回0 if nee_len == 0: return 0 # 循环切片(needle大小),然后与needle比较,一样则返回idx idx = 0 while idx < hay_len - nee_len + 1: if haystack[idx:idx+nee_len] == needle: return idx idx += 1 # 如果一直没找到,则返回-1 return -1
常规思路,循环比对。利用python的切片操作。如果是其他语言,可以使用双指针方法。
###