동적 프로그래밍 입문: 피보나치 수열 모델 및 다중 상태(C++)

동적 프로그래밍에 대한 간략한 소개

    동적 프로그래밍(DP라고도 하는 동적 프로그래밍)은 다단계 의사 결정 문제를 해결하기 위한 알고리즘 아이디어입니다. 문제를 여러 단계로 나누고 중간 결과를 저장하여 이중 계산을 방지함으로써 효율성을 향상시킵니다.


동적 프로그래밍의 문제 해결 단계는 일반적으로 다음 단계로 나뉩니다.

  1. 상태 표현에 대한 생각, dp 테이블 생성(강조)
  2. 상태 전이 방정식 분석(강조)
  3. 초기화
  4. 양식 작성 순서 결정
  5. 반환 값 결정

피보나치 수열 모델

1. N번째 타이보나치 수(단순)

링크: N번째 티보나치 수

  • 주제 설명

여기에 이미지 설명을 삽입하세요

  • 질문 단계
  1. 상태 표시

동적 프로그래밍 문제에 직면하면 일반적으로 두 가지 상태 표현이 있습니다.

  1. 특정 위치에서 시작하여...
  2. 특정 위치에서 끝나는...

우리는 일반적으로 첫 번째 유형의 표현을 우선시하지만, 첫 번째 유형을 해결할 수 없는 경우 두 번째 유형을 고려합니다.

여기에 이미지 설명을 삽입하세요

  1. 상태 전이 방정식
    이 주제는 상태 전이 방정식을 직접적으로 알려줍니다. dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]

  2. 초기화
    타이보나치 수의 0번째, 1번째, 2번째 위치 는 특수 하고 상태전이 방정식을 만족하지 않으므로 이 세 위치를 0, 1, 1로 초기화 해야 합니다.

  3. 양식을 채우는 순서는
    현재 상태를 채울 때 필요한 상태가 계산되었음을 보장하며 양식을 채우는 순서는 분명히 왼쪽에서 오른쪽입니다.

  4. 반환 값
    상태 표현에 따르면 요청이 n번째 요청이라고 가정하면 반환 값은 dp[n]이어야 합니다.

  • 암호
class Solution {
    
    
public:
    int tribonacci(int n) 
    {
    
    
        //对于第0、1、2单独处理
        if(n == 0) 
            return 0;
        if(n == 1 || n == 2)
            return 1;
        //dp[i]:第i个泰波那契数
        vector<int> dp(n + 1);
        dp[0] = 0; dp[1] = 1; dp[2] = 1; 
           
        for(int i = 3; i < n + 1; i++)
        {
    
    
            dp[i] = dp[i-1] + dp[i-2] + dp[i-3];
        }
        return dp[n];
        //空间复杂度:O(N)
        //时间复杂度:O(N)
    }
};

//不知道大家有没有发现向后填表的过程其实只需要前置的3个状态
//其余的状态都是多余的,我们可以用有限的变量来保存这些状态,这样就实现了空间优化
//这种优化方式被称为“滚动数组”
//经过优化原O(N)->O(1) O(N^2)->O(N)
//但这并不是动态规划讲解的要点,所以我只会把两种优化情况的代码给出

// class Solution {
    
    
// public:
//     int tribonacci(int n) 
//     {
    
    
//         if(n == 0) 
//             return 0;
//         if(n == 1 || n == 2)
//             return 1;

//         int t1 = 0;
//         int t2 = 1;
//         int t3 = 1;
//         int ret = 0;
           
//         for(int i = 3; i < n + 1; i++)
//         {
    
    
//             ret = t1 + t2 + t3;
//             t1 = t2;
//             t2 = t3;
//             t3 = ret;
//         }
//         return ret;
//     }
// };

2. 3단계 질문(간단)

링크: 3단계 질문

  • 주제 설명
    여기에 이미지 설명을 삽입하세요

  • 질문 단계

  1. 상태 표시
    여기에 이미지 설명을 삽입하세요

  2. 상태 전이 방정식
    i 단계에 도달하는 것은 i - 3 단계, i - 2, i - 1 단계에 먼저 도달하는 것으로 변환될 수 있고, 그 3개를 더한 결과가 되므로 상태 전이 방정식은 dp[i] = dp[i - 1] + dp [i - 2] + dp[i - 3].

  3. 초기화
    폼의 채우기가 경계를 초과하지 않도록 1, 2, 3레벨에 도달하도록 메소드를 초기화합니다 .

  4. 양식을 채우는 순서는
    현재 상태를 채울 때 필요한 상태가 계산되었는지 확인하고 양식을 채우는 순서는 왼쪽에서 오른쪽입니다 .

  5. 반환 값
    상태 표현에 따르면 요구 사항이 n차라고 가정하면 반환 값은 dp[n]이어야 합니다.

  • 암호
