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。
示例:
示例:
输入: [1,8,6,2,5,4,8,3,7]
输出: 49
思路:
双指针法,将两个指针分别置于数组的开头和结尾,使用max_area记录最大面积,每次记录当前矩形的面积(可能会更新最大面积),然后移动值较小的指针,直至两指针重合。
代码:
class Solution(object):
def maxArea(self, height):
"""
:type height: List[int]
:rtype: int
"""
left_index = 0
right_index = len(height) - 1
max_area = 0
while left_index < right_index:
left_value = height[left_index]
right_value = height[right_index]
cur_area = min(left_value, right_value) * (right_index - left_index)
if cur_area > max_area:
max_area = cur_area
if left_value < right_value:
left_index += 1
else:
right_index -= 1
return max_area
分析:
时间复杂度:O(n),只需要扫描一次数组即可
空间复杂度:O(1), 一个额外空间储存max_area
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.
思路:
方法一:穷举个十百千四个位置的罗马字符表示方法,然后对于int按位转换即可。(没有技术含量)
方法二:对于int一直转换至0,转换过程中罗马数字逐渐变大至int原值,注意转换规律是,先转化尽可能大的罗马值字符对应int值
代码:
class Solution(object):
def intToRoman(self, num):
"""
:type num: int
:rtype: str
"""
transform_int = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
transform_roman = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
roman = ""
for index in range(len(transform_int)):
# 下面是一个循环,跳出循环后,index会自动+1
while num >= transform_int[index]:
num -= transform_int[index]
roman += transform_roman[index]
return roman
分析:
时间复杂度:O(1)
空间复杂度:O(1)
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.
思路:
从高位到低位转换罗马字符,需要注意的是,出现C,X,I时,需要考虑后面的两个字符。
代码:
class Solution(object):
def romanToInt(self, s):
"""
:type s: str
:rtype: int
"""
roman_int = {"M": 1000, "CM": 900, "D": 500, "CD": 400,
"C": 100, "XC": 90, "L": 50, "XL": 40,
"X": 10, "IX": 9, "V": 5, "IV": 4,
"I": 1}
num = 0
index = 0
while index < len(s):
one_char = s[index]
if index == len(s) - 1: # 边界情况,two_char可能不存在
return num + roman_int[one_char]
two_char = s[index:index+2]
# 先检查two_char
if two_char in roman_int:
index += 2
num += roman_int[two_char]
elif one_char in roman_int:
index += 1
num += roman_int[one_char]
else:
print("roman is false!")
return 0
return num
分析:
时间复杂度:O(1)
空间复杂度:O(1)
14. 最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
示例:
示例 1:
输入: [“flower”,“flow”,“flight”]
输出: “fl”
示例 2:
输入: [“dog”,“racecar”,“car”]
输出: “”
解释: 输入不存在公共前缀。
说明:
所有输入只包含小写字母 a-z 。
思路:
依次扫描数组中元素的第一个字符,第二个字符…如果索引越界或者某元素字符与第一个元素该位置的字符不一样则终止
代码:
class Solution(object):
def longestCommonPrefix(self, strs):
"""
:type strs: List[str]
:rtype: str
"""
longest_common_prefix = ""
# 边界条件
if strs == [] or strs[0] == "":
return longest_common_prefix
for index in range(len(strs[0])):
target = strs[0][index]
# 扫描strs中的每一个元素
for string in strs:
if index >= len(string) or string[index] != target:
return longest_common_prefix
longest_common_prefix += target
return longest_common_prefix
分析:
时间复杂度:O(nm), n是strs中的字符串数,m是strs中最短的字符串长度
空间复杂度:O(m), 只需要储存最长的公共前缀
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(n3)
方式二:使用双指针+哈希表法,可将时间复杂度控制在O(n2)
程序关键点在于不能包含重复的三元组,上面两种方法加上去重操作,时间复杂度要乘以n
方式三:对数组先排序,对于排序后的数组,使用三个指针i,l,r进行扫描,具体的移动过程如下:
1.指针i从0移动到len(nums)-3位置,移动过程中会跳过相同数值的元素
对于i的移动到某一位置时:
2.l从i+1移动到r-1, 移动过程中跳过重复位置
3.r从len(nums)-1移动到l+1, 移动过程中跳过重复位置
ps: 也可以使用set存储三元组,就会简化移动过程
代码:
class Solution(object):
def threeSum(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
# 需要对数组先排序
nums = sorted(nums)
three_nums = []
for i in range(len(nums)-2):
if i == 0 or nums[i] != nums[i-1]: # 跳过重复的数字
l = i + 1
r = len(nums) - 1
while l < r:
s = nums[i] + nums[l] + nums[r]
if s == 0:
three_nums.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 three_nums
分析:
时间复杂度:排序的时间复杂度是O(nlogn), 找出三元组的时间复杂度是O(n2), 综合的时间复杂度是O(n2)
空间复杂度:O(n), 最多储存O(n)个三元组
16.最接近的三数之和
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
示例:
例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.
与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).
思路:
方式一:暴力枚举,时间复杂度O(n3)
方式二:双指针加哈希表,由于是找最接近的数,无法在O(1)的时间内从哈希表中找出答案,时间复杂度也为O(n3)
方式三:排序加三指针,时间复杂度O(n2), 指针移动的条件要比15题容易一些, 只需要根据三数之和与target的关系移动指针即可
代码:
class Solution(object):
def threeSumClosest(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
if len(nums) < 3:
return None
nums = sorted(nums) # nums进行排序
closest_sum = nums[0] + nums[1] + nums[2] # 初始化cloest_sum
for i in range(0, len(nums)-2):
l = i + 1
r = len(nums) - 1
while l < r:
s = nums[i] + nums[l] + nums[r]
if abs(s-target) < abs(closest_sum-target):
closest_sum = s
if s > target:
r -= 1
elif s == target:
return s
else:
l += 1
return closest_sum
分析:
时间复杂度:O(n2)
空间复杂度:O(1), 只需要常数空间储存几个变量即可
17.电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:“23”
输出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].
思路:
使用循环的方式生成string_list,每次循环的将第一个数字和当前string_list输入一个生成新string_list的函数,
使用递归的方法也可以
代码:
class Solution(object):
def letterCombinations(self, digits):
"""
:type digits: str
:rtype: List[str]
"""
num_letter = {"2":"abc", "3":"def", "4":"ghi", "5":"jkl", "6":"mno", "7":"pqrs", "8":"tuv", "9":"wxyz"}
string_list = []
if digits == None:
return string_list
digits = str(digits)
while digits != "":
if digits[0] not in num_letter:
return None
string_list = self.get_letter_combinations(digits[0], string_list, num_letter)
digits = digits[1:] if len(digits) > 1 else ""
return string_list
def get_letter_combinations(self, digit, string_list, num_letter):
digit_string = num_letter[digit]
string_list = [i for i in digit_string] if string_list == [] else [j + i for j in string_list for i in digit_string]
return string_list
递归版本
class Solution(object):
def __init__(self):
self.num_letter = {"2":"abc", "3":"def", "4":"ghi", "5":"jkl", "6":"mno", "7":"pqrs", "8":"tuv", "9":"wxyz"}
def letterCombinations(self, digits):
"""
:type digits: str
:rtype: List[str]
"""
result = []
index = 0 # 索引指针,从前向后移动
if not digits:
return result
self.get_letter_combinations(digits, index, "", result)
return result
def get_letter_combinations(self, digits,index, s, result):
# 递归基, 一个完成字符串一个完成字符串的生成
if index == len(digits):
result.append(s)
return
num = digits[index]
if num in self.num_letter:
letters = self.num_letter[num]
for letter in letters:
self.get_letter_combinations(digits, index+1, s+letter, result)
else:
self.get_letter_combinations(digits, index+1, s, result)
分析:
时间复杂度:每个数字对应的字母为34个,每次操作用的时间是上次操作的34倍,则时间复杂度为O(3的n次方) 到O(4的n次方)
空间复杂度:需要与时间复杂度级别相同的空间储存生成的结果,O(3的n次方) 到O(4的n次方)
18.四数之和
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]
思路:
类似于三数之和,使用四个指针,两个指针用于外循环,还有两个指针位于剩余数组的左右两端,这两个指针从往中间扫描。
注意不可以有重复的数组,有几种方法可以做到:
1.使用列表存储四元组,每次存储的时候,检查四元组在数组中时候存在
2.使用set存储四元组
3.设置额外的条件,使指针移动的过程中跳过重复的数字
代码:
class Solution(object):
def fourSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[List[int]]
"""
result = []
if len(nums) < 4:
return result
nums = sorted(nums)
for i in range(len(nums) - 3):
for j in range(i+1, len(nums) - 2):
l = j + 1
r = len(nums) - 1
while l < r:
s = nums[i] + nums[j] + nums[l] + nums[r]
combine = [nums[i], nums[j], nums[l], nums[r]]
if s == target:
if combine not in result:
result.append(combine)
l += 1
r -= 1
elif s < target:
l += 1
else:
r -= 1
return result
分析:
时间复杂度:两个外部循环指针,每个是O(n)复杂度,内部l,r两个指针移动是O(n)复杂度,综上是O(n3)
空间复杂度:O(1),储存几个常量即可
19.删除链表的倒数第N个节点
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
进阶:
你能尝试使用一趟扫描实现吗?
思路:
前后指针法,要移除倒数第k个元素时,前面的指针比后面的指针先走k步,当前面的指针达到最后一个节点时,后面的指针的next指向其next的next
代码:
# Definition for singly-linked list.
class ListNode(object):
def __init__(self, x):
self.val = x
self.next = None
class Solution(object):
def removeNthFromEnd(self, head, n):
"""
:type head: ListNode
:type n: int
:rtype: ListNode
"""
dumb = ListNode(0)
dumb.next = head
l_node = dumb # 为了应对倒数的n的节点是首节点,让l_node为哑结点
r_node = head
for _ in range(n-1): # r_node先走n-1步,此时l_node和r_node的距离为n
r_node = r_node.next
if r_node == None:
return dumb.next
while True:
if r_node.next == None:
l_node.next = l_node.next.next
return dumb.next
r_node = r_node.next
l_node = l_node.next
分析:
时间复杂度:O(n)只需要遍历一次链表
空间复杂度:O(1), 只需要常数空间
20.有效的括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
1.左括号必须用相同类型的右括号闭合。
2.左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例:
示例 1:
输入: “()”
输出: true
示例 2:
输入: “()[]{}”
输出: true
示例 3:
输入: “(]”
输出: false
示例 4:
输入: “([)]”
输出: false
示例 5:
输入: “{[]}”
输出: true
思路:
使用栈结构进行验证,逢左括号入栈,逢右括号与栈顶左括号比较,匹配就出栈,不匹配报错,如果栈中没有元素也报错。括号串为空时,如果栈为空则正确。
思路是使用栈结构,其实用列表也可以实现
代码:
class Solution(object):
def isValid(self, s):
"""
:type s: str
:rtype: bool
"""
stack = []
right_left = {")":"(", "}":"{", "]":"["}
for index in range(len(s)):
ele = s[index]
if ele in right_left.values():
stack.append(ele)
elif ele in right_left.keys():
if stack != [] and stack[-1] == right_left[ele]:
stack = stack[:-1]
else:
return False
else:
return False
if stack == []:
return True
return False
分析:
时间复杂度:O(n), 遍历一遍字符串
空间复杂度:O(n),需要O(n)的空间储存括号