力扣算法学习day30-2

文章目录

  • 力扣算法学习day30-2
    • 139-单词拆分 --加时完成
      • 题目
      • 代码实现

力扣算法学习day30-2

139-单词拆分 --加时完成

题目

力扣算法学习day30-2_第1张图片力扣算法学习day30-2_第2张图片

代码实现

class Solution {
    // 回溯1:
    // 能直接想到的,像这种排列问题,就用回溯最直接,然后,案例都通过了,提交就超时了。。尝试使用
    // StringBuilder优化(一开始直接用的String,利用String局部变量比较省事),但是还是超时了。
    // public boolean wordBreak(String s, List wordDict) {
    //     boolean result = recall(s,wordDict);

    //     return result;
    // }
    // StringBuilder temp = new StringBuilder();
    // public boolean recall(String s,List wordDict){
    //     if(temp.length() > s.length()){
    //         return false;
    //     } else if(temp.length() == s.length()){
    //         return temp.toString().equals(s);
    //     }

    //     for(String str : wordDict){
    //         temp.append(str);
    //         if(recall(s,wordDict)){
    //             return true;
    //         }
    //         temp.delete(temp.length()-str.length(),temp.length());
    //     }

    //     return false;
    // }





    // 回溯法2:还回溯通过分割s来匹配是否出现在字典中,速度可能会更快。
    // 结果在很多个a那个案例下没有通过,看了哈其他人回溯的方法是使用记忆化数组,我也试一下,写在下面。
    // public boolean wordBreak(String s, List wordDict) {
    //     Set set = new HashSet<>(wordDict);
    //     boolean result = recall(0,s,set);

    //     return result;
    // }
    // public boolean recall(int index,String s,Set wordDict){
    //     if(index >= s.length()){
    //         return true;
    //     }

    //     for(int i = index;i < s.length();i++){
    //         String subS = s.substring(index,i+1);

    //         if(wordDict.contains(subS)){
    //             if(recall(i+1,s,wordDict)){
    //                 return true;
    //             }
    //         }
    //     }

    //     return false;
    // }






    // 回溯法2改进,记忆化搜索+回溯,通过,5ms
    // 我尝试将memory改成boolean数组,速度没有变化,空间没有变化   -_-.....
    // public boolean wordBreak(String s, List wordDict) {
    //     Set set = new HashSet<>(wordDict);
    //     // 使用memory数组记录一定在字典中找不到全部的起始点index。
    //     int[] memory = new int[s.length()];
    //     boolean result = recall(0,memory,s,set);

    //     return result;
    // }
    // public boolean recall(int index,int[] memory,String s,Set wordDict){
    //     if(index >= s.length()){
    //         return true;
    //     }
    //     if(memory[index] == -1){
    //         return false;
    //     }

    //     for(int i = index;i < s.length();i++){
    //         String subS = s.substring(index,i+1);

    //         if(wordDict.contains(subS) && recall(i+1,memory,s,wordDict)){
    //            return true;
    //         }
    //     }

    //     // 走到这里说明index为起点无法完全在字典中找到,下一次到这里可以直接返回false了。
    //     memory[index] = -1;
    //     return false;
    // }


    // dp, 速度 8ms
    public boolean wordBreak(String s, List<String> wordDict) {
        Set<String> set = new HashSet<>(wordDict);

        // 题目分析:要求用字典中的元素拼出s,实际上类似于求和,类似于将元素相加看是否和为s,
        // 所以考虑背包解法,字典元素可以取多次,所以考虑使用完全背包。那么问题来了,这个求和
        // 和数值求和又有些不一样,所以需要转化一下思路。
        // 思路:这里要求拼接元素为s,s则为背包容量,那么容量的话不是具体的值,而是类似于形状这种
        // 概念,就是需要形状完全匹配才行,形状完全匹配(也就是字符串相同)相当于刚好装满,否则就装
        // 不下。然后就先当成完全背包来做,具体的找前面元素的dp情况来做判断和遍历顺序还需要分析。

        // 1.创建dp数组,dp[i]表示字符串s截取的0到i-1部分是否能被字典元素拼接。
        boolean[] dp = new boolean[s.length()+1];

        // 2.确定迭代公式:造两个指针,一个指针i代表背包长度,一个指针j初始为0,代表截取s子串的起始位置。
        // 每次先截取整个长度看是否有直接匹配的,然后在将j加一,看从j到i-1截取的字符串是否有匹配,如果
        // 匹配,再看dp[j]是否为true,为true说明背包前j位有匹配成功的情况,那么当前长度i在此时截取的
        // 字符串和加之前的成功情况可以拼接成目前长度的字符串,故dp[i]=true.

        // 3.初始化,dp[0]需要初始化为true,因为i长度直接匹配成功的时j则为0,dp[0]初始化为0才能使得 
        // dp[0] && 截取的0到i-1的子字符串匹配结果 = true。
        // 注:dp[0]被用到的时候也只有这种情况。
        dp[0] = true;

        // 4.确定遍历顺序
        // 分析过程:首先需要确定外层先遍历dp数组还是字典,这道题的实质是找到一种拼接的情况,意思只要拼接成就行,
        // 不需要管顺序,好像谁在外层无所谓,但是,这个背包其实和正常背包又不一样,如果先遍历字典元素,重复存在的
        // 元素在字符串中如果不是挨着,它还加不上,比如示例2,apple元素,在背包容量为5(注意dp是有0代表长度0的哈)
        // 时,apple可以放下,标记dp[5]=true,在背包长度为13时,的字符串s的最后五位是apple,如果是普通完全背包
        // 问题,这里只要重量满足就加进去了,但是这里不行,这里的上一个dp[13-5]即上一个位置是dp[8]是false,因为
        // 此时pen是第二个元素,还没有添加,所以apple第二个其实就没有加进去,那么最后同样的思路,输出就会是false
        // 但是很显然,答案应该是true。所以这波分析看得出来,看似好像不需要管顺序,但实际上字符串本身的特点,使得
        // 外层只能先遍历背包长度,这样才能保证不会漏掉某些情况。
        for(int i = 1;i < dp.length;i++){
            for(int j = 0;j < i;j++){
                String str = s.substring(j,i);
                if(set.contains(str) && dp[j]){
                    dp[i] = true;
                    break;
                }
            }
        }

        return dp[s.length()];
    }
}

你可能感兴趣的:(算法,leetcode,动态规划,回溯,记忆化搜索)