一些DP题

文章目录

    • LeetCode 300 -- Longest Increasing Subsequence (最长递增子序列)
    • leetcode -- 5 Longest Palindromic Substring(最长回文子串)
    • 最长回文子序列的长度
    • 回文子串个数
    • Longest Common SubString(最长公共子串)
    • Longest Common Subsequence(最长公共子序列)
    • Coin Change

LeetCode 300 – Longest Increasing Subsequence (最长递增子序列)

传送门

思路:
使用一个数组来记录到当前index的最大子序列的长度是多少。
一些DP题_第1张图片
上面这个图就是当5大于2的时候,我们就需要更新辅助数组的值。

int lengthOfLIS(vector& nums) {
        int size = nums.size();
        vector helper(size, 1);
        int res = 0;
        for (int i = 0; i < size; ++i) {
            for (int j = 0; j < i; ++j) {
                helper[i] = (nums[i] > nums[j] && helper[i] < helper[j] + 1) ? helper[j] + 1 : helper[i];
            }
            res = res > helper[i] ? res : helper[i];
        }
        return res;
 }

从代码可以看出 j 要小于 i ,然后找出 i 之前的子序列的最大值,helper[i]就可以得到到当前index的最长子序列的长度。

leetcode – 5 Longest Palindromic Substring(最长回文子串)

传送门
思路:
因为是子串,是连续的,那么还是用begin和end两个变量来保存回文的开头和结尾,然后求得长度,再计算起始的子串index即可。

public:
    string longestPalindrome(string s) {
        int size = s.size();
        if (size == 0 || size == 1) return s;
        int mBegin = 0, len = 0;
        for (int i = 0; i < size-1; ++i) {
            int one = getMax(s, size, i, i);
            int two = getMax(s, size, i, i+1);
            int curMax = max(one, two);
            if (len < curMax) {
                len = curMax;
                mBegin = i - ((len - 1) >> 1);
            }
        }
        return string(s, mBegin, len);
    }
private:
    int getMax(string& s, int size, int begin, int end) {
        while (begin >= 0 && end < size && s[begin] == s[end]) {
            begin--;
            end++;
        }
        return end - begin - 1;
    }

值得注意的是:one,two这两个长度,是为了计算单数长度的子串和双数子串的长度。

最长回文子序列的长度

传送门
思路:
使用一个二维数组来帮助我们记录长度

d[i][j]代表 s[i:j]的最长的回文子序列的长度。
状态转移方程为: d[i][j] = if s[i] == s[j] : d[i+1][j-1] + 2 else max(d[i+1][j], d[i][j-1]);

    int longestPalindromeSubseq(string s) {
        int size = s.size();
        int* helper[size];
        for (int i = 0; i < size; ++i) {
            helper[i] = new int[size];
            memset(helper[i], 0, sizeof(int) * size);
        }
        int res = 0;
        for (int i = 0; i < size; ++i) {
            helper[i][i] = 1;
            res = res > helper[i][i] ? res : helper[i][i];
            for (int j = i-1; j >= 0; --j) {
                if (s[i] == s[j]) helper[i][j] = helper[i-1][j+1] + 2;
                else helper[i][j] = max(helper[i-1][j], helper[i][j+1]);
                res = res > helper[i][j] ? res : helper[i][j];
            }
        }
        return res;
    }

回文子串个数

传送门
思路:
也是使用一个二维数组来帮助我们,d[i][j]代表s[i:j]子串的回文长度,如果s[i:j]不是回文,那么d[i][j]为0。

状态转移方程为:d[i][j] = if s[i] == s[j] && d[i+1]d[j-1] != 0 : d[i+1][j-1] + 2 else 0;

    int countSubstrings(string s) {
        int size = s.size();
        int* helper[size];
        for (int i = 0; i < size; ++i) {
            helper[i] = new int[size];
            memset(helper[i], 0, sizeof(int) * size);
        }
        int res = size;
        for (int i = 0; i < size; ++i) {
            helper[i][i] = 1;
            for (int j = i-1; j >= 0; --j) {
                if (j+1 == i && s[j] == s[i]) {
                    helper[i][j] = 2;
                    res++;
                } else if (s[j] == s[i] && helper[i-1][j+1] != 0) {
                    helper[i][j] = helper[i-1][j+1] + 2;
                    res++;
                } else {
                    helper[i][j] = 0;
                }
            }
        }
        return res;
    }

