首页 > 其他分享 >动态规划之房屋染色

动态规划之房屋染色

时间:2023-11-02 10:12:02浏览次数:35  
标签:int 染色 k1 房子 最小值 房屋 min1 min2 动态

这里有n个房子在一列直线上,现在我们需要给房屋染色,共有k种颜色。每个房屋染不同的颜色费用也不同,你希望每两个相邻的房屋颜色不同

费用通过一个nxk 的矩阵给出,比如cost[0][0]表示房屋0染颜色0的费用,cost[1][2]表示房屋1染颜色2的费用。

样例:

输入:
costs = [[14,2,11],[11,14,5],[14,3,10]]
输出: 10
说明:
三个屋子分别使用第1,2,1种颜色,总花费是10。

原题链接:https://www.lintcode.com/problem/516/

确定状态

最后一步

这个题每个房子染的房屋颜色有k种选择,假设k=3,颜色为红,绿,蓝。由于相邻的房子颜色不能一样,对于最后一个房子n,如果染成了红色,那么倒数第二个房子n-1只能染成绿色或者蓝色,对于其他两种颜色,也是一样的,所以我们可以知道:

  • 最后一个房子(n)为红色,倒数第二个房子(n-1)颜色为绿或者蓝
  • 最后一个房子(n)为绿色,倒数第二个房子(n-1)颜色为红或者蓝
  • 最后一个房子(n)为蓝色,倒数第二个房子(n-1)颜色为红或者绿

由此可知,当有k种颜色时,最后一栋房子染色就有k种情况,每一种情况对应的倒数第二个房屋染色也是不一样的。

确定子问题

由上面分析可知,假设该问题的最优解最小花费为A,最后一栋房子染色为kn,花费为An,我们必然就可以知道倒数第二栋房子染色选择就可以是 k1,k2,k3 ... kn-1。那么去掉最后一栋房子的花费An,前n-1栋房子的花费也必然是最小的,为A-An,这样我们就把求解前n栋房子的最小花费转为求解前n-1栋房子的最小花费,问题规模自然变小了。这也是我们要找的子问题。

这个有一个问题要注意下,假设我们知道了前n-1栋房子的最优解,但如果我们不知道第n-1栋房子染了什么颜色,那么第n栋房子染什么颜色我们就不能确定了,如果刚好最后一栋房子与倒数第二栋房子染色一样,且花费最小,那么我们得出的结果就是错的。所以我们必须要记录每个房子染了什么颜色,否则该问题无法用动态规划进行求解。

转移方程

根据上面我们确定了问题的解决方式,由于要记录前n栋房子的染色的最小花费且还要记录第n栋房子染了什么颜色,那么我们需要开辟了二维数组来记录这两个场景的数据,所以我们用f[n][i] 来表示前n栋房子的最小花费,且第n栋房子的染色方式为icost为题意给定的费用矩阵,转移方程为:

f[n][i] = cost[n-1][i] + min{f[n-1][0], f[n-1][2] ... f[n-1][j]} 且 [j != i]

上面转移方式的含义为:如果要求前n栋房子(注意第n栋房子在cost数组里面下标为n-1)的最小花费f[n][i],那么我们直接获取第n栋房子染为第i种颜色的花费为cost[n-1][i],不过第n-1栋房子除了第i种房子不能染,其他颜色都可以选择来染,从中选择一个花费最小的,再加上第n栋房子最小花费,即为问题的解。

初始条件和边界情况

由于f[n][i]代表前n栋房子的染色情况,那么f[0][i]代表没有房子,花费必然是0。

代码实现

根据上述分析,代码就很简单了,下面是没有经过优化的代码,待会还要优化:

 public static int minCostII(int[][] costs) {
    // write your code here

    if (costs == null || costs.length == 0) {
      return 0;
    }

    // 房子数
    int m = costs.length;
    // 颜色种类数
    int k = costs[0].length;

    // 转移方程, n 为前n房子的最小花费(n从1开始)
    // f[n][i] = [k != i]  const[n-1][i] + min{f[n-1][0], f[n-1][2] ... f[n-1][k]}

    int[][] f = new int[m+1][k];

    for (int i = 1; i <= m; i++) {
      for (int j = 0; j < k; j++) {
        f[i][j] = Integer.MAX_VALUE;

        for (int z = 0; z < k; z++) {
          if (z == j) {
            continue;
          }

          int v = f[i-1][z] + costs[i-1][j];
          if (f[i][j] > v) {
            f[i][j] = v;
          }
        }
      }
    }

    int result = f[m][0];

    for (int i = 1; i < k; i++) {
      if (f[m][i] < result) {
        result = f[m][i];
      }
    }

    return result;
  }

优化

我们直接来看转移方程:

f[n][i] = cost[n-1][i] + min{f[n-1][0], f[n-1][2] ... f[n-1][j]} 且 [j != i]

