八、基础算法精讲:动态规划一

目录

  • 一、从记忆化搜索到递推
    • 1.1 打家劫舍
    • 1.2 打家劫舍 II
  • 二、01背包 完全背包 至多/恰好/至少
    • 2.1 目标和
    • 2.2 零钱兑换
    • 2.3 和为目标值的最长子序列的长度
  • 三、最长公共子序列 LCS
    • 3.1 最长公共子序列
    • 3.2 编辑距离
  • 四、最长递增子序列 LIS
    • 4.1 最长递增子序列
    • 4.2 最长递增子序列 II
    • 4.3 无矛盾的最佳球队

一、从记忆化搜索到递推

1.1 打家劫舍

Leetcode 198

解法一:递归+记录中间结果 = 记忆化搜索

class Solution:
    def rob(self, nums: List[int]) -> int:
        @cache
        def dfs(i: int)->int:
            if i < 0:
                return 0
            return max(dfs(i - 1), dfs(i - 2) + nums[i]);
        return dfs(len(nums) - 1)
class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        vector<int> memo(n, -1);
        function<int(int)> dfs=[&](int i)->int {
            if (i < 0) return 0;
            if (memo[i] != -1) return memo[i];
            int res = max(dfs(i - 1), dfs(i - 2) + nums[i]);
            memo[i] = res;
            return res;
        };
        return dfs(n - 1);
    }
};
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

解法二:记忆化搜索翻译成递推

class Solution:
    def rob(self, nums: List[int]) -> int:
        f = [0] * (len(nums) + 2)
        for i, x in enumerate(nums):
            f[i + 2] = max(f[i + 1], f[i] + x)
        return f[-1]
class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(n + 2);
        for (int i = 0; i < n; i ++ ) 
            f[i + 2] = max(f[i + 1], f[i] + nums[i]);
        return f.back();
    }
};
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

使用滚动数组进行空间优化

class Solution:
    def rob(self, nums: List[int]) -> int:
        f0 = f1 = 0
        for i, x in enumerate(nums):
            new_f = max(f1, f0 + x)
            f0, f1 = f1, new_f
        return f1
class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        int f0 = 0, f1 = 0;
        for (int x: nums) {
            int new_f = max(f1, f0 + x);
            f0 = f1, f1 = new_f;
        }
        return f1;
    }
};
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

1.2 打家劫舍 II

Leetcode 213

class Solution:
    def rob1(self, nums: List[int])->int:
            f0 = f1 = 0
            for x in nums:
                f0, f1 = f1, max(f1, f0 + x)
            return f1


    def rob(self, nums: List[int]) -> int:
        return max(nums[0] + self.rob1(nums[2:-1]), self.rob1(nums[1:]))
class Solution {
private:
    int rob1(vector<int> nums, int start, int end) {
        int n = nums.size();
        int f0 = 0, f1 = 0;
        for (int i = start; i < end; i ++ ) {
            int new_f = max(f1, f0 + nums[i]);
            f0 = f1, f1 = new_f;
        }
        return f1;
    }

public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        return max(nums[0] + rob1(nums, 2, n - 1), rob1(nums, 1, n));
    }
};

二、01背包 完全背包 至多/恰好/至少

2.1 目标和

Leetcode 494

01 背包变形,恰好的方案数

假设整个数组之和为 s s s,数组中添加正号的数之和为 p p p,那么数组中添加负号的数之和为 s − p s-p sp,那么有 p − ( s − p ) = t a r g e t p - (s-p) =target p(sp)=target,继续有: p = ( t a r g e t + s ) / 2 p = (target + s) / 2 p=(target+s)/2现在问题就转换为了:从数组中找出若干个数,使得其和为 ( t a r g e t + s ) / 2 (target+s)/2 (target+s)/2 的方案数

解法一:记忆化搜索

class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        target += sum(nums)
        if target < 0 or target % 2:
            return 0
        target //= 2

        @cache
        def dfs(i, c):
            if i < 0:
                return 1 if c == 0 else 0
            if c < nums[i]:  # 容量不够,只能不选
                return dfs(i - 1, c)
            return dfs(i - 1, c) + dfs(i - 1, c - nums[i])
            
        return dfs(len(nums) - 1, target)