class Solution {
    
    
public:
    int waysToStep(int n) 
    {
    
    
        //1、2、3阶特殊处理
        if(n == 1) return 1;
        if(n == 2) return 2;
        if(n == 3) return 4;

        //dp[i]表示到达i阶的方法数
        vector<int> dp(n+1); //多开一个空间,可以让下标和层数对应
        dp[1] = 1; dp[2] = 2; dp[3] = 4;
        const int mod = 1e9 + 7;  //有可能超出,需要取模

        for(int i = 4; i < n + 1; i++)
        {
    
    
            dp[i] = ((dp[i-1] + dp[i-2]) % mod + dp[i-3]) % mod;
        }
        return dp[n];
        //时间复杂度:O(N)
        //空间复杂度:O(N)
    }
};

3. 최소한의 비용으로 계단 오르기(쉬움)

링크: 최소 비용으로 계단 오르기

  • 주제 설명
    여기에 이미지 설명을 삽입하세요

  • 질문 단계

  1. 상태 표현 이
    질문의 아이디어는 질문 2의 아이디어와 매우 유사합니다. 끝 단계 n에 도달하려면 단계 n-1에서 한 단계, n-2 단계에서 끝까지 두 단계를 거쳐 다음 단계를 선택할 수 있습니다. 비용이 가장 낮은 것(현재 단계에서 탈퇴하려면 탈퇴 비용을 지불해야 함) , n-1, n-2 레벨에 도달하기 위한 최소 비용은 n-1, n-2 레벨로 다음과 같이 분석할 수 있습니다. 끝점 등. 끝에 도달하는 과정은 각 층에 도달하기 위한 최소 비용이 필요하며 이를 dp 테이블에 저장할 수 있으며 dp[i]는 아래 첨자 i step 에 도달하는 데 필요한 최소 비용을 나타냅니다 .

  2. 상태 전이 방정식
    i 레벨에 도달하는 데 필요한 최소 비용은 min(레벨 i에 도달하는 데 드는 최소 비용 - 1 + 이 레벨에서 벗어나는 데 필요한 비용, i - 레벨 2에 도달하는 최소 비용 + 이 레벨에서 벗어나는 데 드는 비용)으로 변환할 수 있습니다. , 따라서 상태 전환 방정식은 다음과 같습니다. dp[i] = min(dp[i - 1] + 비용[i - 1], dp[i - 2] + 비용[i - 2]).

  3. 초기화
    전달 방정식을 통해 특정 상태를 업데이트하려면 두 개의 선행 상태가 필요함을 알 수 있으며, 테이블이 경계를 넘지 않도록 하기 위해 0단계와 1단계로 가는 최소 비용을 별도로 처리합니다 .

  4. 양식을 채우는 순서는
    현재 상태를 채울 때 필요한 상태가 계산되었는지 확인하고 양식을 채우는 순서는 왼쪽에서 오른쪽입니다 .

  5. 반환 값
    상태 표현에 따르면 배열에 n개의 요소가 있다고 가정하면(끝점은 n차) 반환 값은 dp[n]이어야 합니다.

  • 암호
class Solution {
    
    
public:
    int minCostClimbingStairs(vector<int>& cost) 
    {
    
          
        //dp[i] 表示到这一层的最小花费
        int n = cost.size();
        vector<int> dp(n + 1);
        //一开始就可以在0或1阶,花费为0,vector默认给0,不用处理

        for(int i = 2; i < n + 1; i++)
        {
    
    
            dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]);
        }

        return dp[n];
        //空间复杂度:O(N)
        //时间复杂度:O(N)
    }
};

// //第二种写法:反着来,以某个位置为起点,……
// class Solution {
    
    
// public:
//     int minCostClimbingStairs(vector<int>& cost) 
//     {      
//         //dp[i]:这一层为起点,到终点的最低花费
//         int n = cost.size();
//         vector<int> dp(n + 1);
//         dp[n] = 0;
//         dp[n - 1] = cost[n - 1];
//         for(int i = n - 2; i >= 0; i--)
//         {            
//             dp[i] = min(dp[i + 1] + cost[i], dp[i + 2] + cost[i]);
//         }

