[每日算法] leetcode第115题 Distinct Subsequence 和 第321题 Create Maximum Number

115. Distinct Subsequence

原题目描述

Given a string S and a string T, count the number of distinct subsequences of S which equals T.

A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, “ACE” is a subsequence of “ABCDE” while “AEC” is not).

Example 1:

Input: S = "rabbbit", T = "rabbit"
Output: 3
Explanation:

As shown below, there are 3 ways you can generate "rabbit" from S.
(The caret symbol ^ means the chosen letters)

rabbbit
^^^^ ^^
rabbbit
^^ ^^^^
rabbbit
^^^ ^^^

Example 2:

Input: S = "babgbag", T = "bag"
Output: 5
Explanation:

As shown below, there are 5 ways you can generate "bag" from S.
(The caret symbol ^ means the chosen letters)

babgbag
^^ ^
babgbag
^^    ^
babgbag
^    ^^
babgbag
  ^  ^^
babgbag
    ^^^

题目大意

该题给定字符串S和字符串T,计算在S的子序列中T出现的个数。
一个字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)。

解题思路

该题分类为动态规划,所以我们很自然地往DP方面延伸思考,如何将问题转化为子问题并得出状态转移方程。
我们用DP(i, j)来表示 字符串S的前i个字符组成的字符串的子序列中,T的前j个字符组成的字符串出现的次数(比较拗口但是不难理解,即将题目所给S,T变为S[0:i], T[0:j])。
我们很容易看出边界情况:

  • DP(0, 0) = 1,因为此时T和S对应是空串,空串只在空串中出现一次;
  • DP(i, 0) = 1, i > 0, 因为此时T为空串,而S不为空串,空串只在S中出现一次;
  • DP(0, j) = 0, j > 0, 因为此时S为空串,而T不为空串,T在空串中不可能出现过;

然后,接下来便是对状态转移方程的得出:

  • 当S[i] != T[j]的时候,DP(i, j) = DP(i-1, j),例如:“ABCD”的子序列中匹配“AB”的个数,与"ABC"的子序列匹配“AB”的个数是相同的。
  • 当S[i] == T[j]的时候,DP(i, j) = DP(i-1, j) + DP(i-1, j-1),这个也很好理解,例如,“ABCC”中匹配“”ABC"的数量,除了其子序列"ABC"中匹配“ABC”的数量,还要考虑"ABC"中匹配“AB”的数量。(即最后一个字符可能是前面就匹配过的又重复了的,也可以是现在才匹配的,需要考虑这两种情况)。

得出边界情况和状态方程之后,我们便可以求出答案DP(i, j)了。

C++实现代码

class Solution {
public:
    int numDistinct(string s, string t) {
        int len1 = s.size(), len2 = t.size();
        if (len1 == 0 || len2 == 0)
            return 0;

        int ** dp = new int*[len1+1];
        for (int i = 0; i < len1+1; i++) {
            dp[i] = new int[len2+1];
            for (int j = 0; j < len2+1; j++)
                dp[i][j] = 0;
        }

		// 边界情况
        for (int i = 0; i < len1; i++)
            dp[i][0] = 1;
        // 根据状态转移方程递推
        for (int i = 1; i <= len1; i++)
            for (int j = 1; j <= len2; j++) {
                if (s[i-1] == t[j-1]) {
                    dp[i][j] = dp[i-1][j] + dp[i-1][j-1];
                } else {
                    dp[i][j] = dp[i-1][j];
                }
            }
        return dp[len1][len2];
    }
};

321. Create Maximum Number

原题目描述

Given two arrays of length m and n with digits 0-9 representing two numbers. Create the maximum number of length k <= m + n from digits of the two. The relative order of the digits from the same array must be preserved. Return an array of the k digits.

Note: You should try to optimize your time and space complexity.

Example 1:

Input:
nums1 = [3, 4, 6, 5]
nums2 = [9, 1, 2, 5, 8, 3]
k = 5
Output:
[9, 8, 6, 5, 3]

Example 2:

Input:
nums1 = [6, 7]
nums2 = [6, 0, 4]
k = 5
Output:
[6, 7, 6, 0, 4]

Example 3:

Input:
nums1 = [3, 9]
nums2 = [8, 9]
k = 3
Output:
[9, 8, 9]

题目大意

给定两个数组。现在从这两个数组中选出 k个数字拼接成一个新的数,要求从同一个数组中取出的数字保持其在原数组中的相对顺序。

解题思路

此题虽然分类于DP,但是苦思冥想也难以划分子问题,得出状态转移方程。
然而可以采用一种比较暴力巧妙的方法,即题目要求从两个数组中取k个数字,那么可以将k分为两部分:i个和k-i个,然后从两个数组中分别取出i和k-i个,再将两个部分merge一下便可以了,然后 i 从 0 到 k进行遍历,得到所有的k+1种情况,从中挑选出最大的情况即可。
问题的难点在于如何实现从一个数组选出最大的前i个,merge操作以及比较两种情况哪个比较大的操作。

  • 如何从数组中挑选出前 i 个数字组成最大的数,因为要求这i个在数组中的位置相对不变,那么很容易可以知道,求第一个便是在前len -i个中挑选最大的那一个,后边的也就变成子问题了(也许这一步有DP的可能)。
  • merge操作,这个其实不难,每次比较哪个数组比较大(比较操作看下一步),然后从那个情况中pop出第一个,然后接着比较和pop,知道两个数组都为空了。‘’
  • 比较操作也很简单,就只是单纯依次对数组中每一个数字,一个一个地进行比较而已,考虑好边界情况即可(数组为空或不一样长)。

C++实现代码

class Solution {
public:
    vector<int> maxNumber(vector<int>& nums1, vector<int>& nums2, int k) {
        int len1 = nums1.size(), len2 = nums2.size();
        if (len1 + len2 == k)
            return merge(nums1, nums2);
        vector<int> result;
        for (int i = 0; i <= k; i++) {
            if (i <= len1 && (k-i) <= len2) {
                auto maxSeq1 = maxSeqOfArray(nums1, i);
                auto maxSeq2 = maxSeqOfArray(nums2, k - i);
                auto maxSeq = merge(maxSeq1, maxSeq2);
                if (compare(maxSeq, 0, result, 0)) 
                    result = maxSeq;
            }
        }
        return result;
    }

    vector<int> maxSeqOfArray(vector<int> array, int num) {
        vector<int> res;
        int nowIndex = 0, len = array.size();
        while(num > 0) {
            int max = -1, max_index;
            for (int i = nowIndex; i <= len - num; i++) {
                if (array[i] > max) {
                    max = array[i];
                    max_index = i;
                }
            }
            res.push_back(max);
            nowIndex = max_index + 1;
            num--;
        }
        return res;
    }

    vector<int> merge(vector<int>& nums1, vector<int>& nums2) {
        vector<int> res;
        int len1 = nums1.size(), len2 = nums2.size(), i = 0, j = 0;
        while(i < len1 || j < len2) {
            if (i == len1) {
                res.push_back(nums2[j++]);
            } else if (j == len2) {
                res.push_back(nums1[i++]);
            } else if (compare(nums1, i, nums2, j)) {
                res.push_back(nums1[i++]);
            } else {
                res.push_back(nums2[j++]);
            }
        } 
        return res;
    }

    bool compare(vector<int>& nums1, int index1, vector<int>& nums2, int index2) {
        int len1 = nums1.size() - index1, len2 = nums2.size() - index2;
        int nums1_len = nums1.size(), nums2_len = nums2.size();
        if (len1 <= 0) return false;
        if (len2 <= 0) return true;
        int len = max(len1, len2);
        for (int i = 0; i < len; i++) {
            int digit1 = index1 + i < nums1_len ? nums1[index1 + i] : 0;
        	int digit2 = index2 + i < nums2_len ? nums2[index2 + i] : 0;
        	if(digit1 != digit2){
        	    return digit1 > digit2;
        	}
        }
        return true;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值