【动态规划】子数组、子串系列II|等差数列划分|最长湍流子数组|单词拆分|环绕字符串中唯一的子字符串

一、等差数列划分

413. 等差数列划分

280d78292156484f826d5b3cd72ab4f1.png

算法原理

6985193ba7724a0fbbb4ec5dc4d1f497.png

💡细节:

1.如果当前nums数组中i位置的数和前面两个数可以构成等差数列,那么当前位置所有子数组构成的等差数列个数dp[i]就等于前一个位置有子数组构成的等差数列个数+1(这个1代表增加最后三个数构成的等差数列)【简单理解:就是以a和b结尾的这些等差数列后面都加上一个c,这些新的等差数列也还是等差数列,加上a,b,c这个等差数列就是dp[i] = dp[i-1]+1】

两种情况最后三个数a,b,c根本无法构成等差数列,那么dp[i]=0

2.初始化:因为等差数列至少是三个数,那么dp[0] 和dp[1]根本无法构成等差数列,只能为0

3.根据dp表示可知,结果应该是dp表每个位置的和

class Solution {
    public int numberOfArithmeticSlices(int[] nums) {
        int n = nums.length;

        int[] dp = new int[n];//dp[i]表示:以i位置为结尾的所有子数组中有多少个等差数列

        int sum = 0;
        for(int i=2;i<n;i++) {
            dp[i] = nums[i]-nums[i-1]==nums[i-1]-nums[i-2]?dp[i-1]+1:0;
            sum += dp[i];
        }

        return sum;

    }
}

二、最长湍流子数组

978. 最长湍流子数组

dd48285fc90f493eafe0b60494b6a667.png

算法原理:

404c9de6e6a4425cba267dd5a7e9caf9.png

💡细节:

1.dp表的创建:如果只用一个dp表的话发现无法知道最后位置是上升还是下降,那么就考虑用两个dp表,一个表示子数组上升的最大长度,一个表示子数组下降的最大长度

2.初始化:根据状态转移方程,填表最差的情况都为1,那么直接全部初始化为1,那么有些填表的情况就不需要考虑了

3.返回值:两个表的最大值

💡总结:初始化的三种情况

(1)直接初始化会越界的位置

(2)加虚拟节点(但是有两个注意事项)=>初始化更简单

(3)把表中所有的位置都初始化为最小的情况(跟本题一样)

class Solution {
    public int maxTurbulenceSize(int[] nums) {
        int n = nums.length;

        int[] f = new int[n];//上升
        int[] g = new int[n];//下降

        for(int i=0;i<n;i++) {
            f[i] = g[i] = 1;
        }

        int ret = 1;
        for(int i=1;i<n;i++) {
            if(nums[i-1]<nums[i])
                f[i] = g[i-1] + 1;
            else if(nums[i-1]>nums[i])
                g[i] = f[i-1] + 1;
            ret = Math.max(ret,Math.max(g[i],f[i]));
        }

        return ret;
    }
}

三、单词拆分

139. 单词拆分

0b3ec6134f824b67939fa35d1c27d61d.png

 算法原理:

0468d5c70c22421482a989ba2638b0be.png

💡细节:

1.dp[i]:[0,i]区间内字符串是否可以被字典中的单词拼接而成,求状态转移方程时,需要设置最后一个单词的起始位置下标j,才能将dp进行联系,而且需要保证最后一个单词[j,i]在字典中&&上一个单词的位置dp[j-1]也为true

2.初始化:为了防止出现dp[j-1]中j-1为-1的情况,可以直接在s字符串前加一个字符,这样可以更好的处理下标的映射关系

3.优化1:找[j,i+1)的子串时,可以直接将字典中的单词存到哈希表中,在哈希表中去看子串是否存在

4.优化2:break =>只要在哈希表中找到一个单词即可,找到了就不用继续找下一个了

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        //优化1:将字典里面的单词存到哈希表中,在哈希表中找子串是否存在
        Set<String> hash = new HashSet<>(wordDict);

        int n = s.length();

        boolean[] dp = new boolean[n+1];//dp[i]:0-i区间内的字符串能否被拼接成功

        //初始化
        dp[0] = true;

        s = " " + s;//处理下标的映射关系(s的起始位置是从1开始的)

        for(int i=1;i<=n;i++) {
            for(int j=i;j>=1;j--) {
                if(dp[j-1]==true && hash.contains(s.substring(j,i+1))) {//左闭右开
                dp[i] = true;    
                break;//优化2:只要在哈希表中找到一个单词即可,找到了就不用继续找下一个了    
                }
            }
        }
    return dp[n];
    }
}

四、环绕字符串中唯一的子字符串

467. 环绕字符串中唯一的子字符串

7bd8300bce11407e94ba3fb30837d799.png

算法原理:

1011bf7d55294216af85970a59f59666.png

💡细节:

1.一般涉及子串的都会向长度为1,和长度大于1考虑状态转移方程

2.加一个连续字符的子串和未加这个字符的子串,当长度>1的时候,是一样大的,即dp[i]=dp[i-1]

3.初始化的技巧:直接初始化为最小值1,这样有些填表的情况就不需要考虑了

4.当个末尾字符相同的子串需要进行去重,创建一个大小为26的hash数组,把每个位置都填上以改字符结尾的最大dp值

class Solution {
    public int findSubstringInWraproundString(String ss) {
        int n = ss.length();
        char[] s = ss.toCharArray();//转为数组好用下标

        int[] dp = new int[n];//dp[i]:以i位置为结尾的所有子串里面,有多少个在base中出现过

        for(int i=0;i<n;i++) dp[i] = 1;//全部初始化为最小值

        for(int i=1;i<n;i++) {
            if(s[i-1]+1==s[i]||(s[i-1]=='z'&&s[i]=='a')) 
                dp[i]+=dp[i-1];
        }

        //去重
        int[] hash = new int[26];
        for(int i=0;i<n;i++) {
            hash[s[i]-'a'] = Math.max(dp[i],hash[s[i]-'a']);
        }

        int sum = 0;
        for(int x:hash) sum+=x;
        return sum;
    }
}

 

 

  • 22
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深鱼~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值