class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        target += accumulate(nums.begin(), nums.end(), 0);
        if (target < 0 || target % 2) return 0;
        target /= 2;

		// 两个参数的记忆化搜索
        int n = nums.size(), cache[n][target + 1];
        memset(cache, -1, sizeof cache);
        function<int(int, int)> dfs = [&] (int i, int c)->int {
            if (i < 0) return c == 0;
            int &res = cache[i][c];
            if (res != -1) return res;
            if (c < nums[i]) return res = dfs(i - 1, c);
            res = dfs(i - 1, c) + dfs(i - 1, c - nums[i]);
            return res;
        };
        return dfs(n - 1, target);
    }
};
  • 时间复杂度: O ( n ⋅ ( t a r g e t + S ) ) O(n⋅(target+S)) O(n(target+S)),其中 n n n n u m s nums nums 的长度, S S S n u m s nums nums 的元素之和
  • 空间复杂度: O ( n ⋅ ( t a r g e t + S ) ) O(n⋅(target+S)) O(n(target+S))

解法二:递推

class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        target += sum(nums)
        if target < 0 or target % 2:
            return 0
        target //= 2

        n = len(nums)
        f = [[0] * (target + 1) for _ in range(n + 1)]
        f[0][0] = 1
        for i, x in enumerate(nums):
            for c in range(target + 1):
                if c < x:
                    f[i + 1][c] = f[i][c]
                else:
                    f[i + 1][c] = f[i][c] + f[i][c - x]
        return f[n][target]
class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        target += accumulate(nums.begin(), nums.end(), 0);
        if (target < 0 || target % 2) return 0;
        target /= 2;

        int n = nums.size(), f[n + 1][target + 1];
        memset(f, 0, sizeof f);
        f[0][0] = 1;
        for (int i = 0; i < n; i ++ )
            for (int c = 0; c <= target; c ++ )
                if (c < nums[i]) f[i + 1][c] = f[i][c];
                else f[i + 1][c] = f[i][c] + f[i][c - nums[i]];
        return f[n][target];
    }
};
  • 时间复杂度: O ( n ⋅ ( t a r g e t + S ) ) O(n⋅(target+S)) O(n(target+S)),其中 n n n n u m s nums nums 的长度, S S S n u m s nums nums 的元素之和
  • 空间复杂度: O ( n ⋅ ( t a r g e t + S ) ) O(n⋅(target+S)) O(n(target+S))

解法三:使用滚动数组优化空间

class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        target += sum(nums)
        if target < 0 or target % 2:
            return 0
        target //= 2

        n = len(nums)
        f = [[0] * (target + 1) for _ in range(2)]
        f[0][0] = 1
        for i, x in enumerate(nums):
            for c in range(target + 1):
                if c < x:
                    f[(i + 1) % 2][c] = f[i % 2][c]
                else:
                    f[(i + 1) % 2][c] = f[i % 2][c] + f[i % 2][c - x]

        return f[n % 2][target]
class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        target += accumulate(nums.begin(), nums.end(), 0);
        if (target < 0 || target % 2) return 0;
        target /= 2;

        int n = nums.size(), f[2][target + 1];
        memset(f, 0, sizeof(f));
        f[0][0] = 1;
        for (int i = 0; i < n; i ++ )
            for (int c = 0; c <= target; c ++)
                if (c < nums[i]) f[(i + 1) % 2][c] = f[i % 2][c];
                else f[(i + 1) % 2][c] = f[i % 2][c] + f[i % 2][c - nums[i]];
        return f[n % 2][target];
    }
};
  • 时间复杂度: O ( n ⋅ ( t a r g e t + S ) ) O(n⋅(target+S)) O(n(target+S)),其中 n n n n u m s nums nums 的长度, S S S n u m s nums nums 的元素之和
  • 空间复杂度: O ( t a r g e t + S ) O(target+S) O(target+S)

解法四:仅仅使用一个数组