//         return min(dp[0], dp[1]);
//     }
// };

4. 디코딩 방법(중간)

링크: 디코딩 방법

  • 주제 설명
    여기에 이미지 설명을 삽입하세요

  • 질문 단계

  1. 상태 표시
    여기에 이미지 설명을 삽입하세요

  2. 상태 표현
    첫 번째 비트를 제외하고 각 위치에는 개별 디코딩과 결합 디코딩의 두 가지 모드가 있습니다. 위치 n의 상태 전이 방정식은 다음과 같습니다. dp[n] = dp[n - 1] (성공적인 디코딩 단독) + dp[n - 2] (공동 디코딩 성공)

  3. 초기화
    상태 전이 방정식에 따르면 특정 위치의 상태에는 두 개의 사전 상태가 필요하며 경계를 넘지 않기 위해서는 첫 번째 위치와 두 번째 위치를 별도로 처리해야 합니다. 그러나 위의 분석 과정을 관찰하면 다음과 같은 결과를 얻을 수 있습니다. 두 번째 위치가 다른 위치와 동일하다는 것을 확인합니다. 두 가지 디코딩 가능성이 있습니다. dp 테이블 앞에 가상 노드를 추가하고 처음에 1로 설정하여 첫 번째 위치만 처리하면 됩니다. (사진을 보시려면 사진을 보세요)

여기에 이미지 설명을 삽입하세요

  1. 양식을 채우는 순서는
    현재 상태를 채울 때 필요한 상태가 계산되었는지 확인하고 양식을 채우는 순서는 왼쪽에서 오른쪽입니다 .

  2. 반환 값
    상태 정의에 따르면 시퀀스 길이가 n이라고 가정하면 반환은 n 위치에서 끝나는 디코딩 가능한 숫자, 즉 dp[n]이어야 합니다.

  • 암호
class Solution {
    
    
public:
    int numDecodings(string s) 
    {
    
    
        int n = s.size();
        //dp[i]表示以i位置为结尾的解码可能数
        vector<int> dp(n + 1);
        //第一个位置就为0,最终结果已经是0
        if(s[0] == '0')
            return 0;
        //初始化虚拟节点和第1个位置
        dp[1] = dp[0] = 1;
        
        for(int i = 2; i < n + 1; i++)
        {
    
    
            //单独解码
            if(s[i - 1] != '0')                      
                dp[i] += dp[i-1];
            
            //联合解码(联合解码小于10说明存在前导0,无法联合解码)
            int com = (s[i - 2] - '0') * 10 + (s[i - 1] - '0');
            if(com >= 10 && com <= 26)                       
                dp[i] += dp[i-2];

            //都失败的情况是'00',最终结果已经是0,这里可不加
            //两个连续的0,后面全都是0
            if(dp[i] == 0)
                return 0;        
        }

        return dp[n];
    }
};

간단한 다중 상태

1. 집강도(중)

링크: 강도

  • 주제 설명
    여기에 이미지 설명을 삽입하세요

  • 질문 단계

  1. 상태 표현 이전의 문제 해결 경험에 따르면 상태를 위치 i에서 끝나는 최대 도난 금액으로
    표현할 수 있지만 각 위치에는 훔칠 것인지 훔치지 않을 것인지 두 가지 옵션이 있으므로 상태를 더 구체화할 수 있습니다. 상태 f는 끝을 나타냅니다 . 포지션 i로 이 포지션의 최대량을 스틸합니다. 상태 g는 포지션 i로 끝나지만 이 포지션의 최대량을 스틸하지 않음을 의미합니다.

