动态规划
最大连续子数组
任何一道动态规划 最为关键的是如何找到状态转移方程 (也就是最终问题的解 如何通过一步步其它的解得出来)
这题得出来的状态转移方程就是 f(n) = nums[i] + f(n-1)
其中n是假设该最大子数组的最右下标为 n
nums= [-2,1,-3,4,-1,2,1,-5,4] # 最大子数组 动态规划解法
n = len(nums)
# dp = [-99999 for i in range(n)]
dp = []
for i in range(n):
if i == 0: # 从左往右遍历 因此第一个数一定要写进 dp数组中
dp.append(nums[i])
else:
if (dp[i-1]<=0): # 后续 dp的值取决于前面那个值的大小 如果小于等于0 那就没有加上他们的必要了
dp.append(nums[i])
else:
dp.append(nums[i]+dp[i-1]) # 反之需要加上 这个正数代表着 不管前面是怎样的排列 最终能给我带来正数的加
dp = sorted(dp)
print(dp[n-1])
第N个泰波那契数
题解
class Solution:
def tribonacci(self, n: int) -> int:
if n == 0:
return 0
elif n == 1:
return 1
elif n == 2:
return 1
dp = [0,1,1]
for i in range(3,n+1):
dp.append(dp[i-1]+dp[i-2]+dp[i-3])
return dp[n]
爬楼梯
这题的关键点在于 需要想明白 第n阶楼梯的总方法 是 等于 n-1加上 n-2方法的总和
这样就能写出 状态转移方程(最重要的部分) f(n) = f(n-1) + f(n-2)
class Solution:
def climbStairs(self, n: int) -> int:
if n == 1:
return 1
elif n == 2:
return 2
dp = [1,2]
for i in range(2,n):
dp.append(dp[i-1]+dp[i-2])
return dp[n-1]
使用最小花费爬楼梯
像这种爬楼梯问题 题目限定一次只能爬一阶 或者两阶的问题
设上到第n阶梯最小花费为 F(n) ,则F(n) = min(F(n-1),F(n-2)) 因为上到这一阶肯定是前面
的最小值来的 所以就可以写出 转移方程
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
dp = [cost[0], cost[1]] # 初始dp
n = len(cost)
for i in range(2,n):
dp.append(min(dp[i-1],dp[i-2])+cost[i]) # 代价是前面的最小加上这一阶的
return min(dp[n-1],dp[n-2])
打家劫舍
1. 偷窃第 k 间房屋,那么就不能偷窃第 k−1 间房屋,偷窃总金额为前 k−2 间房屋的最高总金额与第 k 间房屋的金额之和。
2. 不偷窃第 k 间房屋,偷窃总金额为前 k−1 间房屋的最高总金额。
3. 设dp[i]为前i间房屋能偷到的最大金额 那么就有一下状态转移方程:
dp[i] = max(dp[i-2]+nums[i], dp[i-1])
边界条件为:
{
dp[0]=nums[0] 只有一间房屋,则偷窃该房屋
dp[1]=max(nums[0],nums[1]) 只有两间房屋,选择其中金额较高的房屋进行偷窃
}
class Solution:
def rob(self, nums: List[int]) -> int:
n = len(nums)
if n == 1:
return nums[0]
dp = [0] * n
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for i in range(2, n):
dp[i] = max(nums[i]+dp[i-2], dp[i-1])
return dp[n-1]
双指针
两数之和 ||
题目分析:
因为给定的数组是有序(正序)数组 且要找的两个数一定是一大一小
假设要找的数是 nums[i] nums[j] i<j
这样就可以用双指针 左指针起始在0 右指针起始在 len(nums)-1 通过不断对比 target的值 将 指针缩小范围直至确认 i j
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
low, high = 0, len(numbers) - 1 # 确定两个指针的位置
while low < high: # 指针移动条件
total = numbers[low] + numbers[high]
if total == target:
return [low + 1, high + 1] # // 返回下标从1开始
elif total < target:
low += 1 # Python中没有++ --的用法
else:
high -= 1
return [-1, -1]
反转字符串
这里是通过元组不断 交换 头尾指针的数值
class Solution:
def reverseString(self, s: List[str]) -> None:
"""
Do not return anything, modify s in-place instead.
"""
r = len(s)-1
l = 0
while (l<r):
s[l],s[r] = s[r], s[l]
l += 1
r -= 1
print(s)
# s = list(s[::-1])
# print(s[::-1])
移动零
题目分析
这里也是采用两个指针 左右指针 两个指针的起始位置都是 0
往右遍历 不同的是:
左指针左边的数都是处理好的数 右指针负责主要遍历
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
left = right = 0 # 初始化左指针和右指针均指向列表的开头
while right < n:
if nums[right] != 0: # 左指针应当指向为0的元素,如果右指针不为0就将左右指针指向的元素互换
nums[left],nums[right] = nums[right],nums[left]
left += 1
right += 1
两个数组的交集 ||
这题我用到的思路也是双指针 题目中的序列是未排序的
那就先将其排序完后 在分别遍历两个数组 一致就放入新的数组中 因为是有序数组了 因此只需要一直向右遍历就可
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
nums1 = sorted(nums1)
nums2 = sorted(nums2)
n = len(nums1)
m = len(nums2)
i = j = 0
dp = []
while i<n and j<m:
if nums1[i]<nums2[j]:
i += 1
elif nums1[i]>nums2[j]:
j += 1
else:
dp.append(nums1[i])
i +=1
j +=1
return dp
寻找两个正序数组的中位数
题目分析
这题因为给的 两个数组是有序的 因此我们可以采用左右数组排序法(类似于归并排序的合并数组)
将两个有序数组合并成一个数组 因为是求中位数
因此我们不需要合并完 只需要合并到刚好能求出中位数即可
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
n = len(nums1)
m = len(nums2)
l = r = i = 0 # l用来当作nums1的下标 r当作是nums2的下标来遍历 i是计数
end = int((n+m)/2)
fisrt = second = 0 # 因为中位数 最多也只需要两个数来求和 因此这里使用滚动数组
while (i<=end): # 只需遍历到end即可刚好求出中位数
if l>=n: # 当一边数组已经遍历完了 那就直接取另一边的就行
fisrt , second = second , nums2[r] # 滚动数组
i += 1
r += 1
continue
if r>=m:
fisrt , second = second , nums1[l]
i += 1
l += 1
continue
if (nums1[l]<nums2[r]): # 判断 小的放入滚动数组中 实现排序滚动数组
fisrt , second = second , nums1[l]
i += 1
l += 1
else:
fisrt , second = second , nums2[r]
i += 1
r += 1
if (n+m)%2 == 0: # 判断 如果数组个数为偶数 那么就需要两个数来求出中位数 反之一个就行
return (fisrt+second)/2
else:
return second
滑动窗口
无重复字符的最长子串
题目分析:
滑动窗口模板 : 需要两个指针 左右指针 l r
l指针负责控制这个“窗口”在序列中的起始位置 (窗口可以是一个集合 或者字符串 或者数组 具体看题目)
r指针负责遍历 题目给出的序列
所以代码开头就会有一个遍历 用r指针遍历完对象序列 你可以while r<len(指定序列)
每次遍历 需要看看新增的元素是否满足该“窗口” 满足就放进去 不满足就将窗口最左边的移除掉
且l 右移一位
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
l = r = 0 # 左右指针
n = len(s)
max_ = ans = 0 # max_来统计窗口最大长度 ans是负责统计窗口的实时长度
ss = '' # 窗口
while (r<n): # 用r指针 从左到右遍历目标序列 n为序列长度
while s[r] in ss: # 该情况就是遍历到的元素不满足窗口 需要将窗口左侧的元素剔除 因为窗口的长度变换 需要实时更新ans的大小 且将l指针右移
ss = ss[1:] # 剔除窗口最左边元素
l += 1 # 右移左指针
ans -= 1 # 将长度-1
ss += s[r] # 满足题目的要求 因此放入窗口中
r += 1 # 更新右指针
ans += 1 # 更新目标序列长度
max_ = max(max_,ans) # max_永远记录最大的那个长度
print(max_)
return max_