class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        target += sum(nums)
        if target < 0 or target % 2:
            return 0
        target //= 2

        f = [1] + [0] * target
        for x in nums:
            for c in range(target, x - 1, -1):
                    f[c] += f[c - x]

        return f[target]
class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        target += accumulate(nums.begin(), nums.end(), 0);
        if (target < 0 || target % 2) return 0;
        target /= 2;

        int f[target + 1];
        memset(f, 0, sizeof f);
        f[0] = 1;
        for (int x: nums)
            for (int c = target; c >= x; c -- )
                f[c] += f[c - x];
        return f[target];
    }
};

2.2 零钱兑换

Leetcode 322

解法一:记忆化搜索

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        @cache
        def dfs(i, c):
            if i < 0:
                return 0 if c == 0 else inf
            if c < coins[i]:
                return dfs(i - 1, c)
            return min(dfs(i - 1, c), dfs(i, c - coins[i]) + 1)
        ans = dfs(len(coins) - 1, amount)
        return ans if ans < inf else -1
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int n = coins.size(), cache[n][amount + 1];
        memset(cache, -1, sizeof(cache));
        function<int(int, int)> dfs = [&](int i, int c)-> int {
            if (i < 0) return c == 0 ? 0 : INT_MAX / 2;
            int &res = cache[i][c];
            if (res != -1) return res;
            if (c < coins[i]) return res = dfs(i - 1, c);
            res = min(dfs(i - 1, c), dfs(i, c - coins[i]) + 1);
            return res;
        };
        int ans = dfs(n - 1, amount);
        return ans < INT_MAX / 2 ? ans : -1;
    }
};
  • 时间复杂度: O ( n ⋅ a m o u n t ) O(n⋅amount) O(namount),其中 n n n c o i n s coins coins 的长度
  • 空间复杂度: O ( n ⋅ a m o u n t ) O(n⋅amount) O(namount)

解法二:翻译成递推

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        n = len(coins)
        f = [[inf] * (amount + 1) for _ in range(n + 1)]
        f[0][0] = 0
        for i, x in enumerate(coins):
            for c in range(amount + 1):
                if c < x:
                    f[i + 1][c] = f[i][c]
                else:
                    f[i + 1][c] = min(f[i][c], f[i + 1][c - x] + 1)
        ans = f[n][amount]
        return ans if ans < inf else -1
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int n = coins.size(), f[n + 1][amount + 1];
        memset(f, 0x3f, sizeof f);
        f[0][0] = 0;
        for (int i = 0; i < n; i ++ )
            for (int c = 0; c <= amount; c ++ ) 
                if (c < coins[i]) f[i + 1][c] = f[i][c];
                else f[i + 1][c] = min(f[i][c], f[i + 1][c - coins[i]] + 1);
        int ans = f[n][amount];
        return ans < 0x3f3f3f3f ? ans : -1;
    }
};
  • 时间复杂度: O ( n ⋅ a m o u n t ) O(n⋅amount) O(namount),其中 n n n c o i n s coins coins 的长度
  • 空间复杂度: O ( n ⋅ a m o u n t ) O(n⋅amount) O(namount)

解法三:使用滚动数组进行空间优化

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        n = len(coins)
        f = [[inf] * (amount + 1) for _ in range(2)]
        f[0][0] = 0
        for i, x in enumerate(coins):
            for c in range(amount + 1):
                if c < x:
                    f[(i + 1) % 2][c] = f[i % 2][c]
                else:
                    f[(i + 1) % 2][c] = min(f[i % 2][c], f[(i + 1) % 2][c - x] + 1)
        ans = f[n % 2][amount]
        return ans if ans < inf else -1
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int n = coins.size(), f[2][amount + 1];
        memset(f, 0x3f, sizeof f);
        f[0][0] = 0;
        for (int i = 0; i < n; i ++ )
            for (int c = 0; c <= amount; c ++ ) 
                if (c < coins[i]) f[(i + 1) % 2][c] = f[i % 2][c];
                else f[(i + 1) % 2][c] = min(f[i % 2][c], f[(i + 1) % 2][c - coins[i]] + 1);
        int ans = f[n % 2][amount];
        return ans < 0x3f3f3f3f ? ans : -1;
    }
};
  • 时间复杂度: O ( n ⋅ a m o u n t ) O(n⋅amount) O(namount),其中 n n n c o i n s coins coins 的长度
  • 空间复杂度: O ( n ⋅ a m o u n t ) O(n⋅amount) O(namount)

