子串问题
LC-3 无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
来源:力扣(LeetCode)
链接:leetcode-cn.com/problems/lo…
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
使用滑动窗口方法,维护left和right两个指针,不断增加right来扩大窗口范围,当窗口不满足要求(窗口内出现重复字符)的时候,增加left来缩小窗口范围,直到窗口再次满足要求。
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> window = new HashMap<>();
int left = 0, right = 0;
int max = 0;
while (right < s.length()) {
char c = s.charAt(right);
right++;
window.put(c, window.getOrDefault(c, 0) + 1);
while (window.get(c) > 1) {
char d = s.charAt(left);
left++;
window.put(d, window.get(d) - 1);
}
max = Math.max(max, right - left);
}
return max;
}
LC-5 最长回文子串
给你一个字符串 s,找到 s 中最长的回文子串。
来源:力扣(LeetCode)
链接:leetcode-cn.com/problems/lo…
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
寻找回文串的问题核心思想是:从中间开始向两边扩散来判断回文串。对于最长回文子串,就是这个意思:
for 0 <= i < len(s):
找到以 s[i] 为中心的回文串
更新答案
但是回文串的长度可能是奇数也可能是偶数,比如abba这种情况,因此需要考虑长度是奇数和偶数两种情况:
for 0 <= i < len(s):
找到以 s[i] 为中心的回文串
找到以 s[i] 和 s[i+1] 为中心的回文串
更新答案
public String longestPalindrome(String s) {
if (s == null || s.length() < 2) return s;
String ans = "";
for (int i = 0; i < s.length(); i++) {
String s1 = palindrome(s, i, i);
String s2 = palindrome(s, i, i + 1);
ans = ans.length() > s1.length() ? ans : s1;
ans = ans.length() > s2.length() ? ans : s2;
}
return ans;
}
private String palindrome(String s, int l, int r) {
while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) {
l--;
r++;
}
return s.substring(l + 1, r);
}
LC-125 验证回文串
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
来源:力扣(LeetCode)
链接:leetcode-cn.com/problems/va…
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
使用两个指针分别指向头和尾,依次判断是否相同字符,注意要跳过非字母和数字的字符。
public boolean isPalindrome(String s) {
if (s == null || s.length() == 0) return true;
s = s.toLowerCase();
int low = 0, high = s.length() - 1;
while (low < high){
while (low < high && !Character.isLetterOrDigit(s.charAt(low)))
low++;
while (low < high && !Character.isLetterOrDigit(s.charAt(high)))
high--;
if (s.charAt(low) != s.charAt(high))
return false;
low++;
high--;
}
return true;
}
LC-647 回文子串
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
来源:力扣(LeetCode)
链接:leetcode-cn.com/problems/pa…
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
s[i..j]是否是回文串,依赖于s[i+1..j-1]是回文串且s[i] == s[j],比较容易想到动态规划解法。使用一个dp数组,用来存储子串是否是回文串。然后再遍历一遍dp数组,计算出所有回文子串的个数。
public int countSubstrings(String s) {
int n = s.length();
boolean[][] dp = new boolean[n][n];
for (int i = 0; i < n; i++) {
Arrays.fill(dp[i], true);
}
for (int i = n - 1; i >= 0; i--) {
for (int j = i + 1; j < n; j++) {
dp[i][j] = dp[i + 1][j - 1] && s.charAt(i) == s.charAt(j);
}
}
int res = 0;
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
if (dp[i][j]) {
res += 1;
}
}
}
return res;
}
LC-131 分割回文串
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
来源:力扣(LeetCode)
链接:leetcode-cn.com/problems/pa…
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这道题是动态规划和回溯法结合起来的一道题目。通过动态规划得出s[i..j]子串是否是回文串,再通过回溯找到所有可能的分隔方案。
public List<List<String>> partition(String s) {
int n = s.length();
boolean[][] dp = new boolean[n][n];
for (int i = 0; i < n; i++) {
Arrays.fill(dp[i], true);
}
for (int i = n - 1; i >= 0; i--) {
for (int j = i + 1; j < n; j++) {
dp[i][j] = dp[i + 1][j - 1] && s.charAt(i) == s.charAt(j);
}
}
List<List<String>> res = new ArrayList<>();
LinkedList<String> ans = new LinkedList<>();
dfs(s, 0, n, dp, ans, res);
return res;
}
private void dfs(String s, int i, int n, boolean[][] dp, LinkedList<String> ans, List<List<String>> res) {
if (i == n) {
res.add(new ArrayList<>(ans));
return;
}
for (int j = i; j < n; j++) {
if (dp[i][j]) {
ans.add(s.substring(i, j + 1));
dfs(s, j + 1, n, dp, ans, res);
ans.removeLast();
}
}
}
子序列问题
LC-392 判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
进阶:
如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
来源:力扣(LeetCode)
链接:leetcode-cn.com/problems/is…
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
原题目思路非常简单,利用双指针 i, j 分别指向 s, t,一边前进一边匹配子序列。
public boolean isSubsequence(String s, String t) {
int i = 0, j = 0;
while (i < s.length() && j < t.length()) {
if (s.charAt(i) == t.charAt(j)) {
i++;
}
j++;
}
return i == s.length();
}
对于进阶题目,用一个循环依次检查字符串Sk是否是T的子序列,时间复杂度是O(N2)。利用二分查找可以降低到O(MlogN)。
二分查找的思路是对T做预处理,把每一个字符出现的索引位置记录下来。在向前扫描某个字符c是否在T中时,只需要查找比j大的那个索引位置。
public boolean isSubsequence(String s, String t) {
int m = s.length(), n = t.length();
ArrayList<Integer>[] index = new ArrayList[256];
for (int i = 0; i < n; i++){
char c = t.charAt(i);
if (index[c] == null) index[c] = new ArrayList<>();
index[c].add(i);
}
int j = 0;
for (int i = 0; i < m; i++) {
char c = s.charAt(i);
if (index[c] == null) return false;
int pos = left_bound(index[c], j);
if (pos == index[c].size()) return false;
j = index[c].get(pos) + 1;
}
return true;
}
private int left_bound(ArrayList<Integer> list, int target) {
int low = 0, high = list.size();
while (low < high) {
int mid = low + (high - low) / 2;
if (target > list.get(mid)) {
low = mid + 1;
} else {
high = mid;
}
}
return low;
}
LC-1143 最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。 两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
来源:力扣(LeetCode)
链接:leetcode-cn.com/problems/lo…
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
计算最长公共子序列(Longest Common Subsequence,简称 LCS)是一道经典的动态规划题目。
定义数组dp[i][j]表示text1[0..i-1]和text2[0..j-1]的最长功能子序列,其中text1[0..i-1]表示text1中第0个元素到第i-1个元素,text2[0..j-1]表示text2中第0个元素到第j-1个元素。
- 当
text1[i-1] == text2[j-1]时,说明两个字符串中最后一个字符相等,因此最长公共子序列增加了1,所以有dp[i][j] = dp[i-1][j-1] + 1。 - 否则,说明最后一个字符不相等,分别把最后一个字符放进去看哪个的最长公共子序列更长,所以有
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1])。
初始化要考虑当 i = 0 与 j = 0 时, dp[i][j]应该取值为多少。
当 i = 0 时,dp[0][j] 表示的是 text1 中取空字符串跟 text2 的最长公共子序列,结果肯定为 0.
当 j = 0 时,dp[i][0] 表示的是 text2 中取空字符串跟 text1 的最长公共子序列,结果肯定为 0.
综上,当 i = 0 或者 j = 0 时,dp[i][j] 初始化为 0.
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (text1.charAt(i - 1) == text2.charAt(j - 1))
dp[i][j] = dp[i - 1][j - 1] + 1;
else
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
return dp[m][n];
}
LC-300 最长递增子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
来源:力扣(LeetCode)
链接:leetcode-cn.com/problems/lo…
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
定义dp[i]标识以第i个数字结尾的最长递增子序列的长度。考虑将nums[i]加入到子序列的时候,要看从0到i-1构成的各个子序列,是否能和nums[i]构成新的递增子序列,能构成的话再计算最长的那个,就是以nums[i]为结尾的最长递增子序列的长度。
状态转移方程可表示为:
dp[i] = max(dp[j]) + 1,其中0 <= j < i且nums[j] < nums[i]。
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int[] dp = new int[nums.length];
Arrays.fill(dp, 1);
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
}
int res = 0;
for (int i = 0; i < dp.length; i++) {
res = Math.max(res, dp[i]);
}
return res;
}
LC-516 最长回文子序列
给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。
来源:力扣(LeetCode)
链接:leetcode-cn.com/problems/lo…
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
定义dp[i][j]表示在子串s[i..j]中的最长回文子序列的长度。根据s[i]和s[j],可能有如下两种情况:
s[i] == s[j]:那他俩加上s[i+1..j-1]的最长回文子序列长度即可,因此有dp[i][j] = dp[i+1][j-1] + 2s[i] != s[j]:说明它们不可能同时出现在s[i..j]的最长回文子序列中,那就分别试试看哪个更大,因此有dp[i][j] = max(dp[i+1][j], dp[i][j-1])
public int longestPalindromeSubseq(String s) {
int N = s.length();
int[][] dp = new int[N][N];
// base case
for (int i = 0; i < N; i++)
dp[i][i] = 1;
for (int i = N - 1; i >= 0; i--) {
for (int j = i + 1; j < N; j++) {
if (s.charAt(i) == s.charAt(j)) {
dp[i][j] = dp[i + 1][j - 1] + 2;
} else {
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
}
}
}
return dp[0][N - 1];
}
LC-594 最长和谐子序列
和谐数组是指一个数组里元素的最大值和最小值之间的差别 正好是 1 。
现在,给你一个整数数组 nums ,请你在所有可能的子序列中找到最长的和谐子序列的长度。
数组的子序列是一个由数组派生出来的序列,它可以通过删除一些元素或不删除元素、且不改变其余元素的顺序而得到。
来源:力扣(LeetCode)
链接:leetcode-cn.com/problems/lo…
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
public int findLHS(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
int res = 0;
for (int num : map.keySet()) {
if (map.containsKey(num + 1))
res = Math.max(res, map.get(num) + map.get(num + 1));
}
return res;
}