leetcode-zgd-day57-647.回文子串/516.最长回文子序列/792.匹配子序列的单词数

64 阅读2分钟

647.回文子串

题目链接:647. 回文子串 - 力扣(Leetcode)

解题思路:

 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;