解法四:使用一个数组进行优化

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        f = [0] + [inf] * amount
        for x in coins:
            for c in range(x, amount + 1):
                f[c] = min(f[c], f[c - x] + 1)
        ans = f[amount]
        return ans if ans < inf else -1
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int f[amount + 1];
        memset(f, 0x3f, sizeof f);
        f[0] = 0;
        for (int x: coins)
            for (int c = x; c <= amount; c ++ )
                f[c] = min(f[c], f[c - x] + 1);
        int ans = f[amount];
        return ans < 0x3f3f3f3f ? ans : -1;
    }
};

2.3 和为目标值的最长子序列的长度

Leetcode 2915

class Solution:
    def lengthOfLongestSubsequence(self, nums: List[int], target: int) -> int:
        f = [0] +  [-inf] * target
        s = 0
        for x in nums:
            s = min(s + x, target)
            for j in range(s, x - 1, - 1):
                f[j] = max(f[j], f[j - x] + 1)
        return f[-1] if f[-1] > 0 else -1
class Solution {
public:
    int lengthOfLongestSubsequence(vector<int>& nums, int target) {
        vector<int> f(target + 1, INT_MIN);
        f[0] = 0;
        int s = 0;
        for (int x: nums) {
            s = min(s + x, target);
            for (int j = s; j >= x; j -- )
                f[j] = max(f[j], f[j - x] + 1);
        }
        return f[target] > 0 ? f[target] : -1;
    }
};
  • 时间复杂度: O ( n ⋅ t a r g e t ) O(n⋅target) O(ntarget),其中 n n n n u m s nums nums 的长度
  • 空间复杂度: O ( t a r g e t ) O(target) O(target)

三、最长公共子序列 LCS

3.1 最长公共子序列

Leetcode 1143

解法一:记忆化搜索

class Solution:
    def longestCommonSubsequence(self, s: str, t: str) -> int:
        n, m = len(s), len(t)

        @cache
        def dfs(i, j):
            if i < 0 or j < 0: 
                return 0
            if s[i] == t[j]:
                return dfs(i - 1, j - 1) + 1
            return max(dfs(i - 1, j), dfs(i, j - 1));
        return dfs(n - 1, m - 1)
class Solution {
public:
    int longestCommonSubsequence(string s, string t) {
        int n = s.size(), m = t.size();
        int cache[n][m];
        memset(cache, -1, sizeof cache);
        function<int(int, int)> dfs=[&](int i, int j)->int {
            if (i < 0 || j < 0) return 0;
            int &res = cache[i][j];
            if (res != -1) return res;
            if (s[i] == t[j]) {
                res = dfs(i - 1, j - 1) + 1;
                return res;
            }
            res = max(dfs(i - 1, j), dfs(i, j - 1));
            return res;
        };
        return dfs(n - 1, m - 1);
    }
};
  • 时间复杂度: O ( n m ) O(nm) O(nm),状态数目 x 单个状态计算时间
  • 空间复杂度: O ( n m ) O(nm) O(nm)

解法二:递推

class Solution:
    def longestCommonSubsequence(self, s: str, t: str) -> int:
        n, m = len(s), len(t)
        f = [[0] * (m + 1) for _ in range(n + 1)]
        for i, x in enumerate(s):
            for j, y in enumerate(t):
                if x == y:
                    f[i + 1][j + 1] = f[i][j] + 1
                else:
                    f[i + 1][j + 1] = max(f[i + 1][j], f[i][j + 1])
        return f[n][m]
