• Longest XXX


    Longest Common Substring

    • Brute Force
      遍历ab所有位置的组合,向后延伸,直到遇到两个不同的字符,复杂度是(n^3)级别。
    class Solution {
        public:
        // 返回所有结果
        vector<string> longestCommonSubstring(string& a, string& b) {
            vector<string> ans;
            int maxLen = 0;
            for (int i = 0; i < a.size(); ++i) {
                for (int j = 0; j < b.size(); ++j) {
                    string cur;
                    for (int m = i, n = j; m < a.size() && n < b.size(); ++m, ++n) {
                        if (a[m] != b[n]) {
                            break;
                        }
                        cur += a[m];
                    }
                    if (cur.size() && cur.size() >= maxLen) {
                        maxLen = cur.size();
                        ans.push_back(cur);
                    }
                }
            }
            return ans;
        }
    };
    
    • DP
      暴力解有很多重复计算:比如以(i)(j)为起点去向后延伸,我们可能需要比较(i+1)(j+1)(i+2)(j+2)...而以(i+1)(j+1)为起点时,仍然要比较(i+2)(j+2)重叠子问题给动态规划带来了可能。
      暴力做法是将每个(i)(j)作为起点,现在我们考虑将(i)(j)作为终点,令(L(i,j))表示以(i)(j)作为结尾的相同子串的最大长度,很快就可以推出:

    [L(i,j)= egin{cases} 1+L(i-1,j-1)& ext{a[i]=b[j]}\ 0& ext{a[i]!=b[j]} end{cases}]

    为了简便,假设下标从1开始,那么边界条件:(L(0,j)=0,L(i,0)=0)

    class Solution {
    public:
    	int longestCommonSubstring(string& a, string& b) {
    		vector<vector<int>> L(1 + a.size(), vector<int>(1 + b.size(), 0));
    		int maxLen = 0;
    
    		for (int i = 1; i <= a.size(); ++i) {
    			for (int j = 1; j <= b.size(); ++j) {
    				if (a[i - 1] == b[j - 1]) {
    					L[i][j] = 1 + L[i - 1][j - 1];
    				}
    				else {
    					L[i][j] = 0;
    				}
    				maxLen = max(maxLen, L[i][j]);
    			}
    		}
    		
    		return maxLen;
    	}
    };
    
    

    显然时间降为(O(n^2)),空间升为(O(n^2))。仔细观察,计算(L(i,j))只需要左上方(L(i-1,j-1))的信息,所以我们按照斜线方向计算,可以将空间优化到(O(1))

    class Solution {
    public:
    	int longestCommonSubstring(string& a, string& b) {
    		// from the up-right corner
    		int row = 0, col = b.size() - 1;
    		int maxLen = 0;
    
    		while (row < a.size()) {
    			int curLen = 0;
    			for (int i = row, j = col; i < a.size() && j < b.size(); ++i, ++j) {
    				if (a[i] == b[j]) {
    					++curLen;
    				}
    				else {
    					curLen = 0;
    				}
    				maxLen = max(maxLen, curLen);
    			}
    			if (col > 0) {  
    				--col;  // 斜线左移
    			}
    			else {
    				++row;   // 斜线下移
    			}
    		}
    		
    		return maxLen;
    	}
    };
    
    

    (TODO)输出所有的最长公共子串

    Longest Common Subsequence

    • Brute Force
      找到a的所有子序列,判断是否是b的子序列,指数级复杂度,也就没有写出来的必要了。
    • DP
      重叠子问题很明显,而且LCS具有最优子结构,令(L(i,j))表示以(i)(j)作为结尾的LCS长度:

    [L(i,j)= egin{cases} 1+L(i-1,j-1)& ext{a[i]=b[j]}\ max{L(i-1,j),L(i,j-1)}& ext{a[i]!=b[j]} end{cases}]

    为了简便,假设下标从1开始,那么边界条件:(L(0,j)=0,L(i,0)=0)

    class Solution {
    public:
        int longestCommonSubsequence(string text1, string text2) {
            const int m = text1.size(), n = text2.size();
            vector<vector<int>> L(m + 1, vector<int>(n + 1, 0));
            int ans = 0;
            for(int i = 1;i <= m;++i) {
                for(int j = 1;j <= n;++j) {
                    if(text1[i - 1] == text2[j - 1]) {
                        L[i][j] = 1 + L[i - 1][j - 1];
                    }
                    else {
                        L[i][j] = max(L[i - 1][j], L[i][j - 1]);
                    }
                    ans = max(ans, L[i][j]);
                }
            }
            
            return ans;
        }
    };
    

    时间和空间都是(O(mn))。类似的,(L(i,j))依赖于左上角(L(i-1,j-1))、左边(L(i,j-1))、上边(L(i-1,j)),可以只存储上一行和当前行的(L)。进一步考虑:可以只存储当前行的(L),外加一个变量(pre)存储左上角(L(i-1,j-1)),空间可以优化到(O(min(m,n)))

    class Solution {
    public:
        int longestCommonSubsequence(string text1, string text2) {
            const int m = text1.size(), n = text2.size();
            if(m < n) {
                return longestCommonSubsequence(text2, text1);
            }
            
            vector<int> curRow(n + 1, 0);
            int pre = 0, ans = 0;
            for(int i = 1;i <= m;++i) {
                pre = curRow[0];
                for(int j = 1;j <= n;++j) {
                    int tmp = curRow[j];
                    if(text1[i - 1] == text2[j - 1]) {
                        curRow[j] = 1 + pre;
                    }
                    else {
                        curRow[j] = max(curRow[j], curRow[j - 1]);
                    }
                    ans = max(ans, curRow[j]);
                    pre = tmp;
                }
            }
            
            return ans;
        }
    };
    

    (TODO)输出所有的最长公共子串

    Longest Increasing Subsequence

    • DP
      (dp[i])表示只考虑(i)及之前的信息,所形成的LIS的长度:

    [dp[i]=max{dp[i],1+dp[j]}, 0leq j<i,a[i]>a[j] ]

    最终答案即是(dp)数组的最大值:

    class Solution {
    public:
        int lengthOfLIS(vector<int>& nums) {
            if(!nums.size()) {
                return 0;
            }
            vector<int> dp(nums.size(), 1);
            int ans = 1;
            
            for(int i = 1;i < nums.size();++i) {
                int curMax = 0;
                for(int j = 0;j < i;++j) {
                    if(nums[i] > nums[j]) {
                        curMax = max(curMax, dp[j]);
                    }
                }
                dp[i] = 1 + curMax;
                ans = max(ans, dp[i]);
            }
            
            return ans;
        }
    };
    

    时间(O(n^2)),空间(O(n))

    • DP+Binary Search
      遍历数组的过程中,不停填充(dp)数组,维护(dp)数组使得其存储递增序列:
      如果(nums[i]<dp[0]),更新(dp[0])
      如果(nums[i]>dp[len])(++len),并且存入(nums[i])
      如果(nums[i])介于(dp)中间,就二分查找(nums[i])的位置,并更新相应的(dp)值。
      举例来说,(nums=[0,8,4,12,2])(dp)数组:
      ([0])
      ([0,8])
      ([0,4])
      ([0,4,12])
      ([0,2,12])
      虽然(dp)数组最终存储的不是LIS,但长度确是LIS的长度:
    class Solution {
    public:
        int lengthOfLIS(vector<int>& nums) {
            const int n = nums.size();
            if(0 == n)
                return 0;
            
            vector<int> dp(n);
            dp[0] = nums[0];
            int len = 0;
            for(int i = 1;i < n;++i) {
                if(nums[i] < dp[0]) {
                    dp[0] = nums[i];
                }
                else if(nums[i] > dp[len]) {
                    dp[++len] = nums[i];
                }
                else {
                    int index = biSearch(dp, 0, len, nums[i]);
                    dp[index] = nums[i];
                }
            }
            return len + 1;
        }
    private:
        int biSearch(vector<int>& nums, int l, int r, int target) {
            while(l < r) {
                int m = l + (r - l) / 2;
                if(nums[m] == target) {
                    return m;
                }
                if(target > nums[m]) {
                    l = m + 1;
                }
                else {
                    r = m;
                }
            }
            return l;
        }
    };
    

    时间(O(nlgn)),空间(O(n))

    Longest Palindromic Substring

    • Brute Force
      枚举每个子串的起始和结束位置,判断是否回文。时间(O(n^3)),空间(O(1))
    • DP
      假设输入ababa,如果我们已经判断了bab是回文的,那么ababa就不需要再扫描一遍,因为两端都是a
      所以一个很直观的动规:
      (dp(i,j))记忆(i)(j)之间的串是否回文,那么转移方程:

    [dp(i,j)=dp(i+1,j-1)&&s[i]=s[j] ]

    边界条件(dp(i,i)=true,dp(i,i+1)=(s[i]=s[i+1]))

    // 填充方向:由边界条件dp(i,i)向其他地方扩展,只需要填充j>i的三角形部分
    class Solution {
    public:
        string longestPalindrome(string s) {
            vector<vector<bool>> dp(s.length() + 1, vector<bool>(s.length() + 1, false));
            int start = 0, end = 0;
            int maxLen = 1;
            for (int i = 0; i < s.length(); ++i) {
                dp[i][i] = true;
                if (i < s.length() - 1) {
                    if (s[i] == s[i + 1]) {
                        dp[i][i + 1] = true;
                        start = i;
                        end = i + 1;
                    }
                    else {
                        dp[i][i + 1] = false;
                    }
                }
            }
            
            for (int i = s.length() - 1; i >= 0; --i) {
                for (int j = i + 2; j < s.length(); ++j) {
                    if (s[i] == s[j]) {
                        dp[i][j] = dp[i + 1][j - 1];
                        if (dp[i][j] && maxLen < j - i + 1) {
                            start = i;
                            end = j;
                            maxLen = end - start + 1;
                        }
                    }
                    else {
                        dp[i][j] = false;
                    }
                }
            }
            return s.substr(start, end - start + 1);
        }
    };
    

    时间(O(n^2)),空间(O(n^2))

    • Expand Around Center
      回文串都是镜像对称的,可以遍历整个串,从当前位置向两边延伸,直到遇到不相等的字母。这里要考虑字符串长度的奇偶:
    class Solution {
    public:
    	string longestPalindrome(string s) {
    		const int n = s.length();
    		if (!n)
    			return "";
    
    		int start = 0, end = 0;
    		for (int i = 0; i < n; ++i) {
    			int len1 = expandCenter(s, i, i);
    			int len2 = expandCenter(s, i, i + 1);
    			int len = max(len1, len2);
    			if (len > end - start + 1) {
    				start = i - (len - 1) / 2;
    				end = i + len / 2;
    			}
    		}
    		return s.substr(start, end - start + 1);
    	}
    private:
    	int expandCenter(string s, int start, int end) {
    		while (start >= 0 && end < s.length() && s[start] == s[end]) {
    			--start;
    			++end;
    		}
    		return end - start - 1;
    	}
    };
    

    时间(O(n^2)),空间(O(1))

    • Manacher's Algorithm(待填)
      时间(O(n))

    Longest Palindromic Subsequence

    • DP
      (dp(i,j))表示介于(i)(j)间的LPS的长度,那么状态转移方程:

    [dp(i,j)= egin{cases} 2+dp(i+1,j-1)& ext{s[i]=s[j]}\ max{dp(i+1,j),dp(i,j-1)}& ext{s[i]$ eq$s[j]} end{cases}]

    class Solution {
    public:
        int longestPalindromeSubseq(string s) {
            const int n = s.size();
            if(!n) {
                return 0;
            }
            
            vector<vector<int>> dp(n, vector<int>(n, 0));
            int ans = 1;
            for(int i = n - 1;i >= 0;--i) {
                for(int j = i;j < n;++j) {
                    if(i == j) {
                        dp[i][j] = 1;
                        continue;
                    }
                    if(s[i] == s[j]) {
                        dp[i][j] = dp[i + 1][j - 1] + 2;
                    }
                    else {
                        dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
                    }
                    ans = max(ans, dp[i][j]);
                }
            }
            
            return ans;
        }
    };
    

    时间(O(n^2)),空间(O(n^2))
    同样,空间可以优化到(O(n))

    class Solution {
    public:
        int longestPalindromeSubseq(string s) {
            const int n = s.size();
            if(!n) {
                return 0;
            }
            
            vector<int> dp(n, 0);
            int ans = 1, pre = 0;
            for(int i = n - 1;i >= 0;--i) {
                pre = dp[0];
                for(int j = i;j < n;++j) {
                    int tmp = dp[j];
                    if(i == j) {
                        dp[j] = 1;
                        continue;
                    }
                    if(s[i] == s[j]) {
                        dp[j] = pre + 2;
                    }
                    else {
                        dp[j] = max(dp[j - 1], dp[j]);
                    }
                    pre = tmp;
                    ans = max(ans, dp[j]);
                }
            }
            
            return ans;
        }
    };
    
  • 相关阅读:
    Python字典dict对象方法总结
    PythonString字符串的相关方法
    Mysql5.7.20使用group by查询(select *)时出现错误修改sql mode
    HtmlTestRunner无法生成HTML报告问题
    话说 type 之 record 记录的使用技巧 F#
    Silverlight OOB 获取桌面可视尺寸 F# PInvoke
    目前让 F# 支持 Silverlight 5 的解决方案(包括 lazy 不可用)
    话说 type 之 let 绑定与 val 显式字段 F#
    这两天自己模仿写的一个Asp.Net的显示分页方法 附加实体转换和存储过程 带源码下载
    Asp.net 在三层架构中事务的使用
  • 原文地址:https://www.cnblogs.com/EIMadrigal/p/12460777.html
Copyright © 2020-2023  润新知