647.回文子串
解题思路:
class Solution {
public int countSubstrings(String s) {
/**
* 1.dp[i][j] 下标i到下标j是否是回文 i <= j dp数组只维护一个上三角
* 2.if(i + 1 < j)dp[i][j] = dp[i + 1][j - 1] && (s.charAt(i) == s.charAt(j));
* else dp[i][j] = s.charAt(i) == s.charAt(j);
* 3.dp[i][i] = true;
* 4.遍历 i要用大的 j要用小的 从下到上i 从左到右j
*/
int ans = 0;
boolean[][] dp = new boolean[s.length()][s.length()];
for(int i = 0; i < s.length(); i++){
dp[i][i] = true;
ans++;
}
for(int i = s.length() - 2; i >= 0; i--){
for(int j = i + 1; j < s.length(); j++){
if(i + 1 < j) dp[i][j] = dp[i + 1][j - 1] && (s.charAt(i) == s.charAt(j));
else dp[i][j] = (s.charAt(i) == s.charAt(j));
if(dp[i][j] == true) ans++;
}
}
return ans;
}
}
516.最长回文子序列
题目链接:516. 最长回文子序列 - 力扣(Leetcode)
解题思路:
思考递推关系式的方法就是,考虑当前情况可以从哪些其他情况得到,然后这几种情况取最大取最小。基本是这个思路。
class Solution {
public int longestPalindromeSubseq(String s) {
/**
* 动态规划 这种可以不连续的子序列,就用指定的dp定义方式
* 1.dp[i][j] 从下标i到下标j的最长子序列回文长度 i <= j 上三角
* 2.if(s.charAt(i) == s.charAt(j)) dp[i + 1][j - 1] + 2;
* else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])
*/
int[][] dp = new int[s.length()][s.length()];
// 初始化
for(int i = 0; i < s.length(); i++){
dp[i][i] = 1;
}
// 递推
for(int i = s.length() - 2; i >= 0; i--){
for(int j = i + 1; j < s.length(); 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][s.length() - 1];
}
}
792.匹配子序列的单词数
题目链接:792. 匹配子序列的单词数 - 力扣(Leetcode)
解题思路:
最初想到的就是采用回溯法去进行暴力搜索,不过这个方法显然超时了,没注意到输入的数据这么大,回溯法这种时间复杂度很高的方法肯定要爆的。
class Solution {
Map<String,Integer> rcd = new HashMap<>();
StringBuilder path = new StringBuilder();
// List<Character> path = new LinkedList<>();
int ans = 0;
public int numMatchingSubseq(String s, String[] words) {
for(String ss : words){
if(!rcd.containsKey(ss)) rcd.put(ss,1);
else rcd.put(ss,rcd.get(ss) + 1);
}
// 回溯法解题
backTracking(s, 0);
return ans;
}
public void backTracking(String s, int startIndex){
String s1 = path.toString();
// System.out.println(s1);
if(rcd.containsKey(s1)){
ans += rcd.get(s1);
rcd.remove(s1);
}
// 终止条件
if(startIndex == s.length()) return;
for(int i = startIndex; i < s.length(); i++){
path.append(s.charAt(i));
backTracking(s,i + 1);
path.deleteCharAt(path.length() - 1);
}
}
}
预处理+哈希表+二分查找
class Solution {
public int numMatchingSubseq(String s, String[] words) {
// 哈希表+二分查找
int ans = 0;
Map<Character, List<Integer>> rcd = new HashMap<>();
for(int i = 0; i < s.length(); i++){
List<Integer> idxrcd = rcd.getOrDefault(s.charAt(i),new ArrayList<>());
idxrcd.add(i);
rcd.put(s.charAt(i),idxrcd);
}
for(String ss : words){
boolean ok = true;
int idx = -1;
for(int i = 0; i < ss.length() && ok; i++){
List<Integer> list = rcd.get(ss.charAt(i));
if(list == null){
ok = false;
continue;
}
if(list.size() == 0){
ok = false;
}else{
// int left = 0, right = list.size() - 1;
// // 要找第一个大于等于idx的索引
// while(left <= right){ // 维护的索引区间是所有可能的值
// int mid = left + ((right - left) >> 1);
// if(list.get(mid) > idx) right = mid - 1; // right右边都是大于等于idx的索引
// else left = mid + 1; // left左边都是小于idx的索引
// }
// // 最后结果 right + 1 = left
// if(right + 1 >= list.size() || list.get(right + 1) <= idx) ok = false;
// else idx = list.get(right + 1);
int left = 0, right = list.size();
// 要找第一个大于等于idx的索引
while(left < right){ // 维护的索引区间是所有可能的值
int mid = left + ((right - left) >> 1);
if(list.get(mid) > idx) right = mid; // right以及右边都是大于等于idx的索引
else left = mid + 1; // left左边都是小于idx的索引
}
// 最后结果 right = left
if(left > list.size() - 1 || list.get(left) <= idx) ok = false;
else idx = list.get(right);
}
}
if(ok) ans++;
}
return ans;
}
}
这个题需要复习的点:
1.这个跳出for循环的条件
boolean ok = true;
for(int i = 0; i < ss.length() && ok; i++){
if(xxxxxx) ok = false;
}
2.map的getOrDefault api
map.getOrDefault(key, defaultValue) // 有这个key就返回value,没有就用defaultValue
3.二分查找
二分查找这里卡了一小天,一直不知道问题出在哪里,一直超时,最后查出问题所在了
int mid = left + (right - left) >> 1;
int mid = left + ((right - left) >> 1);
上面的是错误写法,忘记加括号了。导致二分不正确。
还有就是二分之后应该如何判断最后我们要的值是left指向的值还是right指向的值
在帖子里看到了一个比较好的思路,以这个题为例
int left = 0, right = list.size();
// 要找第一个大于等于idx的索引
while(left < right){ // 维护的索引区间是所有可能的值
int mid = left + ((right - left) >> 1);
if(list.get(mid) > idx) right = mid; // right以及右边都是大于等于idx的索引
else left = mid + 1; // left左边都是小于idx的索引
}
// 最后结果 right = left
if(left > list.size() - 1 || list.get(left) <= idx) ok = false;
else idx = list.get(right);
首先是左闭右开的情况,从while循环中的if和else出发
right指针元素以及其右侧的元素都是大于idx的索引。
left左侧的元素都是小于idx的索引。
因为while循环结束时指针一定是left=right,所以left指针左侧都是小于idx的索引。left以及left指针右侧都是大于idx的索引。
int left = 0, right = list.size() - 1;
// 要找第一个大于等于idx的索引
while(left <= right){ // 维护的索引区间是所有可能的值
int mid = left + ((right - left) >> 1);
if(list.get(mid) > idx) right = mid - 1; // right右边都是大于等于idx的索引
else left = mid + 1; // left左边都是小于idx的索引
}
// 最后结果 right + 1 = left
if(right + 1 >= list.size() || list.get(right + 1) <= idx) ok = false;
else idx = list.get(right + 1);
如果是左闭右闭的情况
right右边都是大于等于idx的索引
left左边都是小于idx的索引
while循环结束时的情况一定是right + 1 = left,所以left指针指向的元素,就是第一个大于idx的索引。
也就是right + 1;