class Solution {
public:
    int longestCommonSubsequence(string s, string t) {
        int n = s.size(), m = t.size();
        int f[n + 1][m + 1];
        memset(f, 0, sizeof f);
        for (int i = 0; i < n; i ++ )
            for (int j = 0; j < m; j ++ )
                f[i + 1][j + 1] = s[i] == t[j] ? f[i][j] + 1 : max(f[i + 1][j], f[i][j + 1]);
        return f[n][m];
    }
};
  • 时间复杂度: O ( n m ) O(nm) O(nm),状态数目 x 单个状态计算时间
  • 空间复杂度: O ( n m ) O(nm) O(nm)

解法三:滚动数组

class Solution:
    def longestCommonSubsequence(self, s: str, t: str) -> int:
        n, m = len(s), len(t)
        f = [[0] * (m + 1) for _ in range(2)]
        for i, x in enumerate(s):
            for j, y in enumerate(t):
                if x == y:
                    f[(i + 1) % 2][j + 1] = f[i % 2][j] + 1
                else:
                    f[(i + 1) % 2][j + 1] = max(f[(i + 1) % 2][j], f[i % 2][j + 1])
        return f[n % 2][m]
class Solution {
public:
    int longestCommonSubsequence(string s, string t) {
        int n = s.size(), m = t.size();
        int f[2][m + 1];
        memset(f, 0, sizeof f);
        for (int i = 0; i < n; i ++ )
            for (int j = 0; j < m; j ++ )
                f[(i + 1) % 2][j + 1] = s[i] == t[j] ? f[i % 2][j] + 1 : max(f[(i + 1) % 2][j], f[i % 2][j + 1]);
        return f[n % 2][m];
    }
};
  • 时间复杂度: O ( n m ) O(nm) O(nm),状态数目 x 单个状态计算时间
  • 空间复杂度: O ( m ) O(m) O(m)

解法四:一个数组

class Solution:
    def longestCommonSubsequence(self, s: str, t: str) -> int:
        f = [0] * (len(t) + 1)
        for x in s:
            pre = 0
            for j, y in enumerate(t):
                tmp = f[j + 1]
                f[j + 1] = pre + 1 if x == y else max(f[j + 1], f[j])
                pre = tmp
        return f[-1]
class Solution {
public:
    int longestCommonSubsequence(string s, string t) {
        int m = t.length(), f[m + 1];
        memset(f, 0, sizeof f);
        for (char x: s)
            for (int j = 0, pre = 0; j < m; j ++ ) {
                int tmp = f[j + 1];
                f[j + 1] = x == t[j] ? pre + 1 : max(f[j + 1], f[j]);
                pre = tmp;
            }
        return f[m];
    }
};
  • 时间复杂度: O ( n m ) O(nm) O(nm),状态数目 x 单个状态计算时间
  • 空间复杂度: O ( m ) O(m) O(m)

3.2 编辑距离

Leetcode 72

解法一:记忆化搜索

class Solution:
    def minDistance(self, s: str, t: str) -> int:
        n, m = len(s), len(t)

        @cache
        def dfs(i, j):
            if i < 0: return j + 1
            if j < 0: return i + 1
            if s[i] == t[j]: return dfs(i - 1, j - 1)
            return min(dfs(i - 1, j), dfs(i, j - 1), dfs(i - 1, j - 1)) + 1
        
        return dfs(n - 1, m - 1)
class Solution {
public:
    int minDistance(string s, string t) {
        int n = s.length(), m = t.length();
        int cache[n + 1][m + 1];
        memset(cache, -1, sizeof cache);
        function<int(int, int)> dfs = [&](int i, int j)->int {
            if (i < 0) return j + 1;
            if (j < 0) return i + 1;
            int &res = cache[i][j];
            if (res != -1) return res;
            if (s[i] == t[j]) {
                res = dfs(i - 1, j - 1);
                return res;
            }
            res = min(min(dfs(i - 1, j), dfs(i, j - 1)), dfs(i - 1, j - 1)) + 1;
            return res;
        };
        return dfs(n - 1, m - 1);
    }
};
  • 时间复杂度: O ( n m ) O(nm) O(nm)
  • 空间复杂度: O ( n m ) O(nm) O(nm)

解法二:递推