根据题意我们可以知道,颜色有k种,k的规模是没有说的,如果k为很小的数,那么求解min{f[n-1][0], f[n-1][2] ... f[n-1][j]} 是比较快的,如果k为很大的数字,第n栋房子每换一种颜色染,那么min{f[n-1][0], f[n-1][2] ... f[n-1][j]}都要重新计算一遍,区别就是从该集合中去掉颜色与第n栋染色一样的那一项,我们先来简化一下:

假设现在我们有一个数组A[2,1,4,5,7,3],1就是最应的最小花费,如果1对应的颜色不能选择,那么A的最小值就是2,其余A的最小值都是1,所以我们只需要把A的最小值,次小值都求解出来,问题就解决了。不过怎么求解呢?下面我们用双指针的解法来求解。

首先初始化最小值,次小值(由于用了两个指针,需要初始化两个指针的位置):

  • 如果数组长度为0,没有最小值,更没有次小值;
  • 如果数组长度为1,最小值和次小值都是第一位元素;
  • 如果数组长度大于等于2,需要比较第一位和第二位元素的大小,然后给最小值,次小值赋上对应的值。

之所以初始化最小值,次小值,是因为我们要用双指针来求解,算法时间复杂度为O(n),方式如下:

  • 从数组第三位开始遍历,如果遍历的值比最小值小,那么就将最小值原来的值记录下来,当当前值赋给最小值
  • 判断最小值无更新
    • 如果最小值无更新,且次小值大于当前值,那么就将当前值赋值给次小值
    • 如果最小值有更新,那么直接将最小值原来的值赋给次小值

代码如下:

/**
   * 求解数组m的最小值,次小值
   *
   * @param m
   */
  public static void test(int[] m) {
    if (m == null || m.length == 0) {
      return;
    }

    // 第一小, 第二小
    int min1 = Integer.MAX_VALUE, min2 = Integer.MAX_VALUE;
    // 第一小颜色位置, 第二小颜色位置
    int k1 = 0, k2 = 0;

    if (m.length == 1) {
      min1 = min2 = m[0];
      k1 = k2 = 0;
    }

    if (m.length >= 2) {
      int curr0 = m[0];
      int curr1 = m[1];

      if (curr0 >= curr1) {
        min1 = curr1;
        k1 = 1;
        min2 = curr0;
        k2 = 0;
      } else {
        min1 = curr0;
        k1 = 0;
        min2 = curr1;
        k2 = 1;
      }

      for (int q = 2; q < m.length; q++) {
        int curr = m[q];

        // 选判断是否比次小值小
        int oldMin1 = min1;
        int oldK1 = k1;
        if (curr < min1) {
          min1 = curr;
          k1 = q;
        }

        // 最小值无更新,且min2大于当前值
        if (oldK1 == k1 && min2 > curr) {
          min2 = curr;
          k2 = q;
        }

        // 最小值有更新,那么原来的值就是第二小
        if (oldK1 != k1) {
          min2 = oldMin1;
          k2 = oldK1;
        }
      }
    }

    System.out.println("min1 = " + min1 + ", k1 = " + k1);
    System.out.println("min2 = " + min2 + ", k2 = " + k2);
  }

根据上面分析,在求解前n栋房子花费最小值是,我们直接将前n-1栋房子花费的最小值,次小值求出来,然后根据第n栋房子的颜色来进行合适判断即可,完整代码如下:

  public static int minCostII2(int[][] costs) {
    // write your code here

    if (costs == null || costs.length == 0) {
      return 0;
    }

    // 房子数
    int m = costs.length;
    // 颜色种类数
    int k = costs[0].length;

    // 转移方程, n 为前n房子的最小花费(n从1开始)
    // f[n][i] = [j != i]  const[n-1][i] + min{f[n-1][0], f[n-1][2] ... f[n-1][k]}

    int[][] f = new int[m+1][k];

    for (int i = 1; i <= m; i++) {
      // 第一小, 第二小
      int min1 = Integer.MAX_VALUE, min2 = Integer.MAX_VALUE;
      // 第一小颜色位置, 第二小颜色位置
      int k1 = 0, k2 = 0;

      if (k == 1) {
        min1 = min2 = f[i-1][0];
        k1 = k2 = 0;
      }

      if (k >= 2) {
        int curr0 = f[i-1][0];
        int curr1 = f[i-1][1];

        if (curr0 >= curr1) {
          min1 = curr1;
          k1 = 1;
          min2 = curr0;
          k2 = 0;
        } else {
          min1 = curr0;
          k1 = 0;
          min2 = curr1;
          k2 = 1;
        }

        // 算出{f[i-1][0], f[i-1][2] ... f[i-1][k]} 的第一小,记录哪个颜色,第二小,记录哪个颜色,下面可以直接用
        for (int q = 2; q < k; q++) {
          int curr = f[i-1][q];

          // 选判断是否比次小值小
          int oldMin1 = min1;
          int oldK1 = k1;
          if (curr < min1) {
            min1 = curr;
            k1 = q;
          }

          // 最小值无更新,且min2大于当前值
          if (oldK1 == k1 && min2 > curr) {
            min2 = curr;
            k2 = q;
          }

          // 最小值有更新,那么原来的值就是第二小
          if (oldK1 != k1) {
            min2 = oldMin1;
            k2 = oldK1;
          }
        }
      }

      for (int j = 0; j < k; j++) {
        f[i][j] = Integer.MAX_VALUE;

        // 如果当前位置为k1,当前不能取,只能取次小的
        if (j == k1) {
          f[i][j] = min2 + costs[i-1][j];
        } else {
          f[i][j] = min1 + costs[i-1][j];
        }
      }
    }

    int result = f[m][0];

    for (int i = 1; i < k; i++) {
      if (f[m][i] < result) {
        result = f[m][i];
      }
    }

    return result;
  }

