文章目录
1. 动态规划
1.1 最小路径和(64)
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
解法
采用动态规划的思想,对于第一行和第一列的的元素分别进行处理,第一行只能是左边的元素相加得到,第一列只能是上边的元素相加得到,而其他的元素可以由其左边和上边元素的最小值加上自身得到,
下面给出两种解法,一种是新建一个矩阵,另一种是直接在上面修改。
class Solution(object):
def minPathSum(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
dp = grid
for i in range(len(grid)):
for j in range(len(grid[0])):
if i == 0:
if j == 0:
dp[0][0] == grid[0][0]
else:
dp[i][j] = dp[i][j-1] + grid[i][j]
elif j == 0:
dp[i][j] = dp[i-1][j] + grid[i][j]
else:
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
return dp[i][j]
class Solution(object):
def minPathSum(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
m=len(grid)
n=len(grid[0])
for i in range(1,m): #修改第一列
grid[i][0]=grid[i-1][0]+grid[i][0]
for j in range(1,n): #修改第一行
grid[0][j]=grid[0][j-1]+grid[0][j]
for i in range(1,m): #开始填表
for j in range(1,n):
grid[i][j]=grid[i][j]+min(grid[i-1][j],grid[i][j-1])
return grid[-1][-1]
1.2 最大子序和(53)
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
解法:可以有两种解法,一种是分治,一种是动态规划。
分治:将输入的序列分成两部分,这个时候有三种情况。
1)最大子序列在左半部分
2)最大子序列在右半部分
3)最大子序列跨越左右部分。
(代码不太好理解)
动态规划
class Solution(object):
def maxSubArray(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
le = len(nums)
for i in range (1,le):
###前i-1个最大的结果已经存在i-1中,每次都可看做是两个数相加
submax = max(nums[i]+nums[i-1],nums[i])
###把每次最大的结果赋值给当前值,然后继续下一个两数相加
nums[i] = submax
return max(nums)
1.3 编辑距离(72)
给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
示例 1:
输入: word1 = “horse”, word2 = “ros”
输出: 3
解释:
horse -> rorse (将 ‘h’ 替换为 ‘r’)
rorse -> rose (删除 ‘r’)
rose -> ros (删除 ‘e’)
示例 2:
输入: word1 = “intention”, word2 = “execution”
输出: 5
解释:
intention -> inention (删除 ‘t’)
inention -> enention (将 ‘i’ 替换为 ‘e’)
enention -> exention (将 ‘n’ 替换为 ‘x’)
exention -> exection (将 ‘n’ 替换为 ‘c’)
exection -> execution (插入 ‘u’)
解法:动态规划
动态规划的思想,和前面不同路径的题目类似。设想用dp二维数组存储word1的前 i 个字符和Word2的前 j 个字符达成匹配的步数。
边界条件:dp[i][0]=0:m,dp[0][j]=0:n,其中,m和n分别表示word1和word2的字符数。
状态转移方程是:
如果word[i]==word[j],则当前达到匹配的步数dp[i+1][j+1]=dp[i][j]
如果word[i] word[j],则需要在word1进行添加,删除,替换的一种操作。选择当前位置之前已经累计最少的次数,然后加上当前的操作数 1 即可。
如果是添加操作,则先统计word1的前 i 个字符匹配word2的前 j+1 个字符(因为word1添了一个字符)的方法数。即add=dp[i][j+1];
如果是删除操作,则先统计word1的前 i +1个字符匹配word2的前 j 个字符(因为word1删了一个字符)的方法数。即delete=dp[i+1][j];
如果是替换操作,则统计word1的前 i 个字符匹配word2的前 j 个字符的方法数。即replace=dp[i][j];
最后,dp[i+1][j+1]=min(add,delete,replace)+1
class Solution(object):
def minDistance(self, word1, word2):
"""
:type word1: str
:type word2: str
:rtype: int
"""
m = len(word1)
n = len(word2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m + 1):
dp[i][0] = i
for i in range(n + 1):
dp[0][i] = i
for i in range(m):
for j in range(n):
if word1[i] == word2[j]:
dp[i + 1][j + 1] = dp[i][j]
else:
add = dp[i][j + 1]
delete = dp[i + 1][j]
replace = dp[i][j]
dp[i + 1][j + 1] = min(add, delete, replace) + 1
return dp[m][n]
1.4 打家劫舍(198)
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
思路:
考虑前i项的结果dp[i]时,
当i = 1, 返回dp[0] = nums[0]
当i = 2, 返回dp[1] = max(nums[0], nums[1])
当i = 3, 分为偷3号房屋和不偷3号房屋,
偷的情况下, 2号就不能偷了,结果为nums[2] + dp[0]
不偷的情况下,结果为dp[1]
所以返回dp[2] = max(dp[0] + nums[2], dp[1])
…
以此类推,dp[i] = max(dp[i-2] + nums[i], dp[i-1])
class Solution(object):
def rob(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if not nums:
return 0
dp = [nums[0]]
if len(nums) == 1:
return dp[0]
dp.append(max(nums[0],nums[1]))
if len(nums) == 2:
return dp[1]
for i in range(2,len(nums)):
dp.append(max(dp[i-2] + nums[i],dp[i-1]))
return dp[i]
leetcode中的动态规划(dp)问题:85 ,91,97,120,131,132,139,140,150。
2. 排序查找
2.1 搜索二维矩阵(74)
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
示例 1:
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 3
输出: true
示例 2:
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 13
输出: false
解法:和剑指offer上第一题相同。
class Solution(object):
def searchMatrix(self, matrix, target):
"""
:type matrix: List[List[int]]
:type target: int
:rtype: bool
"""
if not matrix or not matrix[0]:
return False
row = len(matrix)-1
col = len(matrix[0])-1
i = row
j = 0
while i >= 0 and j <= col:
if target < matrix[i][j]:
i -= 1
elif target > matrix[i][j]:
j += 1
else:
return True
return False
2.2 寻找两个有序数组的中位数(4)
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
解法:先进行排序,再进行中位数的计算
class Solution(object):
def findMedianSortedArrays(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: float
"""
both = sorted(nums1 + nums2) # 合并两个数组
l = len(both)
# 计算中位数
if l % 2 == 0:
return sum(both[l//2-1:l//2+1]) / 2.0
else:
return both[l//2]
2.3 颜色分类(荷兰国旗)
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意:
不能使用代码库中的排序函数来解决这道题。
示例:
输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
解法1: 计数排序的两趟扫描算法
class Solution(object):
def sortColors(self, nums):
"""
:type nums: List[int]
:rtype: None Do not return anything, modify nums in-place instead.
"""
r, w = 0, 0
for num in nums:
if num == 0:
r += 1
elif num == 1:
w += 1
for i in range(len(nums)):
if i < r:
nums[i] = 0
elif i < r + w:
nums[i] = 1
else:
nums[i] = 2
return nums
解法2: 双指针
class Solution(object):
def sortColors(self, nums):
"""
:type nums: List[int]
:rtype: None Do not return anything, modify nums in-place instead.
"""
lo, hi = 0, len(nums) - 1
i = 0
while i <= hi:
x = nums[i]
if x == 0:
nums[lo], nums[i] = nums[i], nums[lo]
lo += 1
i += 1
elif x == 2:
nums[hi], nums[i] = nums[i], nums[hi]
hi -= 1
else:
i += 1
3. 图搜索
3.1 括号生成(22)
给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
例如,给出 n = 3,生成结果为:
[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]
思路:
回溯+剪枝。
剪枝原则:
放了左括号才能放右括号,left 代表还能放左括号的个数,right 代表还能放右括号的个数。
class Solution(object):
def generate(self, temp, left, right, result):
if (left == 0 and right == 0):
result.append(temp)
return
if (left > 0):
self.generate(temp + "(", left - 1, right, result)
if (left < right):
self.generate(temp + ")", left, right - 1, result)
def generateParenthesis(self, n):
"""
:type n: int
:rtype: List[str]
"""
result = []
self.generate("", n, n, result)
return result
解法2
class Solution(object):
def generateParenthesis(self, n):
"""
:type n: int
:rtype: List[str]
"""
res = []
def dfs(tmp, left, right):
if len(tmp) == 2 * n:
res.append(tmp)
if left:
dfs(tmp + "(", left - 1, right)
if right > left:
dfs(tmp + ")", left, right - 1)
dfs("", n, n)
return res
3.2岛屿数量(200)
给定一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。
示例 1:
输入:
11110
11010
11000
00000
输出: 1
示例 2:
输入:
11000
11000
00100
00011
输出: 3
解法
class Solution(object):
def numIslands(self, grid):
"""
:type grid: List[List[str]]
:rtype: int
"""
if not grid or not grid[0]:
return 0
m, n = len(grid), len(grid[0])
visited = [[0 for j in range(n)] for i in range(m)]
# print visited
dx = [1, -1, 0, 0]
dy = [0, 0, 1, -1]
res = 0
def dfs(x0, y0):
for k in range(4):
x = x0 + dx[k]
y = y0 + dy[k]
# print x, y
if 0<= x < m and 0 <= y < n and grid[x][y] == '1' and visited[x][y] ==0:
visited[x][y] = 1
dfs(x, y)
for i in range(m):
for j in range(n):
if grid[i][j] == '1' and visited[i][j] == 0:
res += 1
visited[i][j] = 1
dfs(i, j)
# print visited
return res
3.3 单词拆分(139)
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 1:
输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以被拆分成 “leet code”。
示例 2:
输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
输出: true
解释: 返回 true 因为 “applepenapple” 可以被拆分成 “apple pen apple”。
注意你可以重复使用字典中的单词。
思路:
动态规划,用一个数组record记录切割字符串s时下刀的下标。
每次刷新最远可以拆分的下标,最后判断一下是不是整个字符串都可以被拆分。
class Solution(object):
def wordBreak(self, s, wordDict):
"""
:type s: str
:type wordDict: List[str]
:rtype: bool
"""
record = [0]#一开始从开头开始找
for j in range(len(s) + 1):
for i in record:#在之前每一种找法的基础上找
if s[i : j] in wordDict: #找到一种可行的分法,说明最远可以拆分到j
record.append(j)
break
# print record
return record[-1] == len(s)
4. 数组
4.1 寻找重复数(287)
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例 1:
输入: [1,3,4,2,2]
输出: 2
示例 2:
输入: [3,1,3,4,2]
输出: 3
思路
二分查找,已知所有数字的范围是1-n, 就可以把left设为1, right 设为n,mid设为left 和right的中间值,
每次循环,用count记录一下有多少个小于等于mid的值,
如果count <= mid,就代表重复的数字应该不会落在mid左侧的区间内,于是更新left;
反之, 就更新right。
class Solution(object):
def findDuplicate(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
left, right = 1, len(nums) - 1
while(left < right):
mid = left + (right - left) /2
count = 0
for num in nums:
if num <= mid:
count += 1
# print mid, count
if count <= mid:
left = mid + 1
else:
right = mid
return left
5. 字符串
5.1 无重复字符的最长子串(3)
思路:
问子串可以考虑用双指针法 + sliding window解题,start, end可以夹出一个window。
用end线性遍历每个数组,用record记录下每个字母出现的最新的下标。
当遇到一个新元素char在record里没有记录时,代表它没有跟window里的字母重复。
如果在record里有记录,说明start需要刷新, 取当前start和record[char]里的最大值作为新的start即可。
class Solution(object):
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
record = dict()#记录每个字母最后一次出现的下标,key是字母,val是下标
res, start = 0, 0
for end in range(len(s)):
if s[end] in record:#出现过
start = max(start, record[s[end]] + 1)
record[s[end]] = end #刷新最新下标
res = max(res, end - start + 1) #刷新res
return res