开始刷题啦,打算先刷前100道题,从简单——中等——困难的顺序刷。根据前100题的经验判断100题-200题是否要全部刷。
还是中等的题有些难度,至少一天三道。加油\^-^/ 39/100完成度
值得再看一遍的题:14,26,38,53,70,88,100
不需要再看的:1 34 36
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: List[int], target: int) -> List[int]:
# 检测输入列表不为空
if not nums:
return None
d = dict()
for i,val in enumerate(nums):
if val in d.keys():
return [d[val], i]
d[target - val] = i
return None
2. 两数相加
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
re = ListNode(0)
r=re
carry=0
while(l1 or l2):
x= l1.val if l1 else 0
y= l2.val if l2 else 0
s=carry+x+y
carry=s//10
r.next=ListNode(s%10)
r=r.next
if(l1!=None):l1=l1.next
if(l2!=None):l2=l2.next
if(carry>0):
r.next=ListNode(1)
return re.next
3. 无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1: 输入: "abcabcbb" 输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。示例 2: 输入: "bbbbb" 输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。示例 3: 输入: "pwwkew" 输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
双指针代表滑动区间:快指针记录要添加的元素。慢指针指向要缩减的元素。
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
# 双指针代表滑动区间的左右坐标
n = len(s)
se = set()
slow = 0
max_len = cur_len = 0
for fast in range(n):
cur_len += 1
while s[fast] in se:
se.remove(s[slow])
cur_len -= 1
slow += 1
max_len = max(max_len,cur_len)
se.add(s[fast])
return max_len
5. 最长回文子串
暴力解法:最长回文子串即颠倒后相同的字符串。使用暴力的滑动窗口解法,首先判断整个字符串是否是回文子串,若是返回整个字符串。若不是再取长度-1,再判断是否是回文子串,若是返回,不是长度继续-1,反复循环,直到长度为1停止。长度为1直接返回字符串首位即得到最长回文子串。
时间复杂度:取得所有子串需要两层循环,将子串翻转还需要O(N),一共需要时间复杂度O(N^3)
class Solution:
def longestPalindrome(self, s: str) -> str:
# 滑动窗口暴力解法
n = len(s)
if n <= 1: return s
for i in range(n,1,-1):
for left in range(0,n-i+1):
a = s[left:left+i]
b = a[::-1]
if a == b:
return a
return s[0]
中心扩散法:
遍历每一个索引,以这个索引为中心,利用“回文串”中心对称的特点,往两边扩散,看最多能扩散多远。
在这里要注意一个细节:回文串在长度为奇数和偶数的时候,“回文中心”的形式是不一样的。
奇数回文串的“中心”是一个具体的字符,例如:回文串 "aba" 的中心是字符 "b";
偶数回文串的“中心”是位于中间的两个字符的“空隙”,例如:回文串串 "abba" 的中心是两个 "b" 中间的那个“空隙”。
我们可以设计一个方法,兼容以上两种情况:
1、如果传入重合的索引编码,进行中心扩散,此时得到的回文子串的长度是奇数;
2、如果传入相邻的索引编码,进行中心扩散,此时得到的回文子串的长度是偶数。
时间复杂度:枚举“中心位置”时间复杂度为 O(N),从“中心位置”扩散得到“回文子串”的时间复杂度为 O(N),因此时间复杂度可以降到 O(N^2)。
class Solution:
def longestPalindrome(self, s: str) -> str:
size = len(s)
if size < 2:
return s
# 至少是 1
max_len = 1
res = s[0]
for i in range(size):
palindrome_odd, odd_len = self.__center_spread(s, size, i, i)
palindrome_even, even_len = self.__center_spread(s, size, i, i + 1)
# 当前找到的最长回文子串
cur_max_sub = palindrome_odd if odd_len >= even_len else palindrome_even
if len(cur_max_sub) > max_len:
max_len = len(cur_max_sub)
res = cur_max_sub
return res
def __center_spread(self, s, size, left, right):
"""
left = right 的时候,此时回文中心是一个字符,回文串的长度是奇数
right = left + 1 的时候,此时回文中心是一个空隙,回文串的长度是偶数
"""
i = left
j = right
while i >= 0 and j < size and s[i] == s[j]:
i -= 1
6. Z 字形变换
将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下:
L C I R
E T O E S I I G
E D H N
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"LCIRETOESIIGEDHN"。
示例 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
思路: 按行访问
首先访问 行 0 中的所有字符,接着访问 行 1,然后 行 2,依此类推...
对于所有整数 k,
行 0中的字符位于索引 k(2⋅numRows−2) 处;
行 numRows−1 中的字符位于索引k(2⋅numRows−2)+numRows−1 处;
内部的行i中的字符位于索引 k(2⋅numRows−2)+i 以及(k+1)(2⋅numRows−2)−i 处;
时间复杂度:O(n),每个索引被访问一次。
空间复杂度:O(n)
class Solution:
def convert(self, s: str, numRows: int) -> str:
n = len(s)
if n<=numRows or numRows<=1: return s
space = [i for i in range((numRows-1)*2,0,-2)]
res = ''
for i in range(numRows):
if i==0 or i==numRows-1:
res += s[i::space[0]]
else:
j = i
flag = True
while j < n:
res += s[j]
j += space[i] if flag else space[0]-space[i]
flag = not flag
return res
思路:用一个二维列表记录元素,内部每一个列表代表一行,只要遍历字符串,将相应字符放置在正确位置,最后按行遍历二维列表得到的字符串即是Z字变换的返回值。
从左到右迭代 s,将每个字符添加到合适的行。可以使用当前行和当前方向这两个变量对合适的行进行跟踪。
只有当我们向上移动到最上面的行或向下移动到最下面的行时,当前方向才会发生改变。
时间复杂度:O(n)
空间复杂度:O(n)
class Solution:
def convert(self, s: str, numRows: int) -> str:
n = len(s)
if n<=numRows or numRows<=1: return s
rows = list()
for i in range(min(numRows,len(s))):
rows.append([])
curRow = 0
goingDown = False
for c in s:
rows[curRow].append(c)
if curRow==0 or curRow==numRows-1: goingDown = not goingDown
curRow += 1 if goingDown else -1
# rows = [['s','a','g'],['a','b','g'],['y','a','i']]
res = ''
for row in rows:
res += ''.join(row)
return res
7. 整数反转
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
输入: 123 输出: 321
输入: -123 输出: -321
输入: 120 输出: 21
注意:假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
class Solution:
def reverse(self, x: int) -> int:
if x<0:
flag = -1
x *= flag
else:
flag = 1
count = 0
while x:
count = count * 10 + x % 10
x = x // 10
count *= flag
if count < -2**31 or count >2**31-1:
return 0
return count
思路:想要将数字倒置,也就是让最低位变为最高位,次最低位变成次最高位,首先将符号先存储好,每次将数字的个位与高位分开,每次将个位与先前拿到的数*10相加,这样不断循环完成数字位数的翻转。注意条件溢出时返回0。
Python还可将数字直接转换为字符串,再将结果用切片翻转,很好想但速度不快。
9. 回文数
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
示例 1: 输入: 121 输出: true
示例 2: 输入: -121 输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3: 输入: 10 输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。
class Solution:
def isPalindrome(self, x: int) -> bool:
x = str(x)
y = x[::-1]
if x == y:
return True
else:
return False
12. 整数转罗马数字
罗马数字包含以下七种字符: 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: 输入: 3 输出: "III"
示例 2: 输入: 4 输出: "IV"
示例 3: 输入: 9 输出: "IX"
示例 4: 输入: 58 输出: "LVIII" 解释: L = 50, V = 5, III = 3.
示例 5: 输入: 1994
输出: "MCMXCIV" 解释: M = 1000, CM = 900, XC = 90, IV = 4.
class Solution:
def intToRoman(self, num: int) -> str:
val_list = [1000,900,500,400,100,90,50,40,10,9,5,4,1]
lab_list = ['M','CM','D','CD','C','XC','L','XL','X','IX','V','IV','I']
res = ''
cur = 0
while num != 0:
while val_list[cur] <= num:
num -= val_list[cur]
res += lab_list[cur]
else:cur += 1
return res
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.
class Solution:
def romanToInt(self, s: str) -> int:
outcome = 0
val = {'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000}
for i in range(len(s)-1):
if val[s[i]] < val[s[i+1]]:
outcome -= val[s[i]]
else:
outcome += val[s[i]]
return outcome + val[s[-1]]
14. 最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。
示例 1: 输入: ["flower","flow","flight"] 输出: "fl"
示例 2: 输入: ["dog","racecar","car"] 输出: ""
解释: 输入不存在公共前缀。
说明: 所有输入只包含小写字母 a-z 。
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
if len(strs) == 0: return ''
if len(strs) == 1: return strs[0]
s = strs[0]
for i in range(1,len(strs)):
while not strs[i].startswith(s):
s = s[:-1]
if s == '':
return ''
return s
思路:先得到 flower 和 flow 的共同前缀,为 flow ,再与 flight 找前缀为 fl
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^3) ,一看就不是很容易。而且此题[-1,0,1,2,-1,-4,1]与[-1,0,1,2,-1,-4]答案相同,不免看出如果按原顺序查找[-1,0,1(第三位的1)],[-1,0,1(第7位的1)]会造成输出过多,那么我们想把有相同的元素的变为一个呢,显然[-1,-1,2]这个答案就证明不能这么做。那么很容易想出想要位置不影响输出结果,就需要首先让数组进行排序。 [-1,0,1,2,-1,-4]--》[-4,-1,-1,0,1,2]
算法流程:
- 特判,对于数组长度 n,如果数组为 null或者数组长度小于 3,返回 []。
- 对数组进行排序。
- 遍历排序后数组:
若 nums[i]>0:因为已经排序好,所以后面不可能有三个数加和等于 0,直接返回结果。
对于重复元素:跳过,避免出现重复解
令左指针 L=i+1,右指针 R=n-1,当 L<R 时,执行循环:
当 nums[i]+nums[L]+nums[R]==0,执行循环,判断左界和右界是否和下一位置重复,去除重复解。并同时将 L,R移到下一位一置,寻找新的解
若和大于 0,说明 nums[R] 太大,R 左移
若和小于 0,说明 nums[L] 太小,L 右移
时间复杂度:O(n^2),数组排序O(NlogN),遍历数组 O(n),双指针遍历O(n),总体O(NlogN)+O(n)∗O(n) =O(n^2)
空间复杂度:O(1)O(1)
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
if(not nums or n<3):
return []
res = []
nums.sort()
for i in range(n):
if(nums[i]>0):
return res
if i == 0 or nums[i] > nums[i-1]:
l = i+1
r = n-1
while l < r:
s = nums[i] + nums[l] + nums[r]
if s == 0:
res.append([nums[i],nums[l],nums[r]])
l += 1
r -= 1
while l < r and nums[l] == nums[l-1]:
l += 1
while l < r and nums[r] == nums[r+1]:
r -= 1
elif s > 0:
r -= 1 # 右边坐标左移一位
else:
l += 1 # 左边坐标右移一位
return res
16. 最接近的三数之和
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.
与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).
思路:与15题相同,首先排序,但此题
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
n = len(nums)
if not nums or n<3:
return
nums.sort() # [-4,-1,1,2] target=1
res = abs(sum(nums[0:3]) - target)
s_out = sum(nums[0:3])
for i in range(n):
if i==0 or nums[i] > nums[i-1]:
l = i+1
r = n-1
while l < r:
s = nums[i] + nums[l] +nums[r]
if s == target:
return target
elif s > target:
if s-target < res:
res = s-target
s_out = s
r -= 1
while l<r and nums[r] == nums[r+1]:
r -= 1
else:
if target -s < res:
res = target -s
s_out = s
l += 1
while l<r and nums[l] == nums[l-1]:
l += 1
return s_out
17. 电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:"23" 输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
class Solution(object):
def letterCombinations(self, digits):
"""
动态规划
dp[i]: 前i个字母的所有组合
由于dp[i]只与dp[i-1]有关,可以使用变量代替列表存储降低空间复杂度
:type digits: str
:rtype: List[str]
"""
if not digits:
return []
d = {'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
'6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'}
n = len(digits)
dp = [[] for _ in range(n)]
dp[0] = [x for x in d[digits[0]]]
for i in range(1, n):
dp[i] = [x + y for x in dp[i - 1] for y in d[digits[i]]]
return dp[-1]
def letterCombinations2(self, digits):
"""
使用变量代替上面的列表
降低空间复杂度
:type digits: str
:rtype: List[str]
"""
if not digits:
return []
d = {'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
'6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'}
n = len(digits)
res = ['']
for i in range(n):
res = [x + y for x in res for y in d[digits[i]]]
return res
def letterCombinations3(self, digits):
"""
递归
:param digits:
:return:
"""
d = {'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
'6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'}
if not digits:
return []
if len(digits) == 1:
return [x for x in d[digits[0]]]
return [x + y for x in d[digits[0]] for y in self.letterCombinations3(digits[1:])]
20. 有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。示例 1: 输入: "()" 输出: true
示例 2: 输入: "()[]{}" 输出: true
示例 3: 输入: "(]" 输出: false
示例 4: 输入: "([)]" 输出: false
示例 5: 输入: "{[]}" 输出: true
class Solution:
def isValid(self, s: str) -> bool:
if s == '':return True
d = {'}':'{', ')':'(', ']':'['}
l = list()
for i in s:
if i in d:
top = l.pop() if l else '#'
if top != d[i]:
return False
else:
l.append(i)
return not l
思路:栈
21. 合并两个有序链表
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
if not l1:return l2
if not l2:return l1
cu1,cu2 = l1,l2
cur = ListNode(0)
head = cur
while cu1 and cu2:
if cu1.val <= cu2.val:
node = ListNode(cu1.val)
cu1 = cu1.next
else:
node = ListNode(cu2.val)
cu2 = cu2.next
cur.next = node
cur = cur.next
if cu1: cur.next = cu1
if cu2: cur.next = cu2
return head.next
思路:双指针遍历,构建新列表。
空间复杂度:O(m+n)
时间复杂度:O(m+n)
26. 删除排序数组中的重复项
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 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:
if nums == []: return 0
slow = 0
for fast in range(1,len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow+1
思想:用一个慢指针记录新列表尾部,用快指针遍历直到发现不等于尾部的,将此元素添加到尾部。
执行用时 :36 ms
内存消耗 :14.4 MB
方法二:反向遍历
def removeDuplicates(nums):
for num_index in range(len(nums)-1, 0, -1):
if nums[num_index] == nums[num_index-1]:
nums.pop(num_index)
return len(nums)
思想:从nums的最后一个开始遍历, 然后与前一个进行对比,如果相等,,则删除该位置的数;不等, 则继续往前遍历
执行用时:120ms
内存消耗:15.6MB
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。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
if nums == [] or val is None:
return len(nums)
slow = 0
for fast in range(len(nums)):
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
return slow
思想:快慢指针
28. 实现 strStr()
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
示例 1: 输入: haystack = "hello", needle = "ll" 输出: 2
示例 2: 输入: haystack = "aaaaa", needle = "bba" 输出: -1
说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
return haystack.find(needle)
34. 在排序数组中查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]。
示例 1: 输入: nums = [5,7,7,8,8,10], target = 8 输出: [3,4]
示例 2: 输入: nums = [5,7,7,8,8,10], target = 6 输出: [-1,-1]
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
# 二分查找,找到扩散
if not nums:return [-1,-1]
n = len(nums)
low = 0
high = n-1
while low <= high:
mid = (low+high)>>1
if nums[mid] == target:
start = end = mid
while start>0 and nums[start-1]==target:
start -= 1
while end<n-1 and nums[end+1]==target:
end += 1
return [start,end]
elif nums[mid] > target:
high = mid - 1
else:
low = mid + 1
return [-1,-1]
35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1: 输入: [1,3,5,6], 5 输出: 2
示例 2: 输入: [1,3,5,6], 2 输出: 1
示例 3: 输入: [1,3,5,6], 7 输出: 4
示例 4: 输入: [1,3,5,6], 0 输出: 0
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
if target < nums[0]:return 0 # 查找元素小于列表最小值
if target > nums[-1]:return len(nums) # 查找元素大于列表最大值
# 查找元素肯定可以在列表中找到适当位置
for i in range(len(nums)):
# 正好找到该元素,返回索引
if nums[i] == target:
return i
# 前面元素小于查找元素,后面元素大于查找元素,返回后面元素索引
if nums[i] < target and nums[i+1]> target:
return i+1
36. 有效的数独
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
上图是一个部分填充的有效的数独。
数独部分空格内已填入了数字,空白格用
'.'
表示。示例 1:输入:
[
["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
输出: true
示例 2:输入:
[
["8","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
输出: false 解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。
思路:判断行有没有重复的、判断列有没有重复的、判断每个3x3方格有没有重复的
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
if not board or len(board)!=9 or len(board[0])!=9:
return False
# 判断行有没有重复的
for row in board:
l = []
for i in row:
if i == '.':continue
if i not in l: l.append(i)
else: return False
# 判断列有没有重复的
for j in range(9):
l=[]
for i in range(9):
if board[i][j] == '.':continue
if board[i][j] not in l:l.append(board[i][j])
else:return False
# 判断3x3方格有没有重复的
for i in (0,3,6):
for j in (0,3,6):
l=[]
for x in range(i,i+3):
for y in range(j,j+3):
if board[x][y] == '.':continue
if board[x][y] not in l:l.append(board[x][y])
else:return False
return True
38. 外观数列
「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。前五项如下:
1. 1
2. 11
3. 21
4. 1211
5. 1112211 被读作 "one 1" ("一个一") , 即 11。
11 被读作 "two 1s" ("两个一"), 即 21。
21 被读作 "one 2", "one 1" ("一个二" , "一个一") , 即 1211。
给定一个正整数 n(1 ≤ n ≤ 30),输出外观数列的第 n 项。
注意:整数序列中的每一项将表示为一个字符串。
示例 1: 输入: 1 输出: "1"
示例 2: 输入: 4 输出: "1211"
解释:当 n = 3 时,序列是 "21",其中我们有 "2" 和 "1" 两组,"2" 可以读作 "12",也就是出现频次 = 1 而 值 = 2;类似 "1" 可以读作 "11"。所以答案是 "12" 和 "11" 组合在一起,也就是 "1211"。
class Solution:
def countAndSay(self, n: int) -> str:
if n == 1: return '1'
pre = self.countAndSay(n-1) # pre存放前一个字符串
res = []
count = 1
for idx in range(len(pre)):
if idx == 0 :
count = 1
elif pre[idx] != pre[idx -1]:
res.append(count)
res.append(pre[idx-1])
count = 1
elif pre[idx] == pre[idx-1]:
count +=1
if idx == len(pre) - 1:
res.append(count)
res.append(pre[idx])
return ''.join(str(i) for i in res)
思路:
使用递归的方法,这道题其实跟斐波那契数列有点类似,都是基于上一项的结果得出下一项的结果,因此适合使用递归来解决问题。
因为确定了n=1时的值,所以基线条件就可以设置为 n <= 1 时返回。
在后面的循环里,思路是当遇到的字符串与上一个相等的时候,计数器+1,否则结束计数,并将当前结果添加到res变量中,并且计数器
复到1。比较需要注意的是边际条件,在字符串开头跟结尾的时候做了单独的处理。
处理字符串开头的时候
if idx == 0 :
count = 1
处理字符串末尾的时候
if idx == len(pre) - 1:
res.append(count)
res.append(pre[idx])
53. 最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例: 输入: [-2,1,-3,4,-1,2,1,-5,4], 输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
max_sum = nums[0]
for i in range(1,len(nums)):
if nums[i-1] > 0:
nums[i] += nums[i-1]
max_sum = max(max_sum, nums[i])
return max_sum
思想:动态规划
- 时间复杂度:\mathcal{O}(N)O(N)。只遍历了一次数组。
- 空间复杂度:\mathcal{O}(1)O(1),使用了常数的空间。
58. 最后一个单词的长度
给定一个仅包含大小写字母和空格 ' ' 的字符串 s,返回其最后一个单词的长度。
如果字符串从左向右滚动显示,那么最后一个单词就是最后出现的单词。
如果不存在最后一个单词,请返回 0 。
说明:一个单词是指仅由字母组成、不包含任何空格的 最大子字符串。
示例:
输入: "Hello World"
输出: 5
class Solution:
def lengthOfLastWord(self, s: str) -> int:
if s is None or len(s) == 0:
return 0
s = s.rstrip()
words = s.split(' ')
return len(words[-1])
66. 加一
给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
示例 1: 输入: [1,2,3] 输出: [1,2,4]
解释: 输入数组表示数字 123。
示例 2: 输入: [4,3,2,1] 输出: [4,3,2,2]
解释: 输入数组表示数字 4321。
class Solution:
def plusOne(self, digits: List[int]) -> List[int]:
# 尝试最笨的算法
for i in range(len(digits)-1,-1,-1):
if digits[i] + 1 == 10:
digits[i] = 0
else:
digits[i] = digits[i] + 1
return digits
a = [1]
a.extend(digits)
return a
执行用时 :32 ms, 在所有 Python3 提交中击败了86.63%的用户
内存消耗 :13.4 MB, 在所有 Python3 提交中击败了34.26%的用户
class Solution:
def plusOne(self, digits: List[int]) -> List[int]:
# 转换成数字
digits = [str(i) for i in digits]
num = int(''.join(digits))
num += 1
return list(map(eval, list(str(num))))
思路:转换为数字进行加和再转成列表,显然时间复杂度高于上面,但是我们可以用此题复习一下类型转换
首先将,数字列表转为字符列表。注:数字列表需要先变为字符列表才能进一步使用.join方法
list1 = [1,2,3,4]
list2 = [str(i) for i in list1]
# list2 = ['1','2','3','4']
其次,将字符列表连接成字符串。
str = ''.join(['1','2','3','4'])
# str = '1234'
再将数字变成字符列表。注:数字不能直接转换成列表
list3 = list(str(1234))
# list3 = ['1','2','3','4']
最后,将字符列表转换为数字列表
list4 = list(map(eval,['1','2','3','4']))
list5 = list(map(int,['1','2','3','4']))
# list4 = list5 = [1,2,3,4]
67. 二进制求和
给定两个二进制字符串,返回他们的和(用二进制表示)。
输入为非空字符串且只包含数字 1 和 0。
示例 1: 输入: a = "11", b = "1" 输出: "100"
示例 2: 输入: a = "1010", b = "1011" 输出: "10101"
class Solution:
def addBinary(self, a: str, b: str) -> str:
if a == '': return b
if b == '': return a
return '{:b}'.format(int(a, 2) + int(b, 2))
69. x 的平方根
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1: 输入: 4 输出: 2
示例 2: 输入: 8 输出: 2
说明: 8 的平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
import math
class Solution:
def mySqrt(self, x: int) -> int:
return math.trunc(math.sqrt(x))
70. 爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1: 输入: 2 输出: 2
解释: 有两种方法可以爬到楼顶。1. 1 阶 + 1 阶 2. 2 阶
示例 2: 输入: 3 输出: 3
解释: 有三种方法可以爬到楼顶。1. 1 阶 + 1 阶 + 1 阶 2. 1 阶 + 2 阶 3. 2 阶 + 1 阶
暴力法:超出时间限制
class Solution:
def climbStairs(self, n: int) -> int:
if n == 0: return 0
if n == 1: return 1
if n == 2: return 2
return self.climbStairs(n-1) + self.climbStairs(n-2)
动态规划
class Solution:
def climbStairs(self, n: int) -> int:
if n <= 1: return 1
l = [0] * (n+1) # l=[0 for i in range(n+1)]
l[1] = 1
l[2] = 2
for i in range(3,n+1):
l[i] = l[i-1] + l[i-2]
return l[n]
83. 删除排序链表中的重复元素
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1: 输入: 1->1->2 输出: 1->2
示例 2: 输入: 1->1->2->3->3 输出: 1->2->3
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def deleteDuplicates(self, head: ListNode) -> ListNode:
# 直接在链表上修改
if head is None: return None
post = head
while post.next:
cur = post
post = post.next
if cur.val == post.val:
cur.next = post.next
post = cur
return head
88. 合并两个有序数组
给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
说明:
初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例: 输入:nums1 = [1,2,3,0,0,0],m = 3,nums2 = [2,5,6],n = 3
输出: [1,2,2,3,5,6]
自创最笨写法:找到元素该插入的位置,将其它元素后移
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
if n <= 0: return
if m <= 0:
m = n
for i in range(m):
nums1[i] = nums2[i]
return
start = 0
for j in range(n):
for i in range(start,m):
if nums1[i] <= nums2[j]: # 插入值大于当前值
continue
else: # 插入到目前位置
z = m
while z > i:
nums1[z] = nums1[z-1]
z -= 1
nums1[i] = nums2[j]
m += 1
break
if i == m-1:
nums1[m] = nums2[j]
m += 1
start = i
合并后排序
class Solution(object):
def merge(self, nums1, m, nums2, n):
nums1[:] = sorted(nums1[:m] + nums2)
方法二 : 双指针 / 从前往后
一般而言,对于有序数组可以通过 双指针法 达到O(n + m)的时间复杂度。
最直接的算法实现是将指针p1 置为 nums1的开头, p2为 nums2的开头,在每一步将最小值放入输出数组中。
由于 nums1 是用于输出的数组,需要将nums1中的前m个元素放在其他地方,也就需要 O(m) 的空间复杂度。
class Solution(object):
def merge(self, nums1, m, nums2, n):
nums1_copy = nums1[:m]
nums1[:] = []
p1 = 0
p2 = 0
while p1 < m and p2 < n:
if nums1_copy[p1] < nums2[p2]:
nums1.append(nums1_copy[p1])
p1 += 1
else:
nums1.append(nums2[p2])
p2 += 1
if p1 < m:
nums1[p1 + p2:] = nums1_copy[p1:]
if p2 < n:
nums1[p1 + p2:] = nums2[p2:]
方法三 : 双指针 / 从后往前
方法二已经取得了最优的时间复杂度O(n + m),但需要使用额外空间。这是由于在从头改变nums1的值时,需要把nums1中的元素存放在其他位置。
如果我们从结尾开始改写 nums1 的值又会如何呢?这里没有信息,因此不需要额外空间。
这里的指针 p 用于追踪添加元素的位置。
class Solution(object):
def merge(self, nums1, m, nums2, n):
p1 = m - 1
p2 = n - 1
p = m + n - 1
while p1 >= 0 and p2 >= 0:
if nums1[p1] < nums2[p2]:
nums1[p] = nums2[p2]
p2 -= 1
else:
nums1[p] = nums1[p1]
p1 -= 1
p -= 1
nums1[:p2 + 1] = nums2[:p2 + 1]
100. 相同的树
给定两个二叉树,编写一个函数来检验它们是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
示例 1:
输入: 1 1
/ \ / \
2 3 2 3[1,2,3], [1,2,3]
输出: true
示例 2:输入: 1 1
/ \
2 2[1,2], [1,null,2]
输出: false
示例 3:输入: 1 1
/ \ / \
2 1 1 2[1,2,1], [1,1,2]
输出: false
递归:最简单的策略是使用递归。首先判断 p
和 q
是不是 None
,然后判断它们的值是否相等。若以上判断通过,则递归对子结点做同样操作。
class Solution:
def isSameTree(self, p, q):
if not p and not q:
return True
if not q or not p:
return False
if p.val != q.val:
return False
return self.isSameTree(p.right, q.right) and self.isSameTree(p.left, q.left)
复杂度分析
时间复杂度 : O(N),其中 N 是树的结点数,因为每个结点都访问一次。
空间复杂度 : 最优情况(完全平衡二叉树)时为 O(log(N)),最坏情况下(完全不平衡二叉树)时为 O(N),用于维护递归栈。