特别要注意循环里面的第一个判断:j+1 == i 这个也是为了处理当两个相同的字符连在一起的情况。

Longest Common SubString(最长公共子串)

传送门
思路:
使用一个M*N的二维数组来帮助我们,跟编辑距离差不多。

d[i][j] 代表的是 s1[0:i] 和 s2[0:j]两个子串的公共后缀长度。
d[i][j] = if s1[i] == s2[j] : d[i-1][j-1] + 1 else 0;

int LongestCommonSubString(string s1, string s2) {
	int size1 = s1.size(), size2 = s2.size();
	int* helper[size1];
	for (int i = 0; i < size1; ++i) {
		helper[i] = new int[size2];
		memset(helper[i], 0, sizeof(int) * size2);
	}
	int res = 0;
	for (int i = 0; i < size1; ++i) {
		for (int j = 0; j < size2; ++j) {
			if (s1[i] == s2[j] && i == 0 || j == 0) helper[i][j] = 1;
			else if (s1[i] == s2[j]) helper[i][j] = helper[i-1][j-1] + 1;
			else helper[i][j] = 0;
			res = res > helper[i][j] ? res : helper[i][j];
		}
	}
	return res;
}

Longest Common Subsequence(最长公共子序列)

传送门
思路:
跟上面子串差不多,也是一个M*N的二维数组,但是当s[i] != s[j] 的时候,需要记录的是 d[i][j-1] 和 d[i-1][j]的最大值,为的是记录之前的最长子序列,不会跟上面那样断掉。

d[i][j] = if s1[i] == s2[j] : d[i-1][j-1] + 1 else max(d[i-1][j], d[i][j-1]);

int LongestCommonSubString(string s1, string s2) {
	int size1 = s1.size(), size2 = s2.size();
	int* helper[size1];
	for (int i = 0; i < size1; ++i) {
		helper[i] = new int[size2];
		memset(helper[i], 0, sizeof(int) * size2);
	}
	int res = 0;
	for (int i = 0; i < size1; ++i) {
		for (int j = 0; j < size2; ++j) {
			if (s1[i] == s2[j] && i == 0 || j == 0) helper[i][j] = 1;
			else if (s1[i] == s2[j]) helper[i][j] = helper[i-1][j-1] + 1;
			else helper[i][j] = max(helper[i-1][j], helper[i][j-1]);
			res = res > helper[i][j] ? res : helper[i][j];
		}
	}
	return res;
}

Coin Change

传送门
思路:
如果目标是10,有1,2,5三种硬币,那么我们需要从1开始算,需要一个一维数组来帮助我们保存目标是index的时候最少需要多少枚硬币。

    int coinChange(vector& coins, int amount) {
        int size = coins.size();
        vector helper(amount+1, 0);
        for (int i = 1; i <= amount; ++i) {
            int curMin = INT_MAX;
            for (int j = 0; j < size; ++j) {
                int left = i - coins[j];
                if (left > 0 && helper[left] > 0) {
                    curMin = curMin > helper[left] + 1 ? helper[left] + 1 : curMin;
                } else if (left == 0) {
                    curMin = 1;
                    break;
                }
            }
            helper[i] = curMin == INT_MAX ? -1 : curMin;
        }
        return helper[amount];
    }

可以看到上面你的循环是从1开始的,代表我们需要从面值为1的时候开始慢慢往后算。注意当left == 0时:这个时候是代表正好有这个面值的硬币,那么最小值当然是1。直接break内层循环,如果没有找到当前这个面值合适的组合(即没有解)那么填入特殊值-1。

你可能感兴趣的:(Practice,algorithm,and,data,structure)