求职leetcode题目(15)

 1.打家劫舍

 解题思路:

动态规划的的四个解题步骤是:

  • 定义子问题
  • 写出子问题的递推关系
  • 确定 DP 数组的计算顺序
  • 空间优化(可选)

下面我们一步一步地进行讲解。

标签:动态规划

动态规划方程:dp[n] = MAX( dp[n-1], dp[n-2] + num )
由于不可以在相邻的房屋闯入,所以在当前位置 n 房屋可盗窃的最大值,要么就是 n-1 房屋可盗窃的最大值,要么就是 n-2 房屋可盗窃的最大值加上当前房屋的值,二者之间取最大值
举例来说:1 号房间可盗窃最大值为 3 即为 dp[1]=3,2 号房间可盗窃最大值为 4 即为 dp[2]=4,3 号房间自身的值为 2 即为 num=2,那么 dp[3] = MAX( dp[2], dp[1] + num ) = MAX(4, 3+2) = 5,3 号房间可盗窃最大值为 5

时间复杂度:O(n),n 为数组长度

class Solution {
    public int rob(int[] nums) {
       if(nums.length==0){
         return 0;
       }
      int N =nums.length;
      int[] dp =new int[N+1];
      dp[0]=0;
      dp[1]=nums[0];
      for(int k =2;k<=N;k++){
        dp[k]=Math.max(dp[k-1],nums[k-1]+dp[k-2]);
      }
        return dp[N];
    }
}

这里我们可以又快一下空间复杂度:

  • 我们发现 dp[n] 只与 dp[n−1] 和 dp[n−2] 有关系,因此我们可以设两个变量 cur和 pre 交替记录,将空间复杂度降到 O(1) 。

空间优化是动态规划问题的进阶内容了。对于初学者来说,可以不掌握这部分内容。

空间优化的基本原理是,很多时候我们并不需要始终持有全部的 DP 数组。对于小偷问题,我们发现,最后一步计算 f(n) 的时候,实际上只用到了 f(n−1) 和 f(n−2) 的结果。n−3 之前的子问题,实际上早就已经用不到了。那么,我们可以只用两个变量保存两个子问题的结果,就可以依次计算出所有的子问题。下面的图比较了空间优化前和优化后的对比关系:

class Solution {
    public int rob(int[] nums) {
      int pre =0,cur =0,tmp;
      for(int num:nums){
        tmp=cur;
        cur=Math.max(pre+num,cur);
        pre =tmp;
      }
      return cur;
    }
}

2.地下城游戏

  解法一(超时!):

f(1,0)+dungeon[1][0]=f(1,1)
f(0,1)+dungeon[0][1]=f(1,1)

dungeon[1][0] 是已经确定了的,那么 f(1,1) 是多少?
我们知道最后一个格子是 -3,那么最小耗费的生命值就是 3 了, f(1, 1) = 3,其实这个就是 退出条件 了。当我们到了最后一个格子的时候,就是代表着我们已经到了终点了,就可以退出了,


so ...
f(1,0)=f(1,1)−dungeon[1][0]=7
f(0,1)=f(1,1)−dungeon[0][1]=8
f(0,0)=min(f(1,0),f(0,1))−dungeon[0][0]=8
所以起始点的最小耗费生命值为8。

class Solution {

    public int calculateMinimumHP(int[][] dungeon) {
        return dfs(dungeon, dungeon.length, dungeon[0].length, 0, 0);
    }

    private int dfs(int[][] dungeon, int m, int n, int i, int j) {
        // 到达终点,递归终止。
        if (i == m - 1 && j == n - 1) {
            return Math.max(1 - dungeon[i][j], 1);
        }
        // 最后一行,只能向右搜索。
        if (i == m - 1) {
            return Math.max(dfs(dungeon, m, n, i, j + 1) - dungeon[i][j], 1);
        }
        // 最后一列,只能向下搜索。
        if (j == n - 1) {
           return Math.max(dfs(dungeon, m, n, i + 1, j) - dungeon[i][j], 1);
        }
        // 向下搜索 + 向右搜索,得到(i, j)点的后续路径所要求的最低血量 Math.min(dfs(i + 1, j), dfs(i, j + 1)),
        // 又因为(i, j)点本身提供血量dungeon[i][j], 因此从(i, j)开始所需的最低血量为 Math.min(dfs(i + 1, j), dfs(i, j + 1)) - dungeon[i][j]
        // 因为骑士的血量不能小于1,因此要和1取个max。
        return Math.max(Math.min(dfs(dungeon, m, n, i + 1, j), dfs(dungeon, m, n, i, j + 1)) - dungeon[i][j], 1);
    }
}

解法2:DFS + 记忆化(0ms,100%,搜索时会有大量重复计算的分支,加上记忆化即可解决)

3.课程表 

算法流程:

  1. 统计课程安排图中每个节点的入度,生成 入度表 indegrees。
  2. 借助一个队列 queue,将所有入度为 0 的节点入队。
  3. 当 queue 非空时,依次将队首节点出队,在课程安排图中删除此节点 pre:
  4. 并不是真正从邻接表中删除此节点 pre,而是将此节点对应所有邻接节点 cur 的入度 −1,即 indegrees[cur] -= 1。
  5. 当入度 −1后邻接节点 cur 的入度为 0,说明 cur 所有的前驱节点已经被 “删除”,此时将 cur 入队。
  6. 在每次 pre 出队时,执行 numCourses--;
  7. 若整个课程安排图是有向无环图(即可以安排),则所有节点一定都入队并出队过,即完成拓扑排序。换个角度说,若课程安排图中存在环,一定有节点的入度始终不为 0。
  8. 因此,拓扑排序出队次数等于课程个数,返回 numCourses == 0 判断课程是否可以成功安排。

 

 

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        int[] indegrees = new int[numCourses];
        List<List<Integer>> adjacency = new ArrayList<>();
        Queue<Integer> queue = new LinkedList<>();
        for(int i = 0; i < numCourses; i++)
            adjacency.add(new ArrayList<>());
        // Get the indegree and adjacency of every course.
        for(int[] cp : prerequisites) {
            indegrees[cp[0]]++;
            adjacency.get(cp[1]).add(cp[0]);
        }
        // Get all the courses with the indegree of 0.
        for(int i = 0; i < numCourses; i++)
            if(indegrees[i] == 0) queue.add(i);
        // BFS TopSort.
        while(!queue.isEmpty()) {
            int pre = queue.poll();
            numCourses--;
            for(int cur : adjacency.get(pre))
                if(--indegrees[cur] == 0) queue.add(cur);
        }
        return numCourses == 0;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值