贪心+动态规划1

目录

跳跃游戏

跳跃游戏 II

划分字母区间

无重叠区间

动态规划(重叠子问题)

动态规划的解题步骤

斐波那契数

70 爬楼梯

使用最小花费爬楼梯

118 杨辉三角​​​​​​​

​​​​​​​​​​​​​​跳跃游戏

给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。

class Solution {
    public boolean canJump(int[] nums) {
        // 跳跃覆盖范围究竟可不可以覆盖到终点!
        // 直接取最大值,总和大于nums.length即可
        if (nums.length == 1) {
            return true;
        }
        // 覆盖范围, 初始覆盖范围应该是0,因为下面的迭代是从下标0开始的
        int cover = 0;
        // 在覆盖范围内更新最大的覆盖范围 因此是i<=cover
        for (int i = 0; i <= cover; i++) {
            cover = Math.max(cover, i + nums[i]); // 直接在当前下标加上能跳的最大范围
            if (cover >= nums.length - 1) {
                return true;
            }
        }
        return false;
    }
}

跳跃游戏 II

给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。

每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:

  • 0 <= j <= nums[i]
  • i + j < n

返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]。

class Solution {
    public int jump(int[] nums) {
        //要从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点,得到的就是最少步数!

        //在数组末端时,不需要跳转 步数为0
        if (nums == null || nums.length == 0 || nums.length == 1) {
            return 0;
        }
        //需要统计两个覆盖范围,当前这一步的最大覆盖和下一步最大覆盖。
        //记录跳跃的次数
        int count = 0;
        //当前的覆盖最大区域
        int curDistance = 0;
        //最大的覆盖区域
        int maxDistance = 0;
        for (int i = 0; i < nums.length; i++) {
            //在可覆盖区域内更新最大的覆盖区域
            maxDistance = Math.max(maxDistance, i + nums[i]);
            //说明当前一步,再跳一步就到达了末尾
            if (maxDistance >= nums.length - 1) {
                count++;
                break;
            }
            //走到当前覆盖的最大区域时,更新下一步可达的最大区域
            if (i == curDistance) {
                curDistance = maxDistance;
                count++;
            }
        }
        return count;
    }
}

划分字母区间

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段

注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。

返回一个表示每个字符串片段的长度的列表。

edge[chars[i] - 'a'] = i; 找到i对应位置 找到i对应位置 例如:字母c edge[2]=i;进行更新,找到最后位置

class Solution {
    public List<Integer> partitionLabels(String s) {
        //如何统计相同字母出现的最大下标:统计次数
        //1.统计每一个字符最后出现的位置
        //2.从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点

        List<Integer> list = new LinkedList<>();
        int[] edge = new int[27];
        char[] chars = s.toCharArray();
        //1.统计每一个字符最后出现的位置
        for (int i = 0; i < chars.length; i++) {
            edge[chars[i] - 'a'] = i;  //eg.  字母c edge[2]=i;进行更新,找到最后位置
        }
        //2.从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点
        int left = 0;//为什么取-1
        int right = 0;
        for (int i = 0; i < chars.length; i++) {
            right = Math.max(right, edge[chars[i] - 'a']);//获取字符最远处下标
            if (i == right) {
                list.add(right - left + 1);//统计本次符合条件个数
                left = i + 1;
            }
        }
        return list;
    }
}

无重叠区间

重叠区间问题