class Solution:
    def minDistance(self, s: str, t: str) -> int:
        n, m = len(s), len(t)
        f = [[0] * (m + 1) for _ in range(n + 1)]
        f[0] = list(range(m + 1))  # 当i<0的时候
        for i, x in enumerate(s):
            f[i + 1][0] = i + 1    # 当j<0的时候
            for j, y in enumerate(t):
                f[i + 1][j + 1] = f[i][j] if x == y else min(f[i][j + 1], f[i + 1][j], f[i][j]) + 1
        return f[n][m]
class Solution {
public:
    int minDistance(string s, string t) {
        int n = s.length(), m = t.length();
        int f[n + 1][m + 1];
        for (int j = 0; j <= m; j ++ ) f[0][j] = j;
        for (int i = 0; i < n; i ++ ) {
            f[i + 1][0] = i + 1;
            for (int j = 0; j < m; j ++ )
                f[i + 1][j + 1] = s[i] == t[j] ? f[i][j] : min(min(f[i][j + 1], f[i + 1][j]), f[i][j]) + 1;
        }
        return f[n][m];
    }
};
  • 时间复杂度: O ( n m ) O(nm) O(nm)
  • 空间复杂度: O ( n m ) O(nm) O(nm)

滚动数组

class Solution:
    def minDistance(self, s: str, t: str) -> int:
        n, m = len(s), len(t)
        f = [list(range(m + 1)), [0] * (m + 1)]
        for i, x in enumerate(s):
            f[(i + 1) % 2][0] = i + 1    # 当j<0的时候
            for j, y in enumerate(t):
                f[(i + 1) % 2][j + 1] = f[i % 2][j] if x == y else min(f[i % 2][j + 1], f[(i + 1) % 2][j], f[i % 2][j]) + 1
        return f[n % 2][m]
class Solution {
public:
    int minDistance(string s, string t) {
        int n = s.length(), m = t.length();
        int f[2][m + 1];
        for (int j = 0; j <= m; j ++ ) f[0][j] = j;
        for (int i = 0; i < n; i ++ ) {
            f[(i + 1) % 2][0] = i + 1;
            for (int j = 0; j < m; j ++ )
                f[(i + 1) % 2][j + 1] = s[i] == t[j] ? f[i % 2][j] : min(min(f[i % 2][j + 1], f[(i + 1) % 2][j]), f[i % 2][j]) + 1;
        }
        return f[n % 2][m];
    }
};
  • 时间复杂度: O ( n m ) O(nm) O(nm)
  • 空间复杂度: O ( m ) O(m) O(m)

解法四:一个数组

class Solution:
    def minDistance(self, s: str, t: str) -> int:
        f = list(range(len(t) + 1))
        for x in s:
            pre = f[0]
            f[0] += 1
            for j, y in enumerate(t):
                tmp = f[j + 1]
                f[j + 1] = pre if x == y else min(f[j + 1], f[j], pre) + 1
                pre = tmp
        return f[-1]
class Solution {
public:
    int minDistance(string s, string t) {
        int m = t.length(), f[m + 1];
        iota(f, f + m + 1, 0);
        for (char x: s) {
            int pre = f[0];
            f[0] ++ ;
            for (int j = 0; j < m; j ++ ) {
                int tmp = f[j + 1];
                f[j + 1] = x == t[j] ? pre : min(min(f[j + 1], f[j]), pre) + 1;
                pre = tmp;
            }
        }
        return f[m];
    }
};
  • 时间复杂度: O ( n m ) O(nm) O(nm)
  • 空间复杂度: O ( m ) O(m) O(m)

四、最长递增子序列 LIS

LIS 问题的基础解法是时间复杂度为 O ( n 2 ) O(n^2) O(n2)DP 解法,优化的思路有两个:

  • 时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)贪心+二分的解法
  • 基于值域的树状数组/线段树解法

4.1 最长递增子序列

Leetcode 300

思路一:动态规划

  • 解法一:记忆化搜索