여기에 이미지 설명을 삽입하세요

  1. 이전 분석에서 상태 전이 방정식을
    볼 수 있는데, i(f) 위치를 훔치려면 i - 1 위치에서 (g)를 훔치지 않으려면 최대 금액이 필요합니다. i 위치를 훔치지 않으면 i - 1 위치를 선택합니다. 두 가지 옵션 중 더 큰 것을 훔치거나 훔치지 않음 한쪽 면이므로 상태 전이 방정식은 다음과 같습니다:
    (1) f[i] = g[i - 1] + nums[ i ] (이 위치는 돈을 훔칠 수 있음); (2 )
    g[i] = 최대(g[i - 1 ], f[i - 1])

  2. 초기화
    상태 전이 방정식에서 현재 상태에는 이전 상태가 필요하다는 것을 알 수 있으며, 테이블이 경계를 넘지 않도록 하기 위해 첫 번째 위치는 별도로 처리됩니다: f[0] = nums[0], g[ 0] = 0 .

  3. 양식을 채우는 순서는
    현재 상태를 채울 때 필요한 상태가 계산되었는지 확인하고 양식을 채우는 순서는 왼쪽에서 오른쪽입니다 .

  4. 반환값은
    도둑으로 대체되는데, 인접 포지션을 동시에 훔칠 수 없다면 선택을 해야 하는데, 훔치는 과정에서 뒤에 있는 집의 가치를 알 수 없기 때문에 한 걸음만 나아갈 수 있다. 각 단계가 최고인지 확인하는 시간, 끝까지 도루하는 것이 최적의 결과임에 틀림없다. 배열에 n개의 요소가 있다고 가정하면 반환 값은 max(f[n - 1], g[n - 1])입니다.

  • 암호
class Solution {
    
    
public:
    int rob(vector<int>& nums) 
    {
    
    
        int n = nums.size();
        vector<int> f(n); //f[i]表示到底这个位置并偷窃的最大金额
        auto g = f;  //g[i]表示到达这个位置不偷窃的最大金额
        f[0] = nums[0]; //初始化f[0],g[0]默认0不用处理
        for(int i = 1; i < n; i++)
        {
    
    
            f[i] = g[i - 1] + nums[i];
            g[i] = max(g[i - 1], f[i - 1]);
        }
        return max(f[n - 1], g[n - 1]);
        //空间复杂度:O(N)
        //时间复杂度:O(N)
    }
};

2. 집강도 II (보통)

링크: 강도 II

  • 주제 설명
    여기에 이미지 설명을 삽입하세요

  • 질문 단계

  1. 상태 표현 이
    질문과 이전 질문의 유일한 차이점은 끝과 끝이 루프를 형성한다는 것입니다. 이전 질문의 상태 표현을 계속 사용합니다: 상태 f는 위치 i로 끝나고 이 위치의 최대량을 훔치는 것을 의미합니다. ; 상태 g는 i로 끝나는 것을 의미합니다. 포지션은 이 포지션의 최대 금액을 빼앗지 않고 종료됩니다.
    루프 형성 문제를 해결하는 가장 직접적인 방법은 루프를 해체하는 것입니다.
    여기에 이미지 설명을 삽입하세요

  2. 상태 전이 방정식은
    이전 질문과 일치하며 상태 전이 방정식은 다음과 같습니다:
    (1) f[i] = g[i - 1] + nums[ i ] (이 위치에서 도난당할 수 있는 양); (
    2 ) g[i] = 최대 (g[i - 1], f[i - 1])

  3. 초기화
    는 이전 항목과 동일합니다.

  4. 왼쪽에서 오른쪽으로 양식을 작성하세요 .

  5. 반환 값
    _rob 함수는 지정된 간격의 강도를 나타내며 반환 값은
    max(nums[0] + _rob(nums, 2, n - 2), _rob(nums, 1, n - 1)) 입니다 .

  • 암호
class Solution {
    
    
public:
    int _rob(vector<int>& nums, int left,int right) 
    {
    
    
        //区间不存在返回0
        if(left > right)
            return 0;
        int n = nums.size();
        
        vector<int> f(n);  //到这个屋子偷的最大金额
        auto g = f; //到这个屋子不偷的最大金额
        f[left] = nums[left];

        for(int i = left + 1; i <= right; i++)
        {
    
    
            f[i] = g[i - 1] + nums[i];
            g[i] = max(f[i - 1],g[i - 1]);
        }
        return max(f[right],g[right]);
    }
    int rob(vector<int>& nums) 
    {
    
    
        int n = nums.size();
        return max(nums[0] + _rob(nums, 2, n - 2), _rob(nums, 1, n - 1));
    }
};

3. 집 칠하기(중)

