精选: 高级解法 判断子序列, 画图解析动态规划, 容易理解

思路与说明:

说明: 当长串t不变, 有大量的短串需要判断 (比如上亿级别的), 使用此法效率非常高, 可以节省大量时间.

注意: 题目原本说字符串只包含小写字母, 但实际测试用例里面有包含了数字字符的, 对如下代码做一些修改后, 也能解决.
  不做修改的话, 只适用于小写字母的情况.
思路: 举例字符串t是"abcabdd", 使用二维数组d[26][t.length()] 来预处理, 
经过预处理后, 再对子串进行判断, 效率将非常高, 分析如下图:

主要对应代码
for (int i = 0; i < 26; i++) {
    d[i][t.length()] = -1;
}

主要对应代码
for (int i = 25; i >= 0; i--) {
    for (int j = t.length() - 1; j >= 0; j--) {
        if (t.charAt(j) - 'a' == i) {
            d[i][j] = j;
        } else {
            d[i][j] = d[i][j + 1];
        }
    }
}

完整代码:

//方法一: 动态规划
public boolean isSubsequence(String s, String t) {
    if (s == null || t == null || s.length() > t.length()) return false;
    int[][] d = new int[26][t.length() + 1];
    for (int i = 0; i < 26; i++) {
        d[i][t.length()] = -1;
    }
    //小循环在外层效率更好, 请思考: 可以只用一层循环吗? 或者还可以减少循环次数吗? 
    for (int i = 25; i >= 0; i--) {
	   //倒序遍历, 从后往前推
        for (int j = t.length() - 1; j >= 0; j--) {
            if (t.charAt(j) - 'a' == i) {
                d[i][j] = j;
            } else {
                d[i][j] = d[i][j + 1];
            }
        }
    }
    int c = 0;  //c是列, 长串t中的索引
    for (int i = 0; i < s.length(); i++) {
        int r = s.charAt(i) - 'a';
        //等于 -1时, 长串t 后面不再有该字符
        if (d[r][c] == -1) {
            return false;
        }
        c = d[r][c] + 1;
    }
    return true;
}

// 方法二
public boolean isSubsequence(String s, String t) {
    if (s.length() > t.length()) return false;
    List<Integer>[] buckets = new List[26];
    for (int i=0; i<t.length(); i++){
        int k = t.charAt(i) - 'a';
        if (buckets[k] == null){
            buckets[k] = new ArrayList<Integer>();
        }
        buckets[k].add(i);
    }
    //长串里上一个匹配的索引
    int preMatchIndex = -2;
    for (int i=0; i<s.length(); i++){
        char ch = s.charAt(i);
        //获取该字符 在长串里对应的所有索引
        List<Integer> list = buckets[ch-'a'];
        if (list == null || list.size() == 0){
            return false;
        }
        int j = find(list, Math.max(i, preMatchIndex+1));
        if (j < 0){
            return false;
        }
        preMatchIndex = list.get(j);
    }
    return true;
}
public int find(List<Integer> list, int target){
    if (list == null || list.size() == 0){
        return -1;
    }
    int left = 0, right = list.size()-1;
    int ans = -1;
    while (left <= right){
        int mid = (left+right)/2;
        //如果目标值<=中间值, 则找到一个元素,
        //然后继续在左半部分找, 看是否还有其他更小的元素 也满足该条件
        if (target <= list.get(mid)){
            ans = mid;
            right = mid-1;
        }else {
            left = mid+1;
        }
    }
    return ans;
} 
//方法三 双指针
public boolean isSubsequence(String s, String t) {
    if (s.length() > t.length()) return false;
    int i=0, j=0;
    while(i<s.length() && j<t.length()){
        //如果在长串中找到一个字符 与短串的相等,
        // i与j都右移一位, 否则只有j右移一位
        if (s.charAt(i) == t.charAt(j)){
            i++;
        }
        j++;
    }
    //上面循环结束后,如果i能到达短串的末尾,
    //说明短串里的字符全都匹配到, 才是子序列
    if ( i==s.length()){
        return true;
    }
    return false;
}

题后挑战与思考:

在对长串进行预处理环节, 可以只遍历一次吗? 或者还可以减少循环次数吗?

欢迎留言与讨论, 相互学习, 相互帮助, 共同进步.

花了很多时间来解的.
如该解法能帮助到您, 或感觉不错, 如有java工作, 欢迎联系推荐下, 或评论区留言, 谢谢.
本人有好几年经验, 目前待业中, 希望能找到java技术岗位的工作.
#牛客创作赏金赛##我的求职思考##在找工作求抱抱##悬赏#
全部评论
欢迎留言与讨论, 相互学习, 相互帮助, 共同进步.
点赞 回复 分享
发布于 2024-09-24 14:26 北京

相关推荐

评论
1
1
分享

创作者周榜

更多
牛客网
牛客企业服务