2024.1.9力扣每日一题——字符串中的额外

2024.1.9

      • 题目来源
      • 我的题解
        • [方法一 集合+动态规划](#方法一 集合+动态规划)
        • [方法二 字典树+动态规划](#方法二 字典树+动态规划)

题目来源

力扣每日一题;题序:2707

我的题解

方法一 集合+动态规划

题目的要求是讲字符串s划分为多个不重叠的子串,然后尽可能使得更多的子串匹配到dictionary中的字符串,然后将这些匹配的子串删除之后,使得剩下的额外字符最小。

可以假设s的长度为n,分割的方式:

  • 把s的最后一个字符sn-1当做是额外字符,这问题转化为求长度为n-1的子问题;
  • 找到一个s的后缀sj...n-1构成的子串在dictionary中,这问题转换为j-1的子问题。

因此,可以将得出动态规划的转移方程:

          d p i = d p i − 1 + 1 , s i − 1 当做额外字符 dpi=dpi-1+1,si-1当做额外字符 dpi=dpi−1+1,si−1当做额外字符

          d p i = m i n ( d p i , d p j ) , s 的后缀 s j . . . n − 1 构成的子串在 d i c t i o n a r y 中 dpi=min(dpi,dpj),s的后缀sj...n-1构成的子串在dictionary中 dpi=min(dpi,dpj),s的后缀sj...n−1构成的子串在dictionary中

为了判断子串是不是在dictionary中,将dictionary中的字符串使用HashSet集合存储。
时间复杂度 :O( n 3 + m n^3+m n3+m)。n为s的长度,m为dictionary的长度。m是初始化HashSet的时间; n 3 n^3 n3分别是递推需要O(n),每次子问题需要O(n),并且求子串需要O(n)。
空间复杂度:O(n+m)。dp数组的空间和HashSet的空间。

java 复制代码
public int minExtraChar(String s, String[] dictionary) {
	int n=s.length();
   int m=dictionary.length;
   int[] dp=new int[n+1];
   Arrays.fill(dp,Integer.MAX_VALUE);
   Set<String> set=new HashSet<>();
   for(int i=0;i<m;i++){
       set.add(dictionary[i]);
   }
   dp[0]=0;
   for(int i=1;i<=n;i++){
   		// s[i-1]当做额外字符
       dp[i]=dp[i-1]+1;
       for(int j=i-1;j>=0;j--){
       		//s的后缀s[j...n-1]构成的子串在dictionary中
           if(set.contains(s.substring(j,i))){
               dp[i]=Math.min(dp[i],dp[j]);
           }
       }
   }
   return dp[n];
}
方法二 字典树+动态规划

在方法一中查找某个子串是否在dictionary中效率较低,因为查找了si+1,j,再次查找si,j,这时后者仅仅第一个字符不同,但是仍需要从开始开始对比,要花费O(n)的时间重复查找。

可以使用字典树存储dictionary中字符串的逆序,这样在字典树上找到表示后缀si+1,j的节点后,只需要再花O(1)的时间来判断表示后缀si,j的节点是否存在。
时间复杂度:O( n 2 + ∣ S ∣ n^2+|S| n2+∣S∣)。|S|表示dictionary中最长字符串的长度

空间复杂度:O(n+∣T∣⋅Σ)。|T|表示dictionary中所有字符串的长度之和,Σ为字符集的大小,这里是26。

java 复制代码
 public int minExtraChar(String s, String[] dictionary) {
int n=s.length();
    int m=dictionary.length;
    int[] dp=new int[n+1];
    Arrays.fill(dp,Integer.MAX_VALUE);
    Trie t=new Trie();
    // 将dictionary中的字符串逆序插入字典树
    for(int i=0;i<m;i++){
        StringBuilder sb=new StringBuilder(dictionary[i]);
        t.insert(sb.reverse().toString());
    }
    dp[0]=0;
    for(int i=1;i<=n;i++){
        dp[i]=dp[i-1]+1;
        Trie node=t;
        for(int j=i-1;j>=0;j--){
        // 若当前节点为空表示在dictionary中找不到s[j,i]的字符串
            if(node!=null){
            // 看下一个字典树节点是不是存在
                node=node.searchSuffix(s.charAt(j));
                if(node!=null&&node.isEnd()){
                    dp[i]=Math.min(dp[i],dp[j]);
                }
            }
        }
    }
    return dp[n];
}
class Trie {
    Trie children[];//子节点
    boolean isEndOf;//是否是结尾
    public Trie() {
        children=new Trie[26];
        isEndOf=false;
    }
    //插入新的字符串
    public void insert(String word) {
        Trie cur=this;
        for(int i=0;i<word.length();i++){
            int ch=word.charAt(i)-'a';
            if(cur.children[ch]==null){//若没有该分支,则新建分支
                cur.children[ch]=new Trie();
            }
            cur=cur.children[ch];
        }
        cur.isEndOf=true;//记录结尾
    }
    //判断当前字典树节点是不是一个结尾
    public boolean isEnd(){
        return isEndOf;
    }
    private Trie searchSuffix(char ch){//查找ch为结尾的字典树节点
        Trie cur=this;
        if(cur==null||cur.children[ch-'a']==null){
            return null;
        }
        cur=cur.children[ch-'a'];
        return cur;
    }
}

有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~

相关推荐
野生技术架构师5 分钟前
Spring Boot 实现数据脱敏:自定义注解 + Jackson 序列化器
java·spring boot·后端
AI人工智能+电脑小能手13 分钟前
大白话说Java设计模式-28-模板方法模式(业务实战篇)
java·设计模式·模板方法模式·spring源码·订单系统·代码复用
奥莱维16 分钟前
KNX酒店方案_KNX专用线与高端酒店技术逻辑
java·服务器·前端·数据库
_Narcissus_23 分钟前
枚举和模拟算法笔记
c语言·数据结构·c++·笔记·算法·模拟·枚举
工业一体机老司机25 分钟前
Linux工业一体机自启动服务配置:systemd服务单元编写与开机优化实战
java·linux·服务器
天疆说29 分钟前
策略的进化:从随心所欲到稳健前行
算法
冻柠檬飞冰走茶38 分钟前
《数据结构实验指导-C++语言版》 在顺序表 list 中查找元素 x
开发语言·数据结构·c++·算法·list
weixin_538601971 小时前
智能体测开Day56
java
杨丰玮4181 小时前
从零手写Java飞机躲障碍游戏|Swing绘图、鼠标跟随、计时器碰撞检测实战(五)
java·python·游戏·游戏引擎·图形渲染·动画·贴图
莫得感情 o2 小时前
踩坑 - 压测三轮后 502:一个默认 10 的连接池如何拖垮整个服务
java