링크: 집을 칠해 보세요

  • 주제 설명
    여기에 이미지 설명을 삽입하세요

  • 질문 단계

  1. 상태 표현(State Representation)
    경험과 주제의 요구 사항을 바탕으로 상태를 i번째 집을 j 색상으로 칠하는 데 드는 최소 비용으로 정의할 수 있습니다.
    여기에 이미지 설명을 삽입하세요

  2. 상태 전이 방정식 상태 전이
    방정식은 (0은 빨간색, 1은 파란색, 2는 녹색)입니다.
    (1)dp[i][0] = min(dp[i - 1][1], dp[i - 1 ][2]) + 비용[i][0]
    (2)dp[i][1] = min(dp[i - 1][0], dp[i - 1][2]) + 비용[i ][1]
    (3)dp[i][2] = min(dp[i - 1][0], dp[i - 1][1]) + 비용[i][2]

  3. 초기화 양식
    채우기가 경계를 초과하지 않도록 하려면 첫 번째 행의 값을 초기화해야 하지만 이는 너무 번거롭기 때문에 추가 행을 열고 0을 초기화할 수 있으므로 필요하지 않습니다. 첫 번째 행을 별도로 처리합니다 . (비용 배열의 첨자와 일치하는 점에 유의하세요)

  4. 양식을
    위에서 아래로, 각 줄을 왼쪽에서 오른쪽으로 입력합니다.

5. 반환 값
상태 표현에 따르면 마지막 집이 i 번이라고 가정하면 반환 값은 min({dp[n][0], dp[n][1], dp[n][2]} ).

  • 암호
class Solution {
    
    
public:
    int minCost(vector<vector<int>>& costs)
    {
    
    
        int n = costs.size();
        //dp[i][j]表示第i号房子粉刷成j颜色的最低花费
        //其中0表示红色,1表示蓝色,2表示绿色
        vector<vector<int>> dp(n + 1, vector<int>(3));
        //空间多开一行并初始化0,不用单独处理第一行

        for (int i = 1; i < n + 1; i++)
        {
    
    
            dp[i][0] = costs[i - 1][0] + min(dp[i - 1][1], dp[i - 1][2]);
            dp[i][1] = costs[i - 1][1] + min(dp[i - 1][0], dp[i - 1][2]);
            dp[i][2] = costs[i - 1][2] + min(dp[i - 1][0], dp[i - 1][1]);
        }
        return min({
    
    dp[n][0], dp[n][1], dp[n][2]});
        //时间复杂度:O(N)
        //空间复杂度:O(N)
    }
};

4. 포인트 삭제 및 적립(중)

링크: 포인트 삭제 및 적립

  • 주제 설명
    여기에 이미지 설명을 삽입하세요

  • 질문 단계

  1. 상태 표시
    여기에 이미지 설명을 삽입하세요

  2. 상태 전이 방정식
    이 질문은 수정된 "전투 집"이며, 전달 방정식은 동일합니다:
    (1) f[i] = g[i - 1] + v[ i ] (점을 얻으려면 이 위치를 삭제하십시오); (2 ) g[
    i ] = max(g[i - 1], f[i - 1])

  3. 초기화
    배열 변환이 완료된 후에는 dp 테이블을 처리할 필요가 없습니다.

  4. 왼쪽에서 오른쪽으로 양식을 작성하세요 .

  5. 반환 값
    반환 값은 max(f[N - 1],g[N - 1]) 입니다.

  • 암호
class Solution {
    
    
public:
    int deleteAndEarn(vector<int>& nums)
    {
    
    
        int n = nums.size();
    
        //创建数组进行映射
        //题目中1 <= nums[i] <= 10000
        const int N = 10001;
        int v[N] = {
    
    0};
        for(auto val : nums)
            v[val] += val;

        //“打家劫舍”
        vector<int> f(N); //f[i]表示以i区域为结尾并且删除本区域的最大点数
        auto g = f;  //g[i]表示以i区域为结尾但不删除本区域的最大点数
        for (int i = 1; i < N; i++)
        {
    
    
            f[i] = g[i - 1] + v[i];
            g[i] = max(f[i - 1], g[i - 1]);
        }
        
        return max(f[N - 1],g[N - 1]);
        //时间复杂度:O(N)
        //空间复杂度:O(1)
    }
};


//上面的写法简洁一些,但无论数据量多少都会遍历10000次
//可以记录数组的最大、最小值,来加快速度
// class Solution {
    
    
// public:
//     int deleteAndEarn(vector<int>& nums)
//     {
    
    
//         int n = nums.size();
//         vector<int> v(10001);
//         //先遍历一次
//         int _max = nums[0];
//         int _min = nums[0];
//         for (int i = 0; i < n; i++)
//         {
    
    
//             v[nums[i]] += nums[i];
//             _max = max(_max, nums[i]);
//             _min = min(_min, nums[i]);
//         }

