hot100动态规划专题2

1.单词拆分

dp[i]表示s的前i个字符是否可以由字典里的字符组成。

dp[0]初始化为0,dp数组长度 len + 1

需要Hashset存储字典里的字符,方便判断。new HashSet<>(wordDict)即可,不需要一个一个加入。

转移方程:如果dp[j] && set.contains(s.substring(i, j)),那么dp[i] = true,然后break;

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

注意i j的取值范围问题。

2.最长递增子序列

跟连续递增序列不同,这个题不依赖nums[i-1],而是要遍历前i-1个数

dp[i]表示前i个数字的最长递增子序列长度,初始化为1即可

转移方程:

j从0遍历到i - 1,dp[i] = Math.max(dp[i], dp[j] + 1))

每次内循环后,用res来更新最大的dp[i],最后返回res。

class Solution {
    public int lengthOfLIS(int[] nums) {
        int len = nums.length;
        int[] dp = new int[len];
        Arrays.fill(dp, 1);
        int res = 1;
        for (int i = 1; i < len; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            res = Math.max(res, dp[i]);
        }
        return res;
    }
}

3.乘积最大子数组

res = nums[0] 用来存全局最大值

maxDp = nums[0] 用来存以当前数结尾的最大乘积

minDp = nums[0] 用来存以当前数结尾的最小乘积

从i = 1开始遍历,更新上面这三个值

maxDp要用temp存起来。

maxDp的更新:maxDp * nums[i]、minDp * nums[i]、nums[i]中取最大值

minDp的更新:temp * nums[i]、minDp * nums[i]、nums[i]中取最小值

res = Math,max(res, maxDp)

class Solution {
    public int maxProduct(int[] nums) {
        int len = nums.length;
        int minDp = nums[0], maxDp = nums[0], res = nums[0];
        for (int i = 1; i < len; i++) {
            int tempMax = maxDp;
            maxDp = Math.max(Math.max(maxDp * nums[i], minDp * nums[i]), nums[i]);
            minDp = Math.min(Math.min(tempMax * nums[i], minDp * nums[i]), nums[i]);
            res = Math.max(res, maxDp);
        }
        return res;

    }
}

4.分割等和子集

先计算总和,如果总和是奇数直接返回false;

target = sum / 2;

dp[i]表示能否凑出数字i,dp[0] = true;

对于nums中的每一个num,倒着遍历dp[i]。

转移方程:要凑出i,手里有个num,不用num,则看原来的dp[i],用num,则看dp[i-num]

一定要倒着遍历,num才只用一次。否则num会使用多次。

二刷感悟:01背包固定公式:先遍历物品,再倒着遍历容量

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if (sum % 2 == 1) {
            return false;
        }
        int target = sum / 2;
        boolean[] dp = new boolean[target + 1];
        dp[0] = true;
        for (int num : nums) {
            for (int j = target; j >= num; j--) {
                dp[j] = dp[j] || dp[j - num];
            }
        }
        return dp[target];
    }
}

5.最长有效括号

5.1 用栈

用栈存下标,先存一个-1来当基准。

遇到'(',就push(i),如果是 ) 先pop()出来,如果栈空了,则需要把i push进去当新的基准。如果栈不空,则长度是i - st.peek(),更新全局最大值。

class Solution {
    public int longestValidParentheses(String s) {
        int len = s.length();
        if (len == 0) return 0;
        Deque<Integer> st = new LinkedList<>();
        st.push(-1);
        int res = 0;
        for (int i = 0; i < len; i++) {
            char c = s.charAt(i);
            if (c == '(') {
                st.push(i);
            } else {
                st.pop();
                if (st.isEmpty()) {
                    st.push(i);
                } else {
                    res = Math.max(res, i - st.peek());
                }
            }
        }
        return res;
    }
}

5.2 dp

class Solution {
    public int longestValidParentheses(String s) {
        int n = s.length();
        if (n == 0) return 0;
        
        int[] dp = new int[n]; // dp[i] = 以i结尾的最长有效括号长度
        int max = 0;

        for (int i = 1; i < n; i++) {
            // 只有遇到 ) 才可能形成有效结尾
            if (s.charAt(i) == ')') {
                
                // ======================================
                // 情况1:... ()  最后两位是 ()
                // ======================================
                if (s.charAt(i-1) == '(') {
                    // 直接凑一对 + 前面的有效长度
                    dp[i] = (i >= 2 ? dp[i-2] : 0) + 2;
                }
                
                // ======================================
                // 情况2:... ))  最后两位是 ))
                // ======================================
                else {
                    // 找到和当前 ) 配对的 ( 的位置
                    int match = i - dp[i-1] - 1;
                    
                    // 配对位置必须合法,且必须是 (
                    if (match >= 0 && s.charAt(match) == '(') {
                        // 长度 = 中间一段 + 新配对的2 + 配对位置前面的有效长度
                        dp[i] = dp[i-1] + 2;
                        
                        // 加上前面的有效段(如果有的话)
                        if (match > 0) {
                            dp[i] += dp[match - 1];
                        }
                    }
                }
            }
            // 更新全局最大值
            max = Math.max(max, dp[i]);
        }
        return max;
    }
}

二刷感悟:第一种情况很简单,dp[i] = dp[i-2] + 2;

第二种情况,要找到preindex = i - dp[i-1] - 1,也就是跟位置i配对的左括号

如果preindex >= 0 && s.charAt(preidex) == '(',那么dp[i] = dp[i-1] + 2;

更进一步,如果preindex - 1 >= 0,dp[i] += dp[preindex-1]。因为可能出现()((()))这样的情况,所以要加上左边界之前的已经有效的括号序列。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

I_LPL

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

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

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

打赏作者

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

抵扣说明:

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

余额充值