[LeetCode] 동적 프로그래밍의 원리와 프로그래밍 실습

(Datawhale은 8 월에 연구를 위해 팀을 이루어) 제가 본 많은 경험 중 동적 프로그래밍의 빈도가 매우 높습니다. 동적 프로그래밍의 아이디어를 습득하면 실제로 많은 문제를 해결할 수 있습니다. 이 기사에서는 주로 동적 프로그래밍의 원리를 소개하고 동적 프로그래밍으로 해결할 수있는 LeetCode의 많은 문제를 해결합니다.

【LeetCode】 일련의 기사

LeetCode의 분할과 정복 알고리즘의 원리 및 프로그래밍 연습 20,200,819 위에 간행 의 원리 및 프로그래밍 연습
LeetCode의 동적 프로그래밍 방법은 20,200,822에 게시

1. 동적 프로그래밍의 원리

  • 주요 생각

주어진 문제를 해결하기 위해 우리는 다른 부분 (즉, 하위 문제)을 해결 한 다음 하위 문제의 솔루션을 기반으로 원래 문제의 솔루션을 얻어야합니다. 동적 프로그래밍은 종종 재귀 문제를 최적화하는 데 사용됩니다. 반복적 인 방법을 사용하여 동일한 하위 문제를 많이 해결하는 경우 동적 프로그래밍 아이디어는 계산량을 줄일 수 있습니다. 동적 프로그래밍 방법은 각 하위 문제를 한 번만 해결하고 자연 가지 치기 기능을 가지고있어 계산량을 줄입니다. 주어진 하위 문제의 해를 계산하면 다음 번에 동일한 하위 문제 해결이 필요하도록 기억되고 저장됩니다. 미터를 직접 확인하십시오.

  • 단계
  1. 동적 프로그래밍 상태 결정

  2. 상태 전이 방정식 작성 (상태 전이 테이블 그리기)

  3. 초기화 조건 고려

  4. 출력 상태 고려

  5. 시간 및 공간 복잡성 최적화 고려 (보너스)


둘째, 실제 프로그래밍

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]))

추천

출처blog.csdn.net/xylbill97/article/details/108169009