给定一个区间的集合 intervals ,其中 intervals[i] = [start(i), end(i)] 。返回 需要移除区间的最小数量,使剩余区间互不重叠

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        /*重叠区间问题
        //1.按照左区间或是右区间进行排序
        //2.比较intervals[i][0]和interval[i-1][1](左边界和右边界)
        //3.更新最小右边界
        //根据题目要求:合并 、删除 。。。
        */
        //1.按照左区间或是右区间进行排序
        Arrays.sort(intervals, (a, b) -> {
            //升序排列
            return Integer.compare(a[0], b[0]);
        });
        //2.比较intervals[i][0]和interval[i-1][1](左边界和右边界)
        int remove = 0;//需要移除的区间数。
        int pre = intervals[0][1];//初始化为第一个区间的结束位置 intervals[0][1]。
        for (int i = 1; i < intervals.length; i++) {
            //3.更新最小右边界 上一个区间右边界与下一个区间左边界比较
            if (pre > intervals[i][0]) {//检查当前区间的起始位置 intervals[i][0] 是否小于前一个区间的结束位置 pre。
                remove++;
                pre = Math.min(pre, intervals[i][1]);//更新 pre 为当前区间结束位置 intervals[i][1] 和 pre 的较小值,以确保下一个区间的起始位置不小于 pre。
            } else {
                pre = intervals[i][1];
            }
        }
        return remove;
    }
}

动态规划(重叠子问题)

动态规划问题的一般形式就是求最值。比如说让你求最长递增子序列呀,最小编辑距离呀等等。

如果某一问题有很多重叠子问题,使用动态规划是最有效的。动态规划中每一个状态一定是由上一个状态推导出来的。

动规是由前一个状态推导出来的,而贪心是局部直接选最优的。

明确 base case -> 明确「状态」-> 明确「选择」 -> 定义 dp 数组/函数的含义。

重叠子问题、最优子结构、状态转移方程就是动态规划三要素

递归算法的时间复杂度怎么计算?就是用子问题个数乘以解决一个子问题需要的时间

动态规划的解题步骤

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化 (递推公式决定了dp数组要如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

斐波那契数

「自底向上」的思路:直接从最底下、最简单、问题规模最小、已知结果的 f(1) 和 f(2)(base case)开始往上推,直到推到我们想要的答案 f(20)。这就是「递推」的思路,这也是动态规划一般都脱离了递归,而是由循环迭代完成计算的原因。

class Solution {
    public int fib(int n) {
        if(n<=1){
            return n;
        }
        //dp[i]的定义为:第i个数的斐波那契数值是dp[i]
        int[]dp=new int[n+1];
        dp[0]=0;
        dp[1]=1;
        for(int i=2;i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
}

70 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

class Solution {
    public int climbStairs(int n) {
        //dp[i]: 爬到第i层楼梯,有dp[i]种方法
        int[] dp = new int[n + 1];  //为了存储第0层到第n层的结果 数组长度是n+1
0       dp[0]=1;
        dp[1]=1;
        for(int i=2;i<=n;i++){
        dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

使用最小花费爬楼梯

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        //dp[i]的定义:到达第i台阶所花费的最少体力为dp[i]。
        int[] dp = new int[cost.length+1];
        //前两台阶不费力
        dp[0] = 0;
        dp[1] = 0;
        //最小花费,递推公式和花费联系上
        for(int i=2;i<=cost.length;i++){
        dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
        }
        return dp[cost.length];
    }
 }

118 杨辉三角

给定一个非负整数 numRows生成「杨辉三角」的前 numRows行。

在「杨辉三角」中,每个数是它左上方和右上方的数的和。

class Solution {
    public List<List<Integer>> generate(int numRows) {
        // 结果集合
        List<List<Integer>> res = new ArrayList<>();
        // 行
        for (int i = 0; i < numRows; i++) {
            // 每一行
            List<Integer> list = new ArrayList<>();
            // 行数等于列数
            int row = i + 1; //列数
            for (int j = 0; j < row; j++) {
                //如果当前列是第一列或最后一列,则将 1 添加到当前行的列表中
                if (j == 0 || j == row - 1) {
                    list.add(1);
                } else {
                    //否则,使用上一行的第 j 列和第 j-1 列的值相加,将结果添加到当前行的列表中。
                    //获取上一行数据
                    List<Integer> pre = res.get(i - 1);
                    int num = pre.get(j) + pre.get(j - 1);
                    list.add(num);
                }
            }
            res.add(list);
        }
        return res;
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值