leetcode动态规划——字符串系列

139. 单词拆分

题目
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明:

  • 拆分时可以重复使用字典中的单词。
  • 你可以假设字典中没有重复的单词。

示例 1:

输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true
因为 “leetcode” 可以被拆分成 “leet code”。
示例 2:

输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
输出: true
解释: 返回true 因为 “applepenapple” 可以被拆分成 “apple pen apple”。
注意你可以重复使用字典中的单词。
示例 3:

输入: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出: false

dp[i]:子串s[0,i-1] 是否可以被空格拆分为一个或多个在字典中出现的单词
dp[i]= dp[j]&check(j,i-1)
check(j,i-1):s[j,i-1]是否包含于字典中。

注意:先给字典通过set去重,能加快处理时间。
这是对比图:
在这里插入图片描述

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        HashSet<String> wordSet=new HashSet<>(wordDict);
        int n=s.length();
        boolean[] dp=new boolean[n+1];
        dp[0]=true;
        for(int i=1;i<=n;i++){
            for(int j=0;j<i;j++){
                if(dp[j]&&(wordSet.contains(s.substring(j,i)))){
                    dp[i]=true;
                    break;
                }
            }
        }
        return dp[n];
    }
}

面试题 17.13. 恢复空格

题目
哦,不!你不小心把一个长篇文章中的空格、标点都删掉了,并且大写也弄成了小写。像句子"I reset the computer. It still didn’t boot!“已经变成了"iresetthecomputeritstilldidntboot”。在处理标点符号和大小写之前,你得先把它断成词语。当然了,你有一本厚厚的词典dictionary,不过,有些词没在词典里。假设文章用sentence表示,设计一个算法,把文章断开,要求未识别的字符最少,返回未识别的字符数。

注意:本题相对原题稍作改动,只需返回未识别的字符数

示例:

输入: dictionary = [“looked”,“just”,“like”,“her”,“brother”]
sentence =“jesslookedjustliketimherbrother”
输出: 7
解释: 断句后为"jess looked just like tim her brother",共7个未识别字符。

这道题动态规划的思想和上面一题整体是相似的。
这里要具体的分割数值,所以dp用来记录。
dp[i]:表示考虑前 i 个字符最少的未识别的字符数量,从前往后计算 dp 值。

  • if 第 j(j≤i) 个到第i个字符组成的子串sentence[j−1⋯i−1](注意字符串下标从 0 开始)能在词典中找到:dp[i]=min(dp[i],dp[j−1])
  • else :dp[i]=dp[i-1]+1

因此,问题转化为了怎样判断子串sentence[j−1⋯i−1]是否在字典中。
这里使用trie树优化。参考题解
trie树学习
leetcode–trie树集合
注意:因为要从第i-1个字符向前遍历trie树,因此tried树应该倒序插入。

class Solution {
    public int respace(String[] dictionary, String sentence) {
        //构建字典树
        Trie trie=new Trie();
        for(String word:dictionary){
            trie.insert(word);
        }

        //动态规划
        int n=sentence.length();
        int[] dp=new int[n+1];
        for(int i=1;i<=n;i++){
            dp[i]=dp[i-1]+1;
            for(int idx:trie.search(sentence,i-1)){
                dp[i]=Math.min(dp[i],dp[idx]);
            }
        }
        return dp[n];
    }
}

class TrieNode{
    boolean isWord;
    TrieNode[] children=new TrieNode[26];

    public TrieNode() {}
}

class Trie{
    TrieNode root;
    public Trie(){
        root=new TrieNode();
    }

    //单词倒序插入字典树
    public void insert(String word){
        TrieNode cur=root;
        for(int i=word.length()-1;i>=0;i--){
            int c=word.charAt(i)-'a';
            if(cur.children[c]==null){
                cur.children[c]=new TrieNode();
            }
            cur=cur.children[c];
        }
        cur.isWord=true;
    }

    // 找到 sentence中以endPos为结尾的单词,返回这些单词的开头下标
    public List<Integer> search(String sentence,int endPos){
        List<Integer> indicies=new ArrayList<>();
        TrieNode cur=root;
        for(int i=endPos;i>=0;i--){
            int c=sentence.charAt(i)-'a';
            if(cur.children[c]==null){
                break;
            }
            cur=cur.children[c];
            if(cur.isWord){
                indicies.add(i);
            }
        }
        return indicies;
    }
    
}

392. 判断子序列

题目
在这里插入图片描述
方法一:双指针

class Solution {
public:
    bool isSubsequence(string s, string t) {
        int n1=s.length(),n2=t.length();
        int i=0,j=0;
        while(i<n1&&j<n2){
            if(s[i]==t[j]){
                i++;
            }
            j++;
        }
        if(i==n1) return true;
        return false;
    }
};

但是如果要解决后续挑战,该方法是不可以的。

方法二:动态规划

分析方法一,对于k个要查询的字符串,大量的时间用于寻找s的下一个字符在t中的位置。那么可以通过动态规划预处理出对于每个位置,26个字母将会出现的下一个位置。
f [ i ] [ j ] : f[i][j]: f[i][j]: 从位置 i i i开始,下一个字母 j j j出现的位置
在这里插入图片描述
边界采用 f [ m ] [ j ] = m f[m][j]=m f[m][j]=m表示。

class Solution {
public:
    bool isSubsequence(string s, string t) {
        int n=s.length(),m=t.length();
        vector<vector<int>> f(m+1,vector<int>(26));
        for(int j=0;j<26;j++){
            f[m][j]=m;
        }
        for(int i=m-1;i>=0;i--){
            for(int j=0;j<26;j++){
                if((t[i]-'a')==j){
                    f[i][j]=i;
                }else{
                    f[i][j]=f[i+1][j];
                }
            }
        }

        int next=0;
        for(int i=0;i<n;i++){
            if(f[next][s[i]-'a']==m) return false;
            next=f[next][s[i]-'a']+1;
        }
        return true;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值