//         vector<int> f(10001);
//         auto g = f;
//         for (int i = _min; i <= _max; i++)
//         {
    
    
//             f[i] = g[i - 1] + v[i];
//             g[i] = max(f[i - 1], g[i - 1]);
//         }
        
//         return max(f[_max],g[_max]);
//     }
// };

5. 주식을 사고 파는 가장 좋은 시기에는 수수료도 포함됩니다(중)

링크: 주식을 사고 파는 가장 좋은 시기에는 취급 수수료가 포함됩니다.

  • 주제 설명
    여기에 이미지 설명을 삽입하세요

  • 질문 단계

  1. 상태 표시
    여기에 이미지 설명을 삽입하세요

dp[i][j]: i일이 끝날 때 상태 j의 최대 이익.

  1. 상태 전이 방정식, 0은 마지막에 재고가 있음을 의미하고, 1은 마지막에 재고가 없음을 의미하며, fee는 처리 수수료, 가격[i]은 i번째 날의 주가를 의미합니다. (1) dp[i
    ] [0] = max(dp[i - 1 ][0], dp[i - 1][1] - 가격[i]) (2)dp[i][1] = max(dp[i - 1
    ] [1], dp[i - 1][ 0] + 가격[i] - 수수료)

  2. 초기화
    0일차 상태 dp[0][0] -= 가격[0]; 을 초기화하면 됩니다 .

  3. 양식을
    위에서 아래로 작성하세요.

  4. 반환 값 반환 값 은 max(dp[n - 1][1], dp[n - 1][0])
    입니다 .

  • 암호
class Solution {
    
    
public:
    int maxProfit(vector<int>& prices, int fee) 
    {
    
    
        int n = prices.size();
        //dp[i][j]:第i天结束处于j状态的最大利润
        vector<vector<int>> dp(n, vector<int>(2));
        //这种解法买入还是卖出交手续费都一样(反正买入了一定会卖出)
        dp[0][0] -= prices[0];

        for(int i = 1; i < n; i++)
        {
    
    
            dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
            dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
        }
        return max(dp[n - 1][1], dp[n - 1][0]);
        //时间复杂度:O(N)
        //空间复杂度:O(N)
    }
};

6. 주식을 사고 파는 가장 좋은 시기는 동결 기간(중간)을 포함합니다.

링크: 주식을 사고 파는 가장 좋은 시기에는 동결 기간이 포함됩니다.

  • 주제 설명
    여기에 이미지 설명을 삽입하세요

  • 질문 단계

  1. 상태 표시
    여기에 이미지 설명을 삽입하세요

  2. 상태 전이 방정식
    0은 매수(재고 있음), 1은 거래 가능, 2는 동결이며, 가격[i]는 i번째 날의 주가를 나타내며 상태 전이 방정식은 다음과 같습니다. (1) dp[i
    ] [0] = 최대 (dp[i - 1][1] - 가격[i], dp[i - 1][0]) (2)dp[i][1] = 최대(dp[i - 1
    ] [2], dp [i - 1][1])
    (3)dp[i][2] = dp[i - 1][0] + 가격[i]

  3. 초기화
    현재 날짜의 세 가지 상태에는 전날의 상태가 필요하므로
    dp 테이블의 첫 번째 라인 dp[0][0]을 초기화합니다. 하루가 끝난 후 구매 상태로 유지하려면 주식을 사야 함, dp[0][ 0] = -prices[i];
    dp[0][1]: 아무것도 하지 않음, dp[0][1] = 0;
    dp[0][2]: 원함 동결되는 당일 종료, 당일 매수 및 매도, dp[0][2] = 0;

  4. 양식을
    위에서 아래로 작성하세요.

  5. 반환 값
    배열에 n개의 요소가 있다고 가정할 때 최대값은 재고가 없어야 하며 최대값은 max(dp[n - 1][1], dp[n - 1][ 2 ]) 입니다 .

  • 암호
class Solution {
    
    
public:
    int maxProfit(vector<int>& prices) 
    {
    
    
        int n = prices.size();
        //dp[i][j]:第i天结束后处于j状态时的最大利润
        vector<vector<int>> dp(n, vector<int>(3));
        //初始化
        dp[0][0] -= prices[0];

        for(int i = 1; i < n; i++)
        {
    
    
            dp[i][0] = max(dp[i - 1][1] - prices[i], dp[i - 1][0]);
            dp[i][1] = max(dp[i - 1][2], dp[i - 1][1]);
            dp[i][2] = dp[i - 1][0] + prices[i];
        }
        
        return max(dp[n - 1][2],dp[n - 1][1]);
    }
};