class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:

        @cache
        def dfs(i: int)->int:
            res = 0
            for j in range(i):
                if (nums[j] < nums[i]):
                    res = max(res, dfs(j))
            return res + 1
        return max(dfs(i) for i in range(len(nums)))
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size(), cache[n];
        memset(cache, 0, sizeof cache);
        function<int(int)> dfs = [&](int i)->int {
            int &res = cache[i];
            if (res) return res;
            for (int j = 0; j < i; j ++ ) 
                if (nums[j] < nums[i])
                    res = max(res, dfs(j));
            return ++ res;
        };
        int ans = 0;
        for (int i = 0; i < n; i ++ )
            ans = max(ans, dfs(i));
        return ans;
    }
};
  • 解法二:递推
class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        f = [0] * len(nums)
        for i, x in enumerate(nums):
            for j, y in enumerate(nums[:i]):
                if x > y:
                    f[i] = max(f[i], f[j]);
            f[i] += 1
        return max(f)
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size(), f[n];
        for (int i = 0; i < n; i ++ ) {
            f[i] = 0;
            for (int j = 0; j < i; j ++ )
                if (nums[i] > nums[j])
                    f[i] = max(f[i], f[j]);
            f[i] ++ ;
        }
        return *max_element(f, f + n);
    }
};
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n ) O(n) O(n)

思路二:贪心 + 二分

  • 解法一:额外空间
class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        g = []
        for x in nums:
            j = bisect_left(g, x)
            if j == len(g): g.append(x)
            else: g[j] = x
        return len(g)
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> g;
        for (int x: nums) {
            auto it = lower_bound(g.begin(), g.end(), x);
            if (it == g.end()) g.push_back(x);
            else *it = x;
        }
        return g.size();
    }
};
  • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

  • 空间复杂度: O ( n ) O(n) O(n)

  • 解法二:原地修改

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        ng = 0
        for x in nums:
            j = bisect_left(nums, x, 0, ng)
            nums[j] = x
            if j == ng: ng += 1
        return ng
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        auto end = nums.begin();
        for (int x: nums) {
            auto it = lower_bound(nums.begin(), end, x);
            *it = x;
            if (it == end) end ++ ;
        }
        return end - nums.begin();
    }
};
  • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
  • 空间复杂度: O ( 1 ) O(1) O(1)

4.2 最长递增子序列 II

Leetcode 2407

class Solution:
    def lengthOfLIS(self, nums: List[int], k: int) -> int:
        u = max(nums)
        mx = [0] * (4 * u)

        def modify(o: int, l: int, r: int, i: int, val: int)->None:
            if l == r:
                mx[o] = val
                return
            m = (l + r) // 2
            if i <= m: modify(o * 2, l, m, i, val)
            else: modify(o * 2 + 1, m + 1, r, i, val)
            mx[o] = max(mx[o * 2], mx[o * 2 + 1])
        
        def query(o: int, l: int, r: int, L: int, R: int)->int:
            if L <= l and r <= R: return mx[o]
            res = 0
            m = (l + r) // 2
            if L <= m: res = query(o * 2, l, m, L, R)
            if R > m: res = max(res, query(o * 2 + 1, m + 1, r, L, R))
            return res

        for x in nums:
            if x == 1:
                modify(1, 1, u, 1, 1)
            else:
                res = 1 + query(1, 1, u, max(x - k, 1), x - 1)
                modify(1, 1, u, x, res)
        
        return mx[1]
class Solution {
    vector<int> mx;
    void modify(int o, int l, int r, int i, int val) {
        if (l == r) {
            mx[o] = val;
            return;
        }
        int m = (l + r) / 2;
        if (i <= m) modify(o * 2, l, m, i, val);
        else modify(o * 2 + 1, m + 1, r, i, val);
        mx[o] = max(mx[o * 2], mx[o * 2 + 1]);
    }

    int query(int o, int l, int r, int L, int R) {
        if (L <= l && r <= R) return mx[o];
        int res = 0, m = (l + r) / 2;
        if (L <= m) res = query(o * 2, l, m, L, R);
        if (R > m) res = max(res, query(o * 2 + 1, m + 1, r, L, R));
        return res;
    }

public:
    int lengthOfLIS(vector<int>& nums, int k) {
        int u = *max_element(nums.begin(), nums.end());
        mx.resize(u * 4);
        for (int x: nums) 
            if (x == 1) modify(1, 1, u, 1, 1);
            else {
                int res = 1 + query(1, 1, u, max(x - k, 1), x - 1);
                modify(1, 1, u, x, res);
            }
        return mx[1];
    }
};

