31.下一个排列
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
示例:
以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
思路:
本题的重点在于字典序算法,详情请见https://www.cnblogs.com/darklights/p/5285598.html,知道字典序算法后,按照算法实现程序即可。
代码:
class Solution(object):
def nextPermutation(self, nums):
"""
:type nums: List[int]
:rtype: None Do not return anything, modify nums in-place instead.
"""
if nums == []:
return
# 先检查nums是否为最大字典序
max_flag = True
for i in range(len(nums)-1):
if nums[i] < nums[i+1]:
max_flag = False
break
if max_flag:
for i in range(len(nums)//2):
reverse_index = len(nums) - 1 - i
nums[i], nums[reverse_index] = nums[reverse_index], nums[i]
return
# 找到一般情况下的下一个字典序
# 1.从右至左找到第一个左邻比右邻小的数
for i in range(len(nums)-1, 0, -1):
if nums[i-1] < nums[i]:
# 2.从右至左找到第一个比nums[i-1]大的数
for j in range(len(nums)-1, i-1, -1):
if nums[j] > nums[i-1]:
# 3.交换nums[i-1]和nums[j]
nums[j], nums[i-1] = nums[i-1], nums[j]
# 4.对nums[i-1]后面的数,从小到大排序
for k in range(i, len(nums)):
for m in range(i, len(nums) - (k - i) -1):
if nums[m] > nums[m+1]:
nums[m], nums[m+1] = nums[m+1], nums[m]
return
分析:
时间复杂度:上面方法的性能瓶颈在于排序那一步,其他步骤的时间复杂度为O(n),使用归并排序等方法能使得总体时间复杂度降为O(nlogn), 由于是就地算法,如果可以用O(n)的空间,那么就使用桶排序,时间复杂度就降为O(n)
空间复杂度:O(1), 就地算法,只用了常数的额外空间
32.最长有效括号
给定一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。
示例:
示例 1:
输入: “(()”
输出: 2
解释: 最长有效括号子串为 “()”
示例 2:
输入: “)()())”
输出: 4
解释: 最长有效括号子串为 “()()”
思路:
使用两个栈结构进行有效括号统计,一个是"(",")"的栈结构,另一个是数字栈结构,用以记录某位置后面匹配的最长的有效括号数
代码:
class Solution(object):
def longestValidParentheses(self, s):
"""
:type s: str
:rtype: int
"""
# 先放入")"和0,避免边界情况
s_stack = [")"]
n_stack = [0]
for parentheses in s:
if parentheses == ")" and s_stack[-1] == "(": # 只有这种情况会特殊处理
s_stack.pop()
# valid_num记录的是此位置后面有多长的有效括号,当此位置也是有效括号的一部分时,将valid_num + 2 加到前一位置。
valid_num = n_stack.pop()
n_stack[-1] += valid_num + 2
else:
s_stack.append(parentheses)
n_stack.append(0)
return max(n_stack)
分析:
时间复杂度:s中的每一个字符串最多操作O(1)次,那么总的时间复杂度为O(n)
空间复杂度:O(n),引入了栈结构进行储存字符
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
思路:
时间复杂度规定为O(logn),说明需要使用类二分查找法,此题需要两步,第一步二分查找找到旋转点,第二步在有序数组中查找target
旋转数组轴点的特性是,数组中只有轴点的位置nums[pivor-1]>nums[pivor], nums[pivor]是最小点
代码:
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
if nums == []:
return -1
if len(nums) == 1:
return 0 if nums[0] == target else -1
# 先查找旋转点
l = 0
r = len(nums) - 1
while l < r:
# 升序数组不存在相等的情况
mid = (l + r) // 2
if nums[l] < nums[r]:
break
elif nums[l] <= nums[mid]:
# 注意这里的条件是nums[l] <= nums[mid]有一个=号,当l=mid时,说明l和r是相邻的,此时有两种情况,
# 一种nums[l]<nums[r]此时之前的判断已经找到l是轴点,另一种nums[r]>nums[r]此时r为轴点,那么令l=l+1
l = mid + 1
else:
r = mid
# 进行二分查找
pivor = l
index_l = self.binary_search(nums[:pivor], target)
if index_l >= 0:
return index_l
index_r = self.binary_search(nums[pivor:], target)
if index_r >= 0:
return index_r + pivor
return -1
def binary_search(self, nums, target):
l = 0
r = len(nums) - 1
while l <= r:
mid = (l + r) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
l = mid + 1
else:
r = mid - 1
return -1
分析:
时间复杂度:O(logn)
空间复杂度:O(1)
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(object):
def searchRange(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
start = self.search_start(nums, target)
end = self.search_end(nums, target)
return [start, end]
def search_start(self, nums, target):
# 找到最左边的target
if nums == []:
return -1
l = 0
r = len(nums) - 1
while l < r:
mid = (l + r) >> 1
if nums[mid] >= target:
r = mid
else:
l = mid + 1
if nums[l] == target:
return l
return -1
def search_end(self, nums, target):
# 找到最右边的target
if nums == []:
return -1
l = 0
r = len(nums) - 1
while l < r:
mid = (l + r + 1) >> 1 # 注意这里是(l + r + 1) >> 1保证mid取到中间靠右的位置
if nums[mid] <= target:
l = mid
else:
r = mid - 1
if nums[l] == target:
return l
return -1
分析:
时间复杂度:两个二分查找,O(logn)
空间复杂度:O(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
思路:
使用二分查找,查找target,没有找到的话,最后比较nums[l]和target的关系,nums[l]<target,则target的插入位置是l,否则为l+1
代码:
class Solution(object):
def searchInsert(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
if nums == []:
return 0
l = 0
r = len(nums) - 1
while l < r:
mid = (l + r) >> 1
if nums[mid] == target:
return mid
elif nums[mid] < target:
l = mid + 1
else:
r = mid -1
if nums[l] < target:
return l + 1
return l
分析:
时间复杂度:主体为二分查找,时间复杂度为O(logn)
空间复杂度:O(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 存在, 因此这个数独是无效的。
说明:
一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
给定数独序列只包含数字 1-9 和字符 ‘.’ 。
给定数独永远是 9x9 形式的。
思路:
验证数独是否有效比较简单,验证每个位置是否符合数独的三个条件即可(或者验证每行,列和方框是否符合条件即可)
代码:
class Solution(object):
def isValidSudoku(self, board):
"""
:type board: List[List[str]]
:rtype: bool
"""
# 分别验证行,列和方格是否符合数独条件
for row in board:
valid = self.is_valid(row)
if valid == False:
return False
# 注意列的列表表达式
for i in range(len(board)):
column = [board[row][i] for row in range(9)]
valid = self.is_valid(column)
if valid == False:
return False
# 注意每个小方格的列表表达式
for i in range(3):
for j in range(3):
sub_board = [board[3*i+n][3*j+m] for n in range(3) for m in range(3)]
valid = self.is_valid(sub_board)
if valid == False:
return False
return True
def is_valid(self, nums):
num_times = {}
for num in nums:
if num != ".":
if num in num_times:
return False
else:
num_times[num] = 1
return True
分析:
时间复杂度:9*9的方格,时间复杂度是常数级别的。O(1)
空间复杂度:O(1)
37.解数独
编写一个程序,通过已填充的空格来解决数独问题。
一个数独的解法需遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
空白格用 ‘.’ 表示。
示例:
Note:
给定的数独序列只包含数字 1-9 和字符 ‘.’ 。
你可以假设给定的数独只有唯一解。
给定数独永远是 9x9 形式的。
思路:
使用回溯法求解数独,一次填充每一个位置,对于每一个位置从1填充至9。当填充到每一位置时,如果数字有效,填充下一位置(从1到9),如果下一位置都无效,下一位置重置为".",改变当前位置填充值。
代码:
class Solution(object):
def solveSudoku(self, board):
"""
:type board: List[List[str]]
:rtype: None Do not return anything, modify board in-place instead.
"""
start = self.get_start(board) # 获得第一个"."位置
next = self.get_next(board) # 下一个"."位置,是一个位置关系字典
self.solve(board, loc=start, next=next) # 回溯法
def solve(self, board, loc, next):
for num in range(1, 10):
board[loc[0]][loc[1]] = str(num)
if self.is_valid(board, loc): # 检查num填入loc是否有效
if loc in next.keys(): # 有效的情况下,loc在next.keys()中,填充下一位置。
next_loc = next[loc]
if self.solve(board, next_loc, next):
return board
else:
board[next_loc[0]][next_loc[1]] = "." # 这步很重要,下一位置所有数字均无效,则重置为".",返回上级
else: # 有效的情况下,loc不在next.keys()中,说明loc是最后一个"."位置,数独完成。
return True
def is_valid(self, board, loc):
row_num_dict = {}
for row_num in board[loc[0]]:
if row_num != ".":
if row_num in row_num_dict:
return False
else:
row_num_dict[row_num] = True
column_num_dict = {}
for column_num in [board[j][loc[1]] for j in range(9)]:
if column_num != ".":
if column_num in column_num_dict:
return False
else:
column_num_dict[column_num] = True
sub_board_num_dict = {}
sub_board_i = loc[0] // 3
sub_board_j = loc[1] // 3
for sub_board_num in [board[sub_board_i * 3 + n][sub_board_j * 3 + m] for n in range(3) for m in range(3)]:
if sub_board_num != ".":
if sub_board_num in sub_board_num_dict:
return False
else:
sub_board_num_dict[sub_board_num] = True
return True
def get_start(self, board):
for row in range(9):
for column in range(9):
if board[row][column] == ".":
return (row, column)
def get_next(self, board):
next = {}
pre_loc = ""
for row in range(9):
for column in range(9):
if board[row][column] == ".":
cur_loc = (row, column)
if pre_loc:
next[pre_loc] = cur_loc
pre_loc = cur_loc
return next
分析:
时间复杂度:回溯法的时间复杂度很高,具体级别不是很清楚,解数独是一个NP难的问题
空间复杂度:O(1), 储存一个board和常数级别的字典即可
38.报数
报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。其前五项如下:
-
1
-
11
-
21
-
1211
-
111221
1 被读作 “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个数是第n-1个数的读音,每个读连续相同的数字段,例如,数列第一项不是1吗,那第二项就报第一项的有1个1,输出11,然后第三项就在第二项的基础上报数,第二项是11,第三项不就是2个1么,然后输出21。。。
代码:
class Solution(object):
def countAndSay(self, n):
"""
:type n: int
:rtype: str
"""
nums = []
if n <= 0:
return None
# 构建长度为n的nums,最后的输出结果为nums[-1]
nums.append("1")
for i in range(n-1):
nums.append(self.say(nums[-1]))
return nums[-1]
def say(self, num):
say_num = ""
# 读num, 一次读取连续相同的数字字符
pre_char = ""
count = 0
for char in num:
if pre_char == "" or char == pre_char:
count += 1
else:
say_num += str(count) + pre_char
count = 1
pre_char = char
say_num += str(count) + pre_char # 写入最后一串相同数字
return say_num
分析:
时间复杂度:这题的时间复杂度不好论证,我的猜测是至少为O(n2)以上
空间复杂度:O(n2)以上
39.组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例:
示例 1:
输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
思路:
本题需要使用递归的办法,类似于第17题电话号码组合,递归的关键是控制好start,做到不重不漏。
代码:
class Solution(object):
def combinationSum2(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
result = []
if candidates == []:
return result
candidates = sorted(candidates) # 对数组进行排序
for i in range(len(candidates)): # 第一重循环,start从0到len(candidates)-1
self.get_candidate(candidates, target, start=i, sub=[], result=result)
return result
def get_candidate(self, candidates, target, start, sub, result):
ele = candidates[start] # ele是目前可用的最小的元素
# 递归基
if ele > target:
return
elif ele == target:
if sub + [ele] not in result: # 去重
result.append(sub + [ele]) # 使用sub + [ele],不能用sub.append(ele), sub.append(ele)没有返回值
return
elif ele < target:
# 第n重循环,新的start是从start到len(candidates)-1
for i in range(start, len(candidates)):
if candidates[i] <= target - ele: # 提前终止无效的迭代
self.get_candidate(candidates, target - ele, i, sub + [ele], result)
else:
break
分析:
时间复杂度:O(n3), 对于1到n这么一个candidate,target为n,start为1是会有n2种情况,start可以为1到n,所以总的时间复杂度为n3
空间复杂度:O(n3)
40.组合总和 II
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
示例:
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]
思路:
与上题十分类似,区别在于每个数只能用一次,那么第n重循环的新start起点应该为start+1
代码:
class Solution(object):
def combinationSum2(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
result = []
if candidates == []:
return result
candidates = sorted(candidates) # 对数组进行排序
for i in range(len(candidates)): # 第一重循环,start从0到len(candidates)-1
self.get_candidate(candidates, target, start=i, sub=[], result=result)
return result
def get_candidate(self, candidates, target, start, sub, result):
ele = candidates[start] # ele是目前可用的最小的元素
# 递归基
if ele > target:
return
elif ele == target:
if sub + [ele] not in result: # 去重
result.append(sub + [ele]) # 使用sub + [ele],不能用sub.append(ele), sub.append(ele)没有返回值
return
elif ele < target:
# 第n重循环,新的start是从start+1到len(candidates)-1
for i in range(start+1, len(candidates)):
if candidates[i] <= target - ele: # 提前终止无效的迭代
self.get_candidate(candidates, target - ele, i, sub + [ele], result)
else:
break
分析:
时间复杂度:O(n3), 对于1到n这么一个candidate,target为n,start为1是会有n2种情况,start可以为1到n,所以总的时间复杂度为n3
空间复杂度:O(n3)