7. 주식을 사고 파는 가장 좋은 시기 III (어려움)

링크: 주식을 사고 파는 가장 좋은 시기 III

  • 주제 설명
    여기에 이미지 설명을 삽입하세요

  • 질문 단계

  1. 상태 표시
    여기에 이미지 설명을 삽입하세요

  2. 상태 전이 방정식
    이전 분석에 따르면 상태 전이 방정식은 다음과 같습니다.
    (1)f[i][j] = max(f[i - 1][j], g[i - 1][j] - 가격[ i ])
    (2)if(j >= 1) g[i][j] = max(g[i - 1][j], f[i - 1][j - 1] + 가격[i])
      그렇지 않으면 g[i][j] = g[i - 1][j]

  3. 초기화
    하려면 i = 0 상태가 필요하며 첫 번째 행을 초기화합니다. (1) 첫 번째 줄에는 f[0][0] 과 g[0][0] 만 존재합니다. f[0][0] = -prices[0], g[0][0] =
    0 . (2) 존재하지 않는 상태가 최대값을 방해하는 것을 피하기 위해 존재 하지 않는 상태를 INT_MIN/2 로 균일하게 초기화합니다 . (INT_MIN은 가능한 한 작게 범위를 벗어납니다.)

  4. 각 열을 위에서 아래로, 각 행을 왼쪽에서 오른쪽으로 채웁니다 .

  5. 반환 값은
    마지막 행의 최대값을 반환합니다.

  • 암호
class Solution {
    
    
public:
    //可能会越界,取INT_MIN的一半
    const int INF = INT_MIN / 2;
    int maxProfit(vector<int>& prices) {
    
    
        int n = prices.size();
        //dp[i][j]表示在第i天结束后完成j次交易,处于""状态下的最大利润
        vector<vector<int>> f(n, vector<int>(3, INF));  //买入
        auto g = f;  //可交易
        //初始化
        f[0][0] = -prices[0];
        g[0][0] = 0;

        for (int i = 1; i < n; i++)
        {
    
    
            for(int j = 0; j < 3; j++)
            {
    
    
                f[i][j] = max(f[i - 1][j], g[i - 1][j] - prices[i]);
                g[i][j] = g[i - 1][j];
                //j == 0的时候前置状态f[i - 1][j - 1]不存在
                if(j >= 1)
                    g[i][j] = max(g[i][j], f[i - 1][j - 1] + prices[i]);
            }                                    
        }
        return max({
    
    g[n - 1][0], g[n - 1][1], g[n - 1][2]});
    }
};

8. 주식을 사고 파는 가장 좋은 시기 IV (어려움)

이 질문에 대한 사고 방식은 7번 질문과 정확히 동일합니다. 먼저 스스로 시도해 볼 수 있습니다 .
링크: 주식을 사고 파는 가장 좋은 시기 IV

  • 암호
class Solution {
    
    
public:
    const int INF = INT_MIN / 2;
    int maxProfit(int k, vector<int>& prices) 
    {
    
               
        int n = prices.size();
        //n天最多完成n / 2次交易,k不能超过这个值
        k = min(k, n / 2);
        //买入
        //dp[i][j]表示在第i天结束后完成j次交易,处于""状态下的最大利润
        vector<vector<int>> f(n, vector<int>(k + 1, INF));
        //卖出
        auto g = f;
        //初始化(先买再说)
        f[0][0] = -prices[0];
        g[0][0] = 0;

        for (int i = 1; i < n; i++)
        {
    
    
            for(int j = 0; j <= k; j++)
            {
    
    
                f[i][j] = max(f[i - 1][j], g[i - 1][j] - prices[i]);
                g[i][j] = g[i - 1][j];
                if(j >= 1)
                    g[i][j] = max(g[i][j], f[i - 1][j - 1] + prices[i]);
            }                                    
        }
        int ret = g[n - 1][0];
        //把利润最大的那个找出来
        for(int j = 1; j <= k; j++)
        {
    
    
            ret = max(ret, g[n - 1][j]);
        }
        return ret;
    }
};

Supongo que te gusta

Origin blog.csdn.net/2301_76269963/article/details/132457291
Recomendado
Clasificación