(Datawhale은 8 월에 연구를 위해 팀을 이루어) 제가 본 많은 경험 중 동적 프로그래밍의 빈도가 매우 높습니다. 동적 프로그래밍의 아이디어를 습득하면 실제로 많은 문제를 해결할 수 있습니다. 이 기사에서는 주로 동적 프로그래밍의 원리를 소개하고 동적 프로그래밍으로 해결할 수있는 LeetCode의 많은 문제를 해결합니다.
【LeetCode】 일련의 기사
LeetCode의 분할과 정복 알고리즘의 원리 및 프로그래밍 연습 20,200,819 위에 간행 의 원리 및 프로그래밍 연습
LeetCode의 동적 프로그래밍 방법은 20,200,822에 게시
기사 디렉토리
1. 동적 프로그래밍의 원리
- 주요 생각
주어진 문제를 해결하기 위해 우리는 다른 부분 (즉, 하위 문제)을 해결 한 다음 하위 문제의 솔루션을 기반으로 원래 문제의 솔루션을 얻어야합니다. 동적 프로그래밍은 종종 재귀 문제를 최적화하는 데 사용됩니다. 반복적 인 방법을 사용하여 동일한 하위 문제를 많이 해결하는 경우 동적 프로그래밍 아이디어는 계산량을 줄일 수 있습니다. 동적 프로그래밍 방법은 각 하위 문제를 한 번만 해결하고 자연 가지 치기 기능을 가지고있어 계산량을 줄입니다. 주어진 하위 문제의 해를 계산하면 다음 번에 동일한 하위 문제 해결이 필요하도록 기억되고 저장됩니다. 미터를 직접 확인하십시오.
- 단계
-
동적 프로그래밍 상태 결정
-
상태 전이 방정식 작성 (상태 전이 테이블 그리기)
-
초기화 조건 고려
-
출력 상태 고려
-
시간 및 공간 복잡성 최적화 고려 (보너스)
둘째, 실제 프로그래밍
2.1 예 : 질문 300의 가장 긴 오름차순 하위 시퀀스
- 제목 설명
정렬되지 않은 정수 배열이 주어지면 가장 긴 오름차순 하위 시퀀스의 길이를 찾으십시오.
- 문제 해결 아이디어
1 단계 : 동적 프로그래밍 상태 확인
이 토픽은 1 차원 배열 dp를 사용하여 전이 상태를 저장할 수 있습니다. dp [i]는 nums [i]로 끝나는 가장 긴 증가하는 하위 시퀀스의 길이로 정의 할 수 있습니다.
2 단계 : 좋은 상태 전이 방정식 작성
수학적 귀납적 사고를 사용하여 정확한 상태 방정식을 작성합니다. dp [i]의 현재 길이를 dp [i]와 비교하여 새로운 하위 시퀀스 길이를 생성합니다. j를 사용하여 i보다 작은 모든 그룹의 인덱스를 나타냅니다. 다음 코드 공식으로 표현할 수 있습니다.
for i in range(len(nums)):
for j in range(i):
if nums[i]>nums[j]:
dp[i]=max(dp[i],dp[j]+1)
3 단계 : 초기 조건 고려
경계 값 고려는 주로 세 부분으로 나뉩니다.
(1) 전체 dp 배열의 초기 값; (2) 2 차원 dp 배열의 위치 i = 0 및 j = 0
(3) dp 저장 상태의 길이는 전체 어레이의 길이 또는 어레이의 길이 + 1이므로 특별한주의가 필요합니다.
보충 : 일반적으로 사용되는 몇 가지 Python 초기화 방법
# 产生全为1且长度为n的数组
dp=[1 for _ in range(n)]
dp=[1]*n
# 产生全为0,长度为m,宽度为n的二维矩阵
dp=[[0 for _ in range(n)] for _ in range(m)]
dp=[[0]*n for _ in range(m)]
4 단계 : 출력 상태 고려
필요한 배열 값 :
(1) 일반적으로 2 차원 dp 문제에 해당하는 dp 배열의 마지막 값을 출력으로 반환합니다.
(2) 일반적으로 레코드 최대 값 문제에 해당하는 dp 배열에서 가장 큰 수를 반환합니다.
(3) 일반적으로 Maxval = max (Maxval, dp [i]) 형식으로 저장된 최대 값을 반환합니다.
이 질문에 대한 동적 프로그래밍 방법의 표준 답변
class Solution(object):
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums:return 0 # 判断边界条件
dp=[1]*len(nums) # 初始化dp数组状态
for i in range(len(nums)):
for j in range(i):
if nums[i]>nums[j]: # 根据题目所求得到状态转移方程
dp[i]=max(dp[i],dp[j]+1)
return max(dp) # 确定输出状态
5 단계 : 시간 및 공간 복잡성 최적화 고려 (보너스)
dp 목록을 순회하는 이전 방법에는 O (N) O (N)이 필요합니다.O ( N ) , 각 dp [i]를 계산하려면O (N) O (N)이 필요합니다.O ( N ) 시간이므로 총 복잡도는O (N 2) O (N ^ 2)O ( N2 ). dp 목록을 순회하는 시간 복잡도는 줄일 수 없지만 각 라운드에서 [0, i]의 dp [i] 요소를 순회하는 시간 복잡도는 설계 상태 정의에 의해 고려 될 수 있으므로 전체 dp는 이분법을 사용할 수있는 정렬 된 목록입니다. 시간 복잡도를O (N log N) O (NlogN)로줄이려면O ( N l o g N )。
템플릿 요약 :
위의 방법에 따라 템플릿으로 요약하고 실제 전투를 수행 할 수 있습니다.
for i in range(len(nums)):
for j in range(i):
dp[i]=最值(dp[i], dp[j], ...)
2.2 문제 674 : 가장 긴 연속 증가 시퀀스
- 제목 설명
정렬되지 않은 정수 배열이 주어지면 가장 길고 연속적으로 증가하는 시퀀스를 찾습니다.
- 표준 답변
def findLengthOfLCIS(self, nums: List[int]) -> int:
if not nums:return 0 # 判断边界条件
dp=[1]*len(nums) # 初始化dp数组状态
# 注意需要得到前一个数,所以从1开始遍历,否则会超出范围
for i in range(1,len(nums)):
if nums[i]>nums[i-1]: # 根据题目所求得到状态转移方程
dp[i]=dp[i-1]+1
else:
dp[i]=1
return max(dp) # 确定输出状态
2.3 질문 5의 가장 긴 회문 부분 문자열
- 제목 설명
문자열 s가 주어지면 s에서 가장 긴 회문 부분 문자열을 찾습니다. s의 최대 길이는 1000이라고 가정 할 수 있습니다.
- 표준 답변
def longestPalindrome(self, s: str) -> str:
length=len(s)
if length<2: # 判断边界条件
return s
dp=[[False for _ in range(length)]for _ in range(length)] # 定义dp状态矩阵
# 定义初试状态,这步其实可以省略
# for i in range(length):
# dp[i][i]=True
max_len=1
start=0 # 后续记录回文串初试位置
for j in range(1,length):
for i in range(j):
# 矩阵中逐个遍历
if s[i]==s[j]:
if j-i<3:
dp[i][j]=True
else:
dp[i][j]=dp[i+1][j-1]
if dp[i][j]: # 记录位置,返回有效答案
cur_len=j-i+1
if cur_len>max_len:
max_len=cur_len
start=i
return s[start:start+max_len]
2.4 질문 516의 가장 긴 회문 하위 시퀀스
- 제목 설명
문자열 s가 주어지면 s에서 가장 긴 회문 부분 문자열을 찾습니다. s의 최대 길이는 1000이라고 가정 할 수 있습니다.
- 표준 답변
def longestPalindromeSubseq(self, s: str) -> int:
n=len(s)
dp=[[0]*n for _ in range(n)] # 定义动态规划状态转移矩阵
for i in range(n): # 初始化对角线,单个字符子序列就是1
dp[i][i]=1
for i in range(n,-1,-1): # 从右下角开始往上遍历
for j in range(i+1,n):
if s[i]==s[j]: # 当两个字符相等时,直接子字符串加2
dp[i][j]= dp[i+1][j-1]+2
else: # 不相等时,取某边最长的字符
dp[i][j]=max(dp[i][j-1],dp[i+1][j])
return dp[0][-1] # 返回右上角位置的状态就是最长
2.5 문제 72 거리 편집
- 제목 설명
두 단어 word1과 word2가 주어지면 word1을 word2로 변환하는 데 사용되는 최소 피연산자 수를 계산합니다.
- 표준 답변
def minDistance(self, word1, word2):
# m,n 表示两个字符串的长度
m=len(word1)
n=len(word2)
# 构建二维数组来存储子问题
dp=[[0 for _ in range(n+1)] for _ in range(m+1)]
# 考虑边界条件,第一行和第一列的条件
for i in range(n+1):
dp[0][i]=i # 对于第一行,每次操作都是前一次操作基础上增加一个单位的操作
for j in range(m+1):
dp[j][0]=j # 对于第一列也一样,所以应该是1,2,3,4,5...
for i in range(1,m+1): # 对其他情况进行填充
for j in range(1,n+1):
if word1[i-1]==word2[j-1]: # 当最后一个字符相等的时候,就不会产生任何操作代价,所以与dp[i-1][j-1]一样
dp[i][j]=dp[i-1][j-1]
else:
dp[i][j]=min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1 # 分别对应删除,添加和替换操作
return dp[-1][-1] # 返回最终状态就是所求最小的编辑距离
2.6 문제 198
- 제목 설명
당신은 거리를 따라 집을 훔치려는 전문 도둑입니다. 각 방에는 일정량의 현금이 숨겨져 있습니다. 도난에 영향을 미치는 유일한 제약은 인접한 주택에 상호 연결된 도난 방지 시스템이 설치되어 있다는 것입니다. 같은 밤에 두 개의 인접한 주택이 도둑에 의해 침입하면 시스템이 자동으로 경고합니다. .
- 표준 답변
def rob(self, nums):
if(not nums): # 特殊情况处理
return 0
if len(nums)==1:
return nums[0]
n=len(nums)
dp=[0]*n # 初始化状态转移数组
dp[0]=nums[0] # 第一个边界值处理
dp[1]=max(nums[0],nums[1]) # 第二个边界值处理
for i in range(2,n):
dp[i]=max(dp[i-2]+nums[i],dp[i-1]) # 状态转移方程
return dp[-1]
2.7 문제 213 : 집 강도 II
- 제목 설명
당신은 거리를 따라 집을 훔치려는 전문 도둑입니다. 각 방에는 일정량의 현금이 숨겨져 있습니다. 도난에 영향을 미치는 유일한 제약은 인접한 주택에 상호 연결된 도난 방지 시스템이 설치되어 있다는 것입니다. 같은 밤에 두 개의 인접한 주택이 도둑에 의해 침입하면 시스템이 자동으로 경고합니다. .
- 표준 답변
def rob(self, nums: List[int]) -> int:
if not nums:
return 0
elif len(nums)<=2:
return max(nums)
def helper(nums):
if len(nums)<=2:
return max(nums)
dp=[0]*len(nums)
dp[0]=nums[0]
dp[1]=max(nums[0],nums[1])
for i in range(2,len(nums)):
dp[i]=max(dp[i-1],dp[i-2]+nums[i])
return dp[-1]
return max(helper(nums[1:]),helper(nums[:-1]))