4.3 无矛盾的最佳球队

Leetcode 1626

解法一:排序+动态规划

class Solution:
    def bestTeamScore(self, scores: List[int], ages: List[int]) -> int:
        a = sorted(zip(scores, ages))
        f = [0] * len(a)
        for i, (score, age) in enumerate(a):
            for j in range(i):
                if a[j][1] <= age:
                    f[i] = max(f[i], f[j])
            f[i] += score
        return max(f)
class Solution {
public:
    int bestTeamScore(vector<int>& scores, vector<int>& ages) {
        int n = scores.size();
        pair<int, int> a[n];
        for (int i = 0; i < n; i ++ )
            a[i] = {scores[i], ages[i]};
        sort(a, a + n);

        int f[n];
        memset(f, 0, sizeof f);
        for (int i = 0; i < n; i ++ ) {
            for (int j = 0; j < i; j ++ )
                if (a[j].second <= a[i].second)
                    f[i] = max(f[i], f[j]);
            f[i] += a[i].first;
        }

        return *max_element(f, f + n);
    }
};
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n ) O(n) O(n)

解法二:基于值域运算

class Solution:
    def bestTeamScore(self, scores: List[int], ages: List[int]) -> int:
        max_sum = [0] * (max(ages) + 1)
        for score, age in sorted(zip(scores, ages)):
            max_sum[age] = max(max_sum[:age + 1]) + score
        return max(max_sum)
class Solution {
public:
    int bestTeamScore(vector<int>& scores, vector<int>& ages) {
        int n = scores.size();
        pair<int, int> a[n];
        for (int i = 0; i < n; i ++ )
            a[i] = {scores[i], ages[i]};
        sort(a, a + n);

        int u = *max_element(ages.begin(), ages.end());
        int max_sum[u + 1]; memset(max_sum, 0, sizeof max_sum);
        for (auto &[score, age] : a) 
            max_sum[age] = *max_element(max_sum, max_sum + age + 1) + score;
        return *max_element(max_sum, max_sum + u + 1);
    }
};
  • 时间复杂度: O ( n l o g ⁡ n + n U ) O(nlog⁡n+nU) O(nlogn+nU),其中 n n n a g e s ages ages 的长度, U = m a x ⁡ ( a g e s ) U=max⁡(ages) U=max(ages)
  • 空间复杂度: O ( n + U ) O(n+U) O(n+U)

解法三:树状数组优化

class Solution:
    def bestTeamScore(self, scores: List[int], ages: List[int]) -> int:
        u = max(ages)
        t = [0] * (u + 1)

        def query(i: int)->int:
            mx = 0
            while i:
                mx = max(mx, t[i])
                i &= i - 1
            return mx

        def update(i: int, mx: int)->None:
            while i < len(t):
                t[i] = max(t[i], mx)
                i += i & -i

        for score, age in sorted(zip(scores, ages)):
            update(age, query(age) + score)
        
        return query(u)
class Solution {
    static constexpr int MX = 1000;
    int t[MX + 1];

    int query(int i) {
        int mx = 0;
        for (; i; i &= i - 1) mx = max(mx, t[i]);
        return mx;
    }

    void update(int i, int mx) {
        for (; i <= MX; i += i & -i)
            t[i] = max(t[i], mx);
    }

public:
    int bestTeamScore(vector<int>& scores, vector<int>& ages) {
        int n = scores.size();
        pair<int, int> a[n];
        for (int i = 0; i < n; i ++ ) 
            a[i] = {scores[i], ages[i]};
        sort(a, a + n);

        for (auto &[score, age] : a) 
            update(age, query(age) + score);

        return query(MX);
    }
};

你可能感兴趣的:(算法笔记,2,算法,动态规划)