title: 动态规划之房屋染色
tags: [算法,动态规划]
author: Mingshan
categories: [算法,动态规划]
date: 2021-03-07

标签:int,染色,k1,房子,最小值,房屋,min1,min2,动态
From: https://www.cnblogs.com/mingshan/p/17793527.html

相关文章

  • 动态规划之解码方法
    一条包含字母A-Z的消息通过以下映射进行了编码:'A'->1'B'->2...'Z'->26要解码已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,"111"可以将"1"中的每个"1"映射为"A",从而得到"AAA",或者可以将"1......
  • 【算法笔记】动态规划Dynamic Programming
    参考视频:5SimpleStepsforSolvingDynamicProgrammingProblems引子:最长递增子串(LongestIncreasingSubsequence,LIS)LIS([31825])=len([125])=3LIS([52863695])=len([2369])=4解决问题的三个步骤:可视化例子(visualizeexample)(“visualizee......
  • Vue动态添加style样式
    最近在用uniapp开发安卓app,由于语法跟vue一致,就梳理了下动态添加style的方法:Object :style="{fontSize:fontSize+'px'}":style="{fontSize:(fontSize?fontSize:'12')+'px'}" Array :style="[baseStyles,otherStyle......
  • 2023-11-01:用go语言,沿街有一排连续的房屋。每间房屋内都藏有一定的现金, 现在有一位小
    2023-11-01:用go语言,沿街有一排连续的房屋。每间房屋内都藏有一定的现金,现在有一位小偷计划从这些房屋中窃取现金,由于相邻的房屋装有相互连通的防盗系统,所以小偷不会窃取相邻的房屋,小偷的窃取能力定义为他在窃取过程中能从单间房屋中窃取的最大金额,给你一个整数数组nums......
  • 2023-11-01:用go语言,沿街有一排连续的房屋。每间房屋内都藏有一定的现金, 现在有一位小
    2023-11-01:用go语言,沿街有一排连续的房屋。每间房屋内都藏有一定的现金,现在有一位小偷计划从这些房屋中窃取现金,由于相邻的房屋装有相互连通的防盗系统,所以小偷不会窃取相邻的房屋,小偷的窃取能力定义为他在窃取过程中能从单间房屋中窃取的最大金额,给你一个整数数组nums表示每......
  • 线程池如何实现参数的动态修改?线程池如何调优?
    线程池如何实现参数的动态修改线程池提供了几个setter方法来设置线程池的参数。这里主要有两个思路:1、在微服务架构下,可以利用配置中心,如Nacos、Apollo等等,也可以自己开发配置中心。业务服务读取线程池配置,获取相应的线程池实例来修改线程池的参数。2、如果限制了配置中心的使用,也......
  • xml映射文件以及动态sql笔记
     ......
  • web中静态资源和动态资源的区别
    **静态资源:**可以理解为前端的固定页面,这里面包含HTML、CSS、JS、图片等等,不需要查数据库也不需要程序处理,直接就能够显示的页面,也就是说不需要从后台通过读取数据库信息就可以将在html上的所有数据全部显示出来,他的访问数据由于是不需要从数据库拉取数据,故而访问速度很快。**动态......
  • 基于Vue.js和Vanta.js的动态天空颜色效果实现
    背景最近在写一个Vue项目,想要在登录界面加一个动态背景效果,搜索之后发现了Vanta.js(https://www.vantajs.com/)这个库。Vanta可以借助three.js(WebGL)或p5.js渲染动态的3D背景效果,提供了多种预设。几种效果都挺不错的,最终我决定采用clouds效果。随即我发现这个效果是可......
  • python sqlalchemy 动态设置表名__tablename__,一个model对应多个table
    fromsqlalchemyimportcreate_engine,Column,BigInteger,Stringfromsqlalchemy.ext.declarativeimportdeclarative_basefromsqlalchemy.ormimportsessionmakerbase=declarative_base()engine=create_engine("postgresql://postgresadmin:[email protected]......