动态规划(二)

112 阅读4分钟

背包问题

给定两个长度都为N的数组weights和values,

weights[i]和values[i]分别代表 i号物品的重量和价值。

给定一个正数bag,表示一个载重bag的袋子,

你装的物品不能超过这个重量。

返回你能装下最多的价值是多少?

// 所有的货,重量和价值,都在w和v数组里
// 为了方便,其中没有负数
// bag背包容量,不能超过这个载重
// 返回:不超重的情况下,能够得到的最大价值
public static int maxValue(int[] w, int[] v, int bag) {
   if (w == null || v == null || w.length != v.length || w.length == 0) {
      return 0;
   }
   // 尝试函数!
   return process(w, v, 0, bag);
}

public static int process(int[] w, int[] v, int index, int rest) {
   if (index == w.length) {
      return 0;
   }
   int p1 = process(w, v, index + 1, rest); // 不要这个货
   // 如果要了这个货,背包剩余为负数,那么这种可能就不存在
   int p2 = rest - w[index] < 0 ? 0 : process(w, v, index + 1, rest - w[index]) + v[index];
   return Math.max(p1, p2);
}

public static int dp(int[] w, int[] v, int bag) {
   if (w == null || v == null || w.length != v.length || w.length == 0) {
      return 0;
   }
   int N = w.length;
   int[][] dp = new int[N + 1][bag + 1];
   for (int index = N - 1; index >= 0; index--) {
      for (int rest = 0; rest <= bag; rest++) {
         int p1 = dp[index + 1][rest];
         int p2 = rest - w[index] < 0 ? 0 : dp[index + 1][ rest - w[index]] + v[index];
         dp[index][rest] = Math.max(p1, p2);
      }
   }
   return dp[0][bag];
}

public static void main(String[] args) {
   int[] weights = { 3, 2, 4, 7, 3, 1, 7 };
   int[] values = { 5, 6, 3, 19, 12, 4, 2 };
   int bag = 15;
   System.out.println(maxValue(weights, values, bag));
   System.out.println(dp(weights, values, bag));
}

数字字符串转字母字符串

规定1和A对应、2和B对应、3和C对应...26和Z对应

那么一个数字字符串比如"111”就可以转化为:

"AAA"、"KA"和"AK"

给定一个只有数字字符组成的字符串str,返回有多少种转化结果

// str只含有数字字符0~9
// 返回多少种转化方案
public static int number(String str) {
   if (str == null || str.length() == 0) {
      return 0;
   }
   return process(str.toCharArray(), 0);
}

// str[0..i-1]转化无需过问
// str[i.....]去转化,返回有多少种转化方法
public static int process(char[] str, int i) {
   if (i == str.length) { // i到头了,说明找到一种转化方法,没到头的之前一定会return
      return 1;
   }
   // i没到最后,说明有字符
   if (str[i] == '0') { // 之前的决定有问题
      return 0;
   }

   int ways = process(str, i + 1); // i单独转
   if (i + 1 < str.length && (str[i] - '0') * 10 + str[i + 1] - '0' < 27) { // i 和 i + 1转
      ways += process(str, i + 2);
   }
   return ways;
}

// 从右往左的动态规划
// 就是上面方法的动态规划版本
// dp[i]表示:str[i...]有多少种转化方式
public static int dp1(String s) {
   if (s == null || s.length() == 0) {
      return 0;
   }
   char[] str = s.toCharArray();
   int N = str.length;
   int[] dp = new int[N + 1];
   dp[N] = 1;
   for (int i = N - 1; i >= 0; i--) {
      if (str[i] != '0') {
         int ways = dp[i + 1];
         if (i + 1 < str.length && (str[i] - '0') * 10 + str[i + 1] - '0' < 27) {
            ways += dp[i + 2];
         }
         dp[i] = ways;
      }
   }
   return dp[0];
}

691. 贴纸拼词

public static int longestCommonSubsequence1(String s1, String s2) {
   if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0) {
      return 0;
   }
   char[] str1 = s1.toCharArray();
   char[] str2 = s2.toCharArray();
   // 尝试
   return process1(str1, str2, str1.length - 1, str2.length - 1);
}
public static int minStickers2(String[] stickers, String target) {
   int N = stickers.length;
   // 关键优化(用词频表替代贴纸数组)
   int[][] counts = new int[N][26];
   for (int i = 0; i < N; i++) {
      char[] str = stickers[i].toCharArray();
      for (char cha : str) {
         counts[i][cha - 'a']++;
      }
   }
   int ans = process2(counts, target);
   return ans == Integer.MAX_VALUE ? -1 : ans;
}

// stickers[i] 数组,当初i号贴纸的字符统计 int[][] stickers -> 所有的贴纸
// 每一种贴纸都有无穷张
// 返回搞定target的最少张数
// 最少张数
public static int process2(int[][] stickers, String t) {
   if (t.length() == 0) {
      return 0;
   }
   // target做出词频统计
   // target  aabbc  2 2 1..
   //                0 1 2..
   char[] target = t.toCharArray();
   int[] tcounts = new int[26];
   for (char cha : target) {
      tcounts[cha - 'a']++;
   }
   int N = stickers.length;
   int min = Integer.MAX_VALUE; // 如果后续没有拼成 t 的可能,min就会是Integer.MAX_VALUE
   for (int i = 0; i < N; i++) {
      int[] sticker = stickers[i]; // 尝试第一张贴纸是谁
      if (sticker[target[0] - 'a'] > 0) {  // 剪枝,优先考虑可以抵扣掉target中某个字母的贴纸
         StringBuilder builder = new StringBuilder();
         for (int j = 0; j < 26; j++) { // 开始抵扣,得到抵扣后,剩下的字符串
            if (tcounts[j] > 0) {
               int nums = tcounts[j] - sticker[j];
               for (int k = 0; k < nums; k++) {
                  builder.append((char) (j + 'a'));
               }
            }
         }
         String rest = builder.toString();
         min = Math.min(min, process2(stickers, rest));
      }
   }
   return min + (min == Integer.MAX_VALUE ? 0 : 1);
}

public static int minStickers3(String[] stickers, String target) {
   int N = stickers.length;
   int[][] counts = new int[N][26];
   for (int i = 0; i < N; i++) {
      char[] str = stickers[i].toCharArray();
      for (char cha : str) {
         counts[i][cha - 'a']++;
      }
   }
   HashMap<String, Integer> dp = new HashMap<>();
   dp.put("", 0);
   int ans = process3(counts, target, dp);
   return ans == Integer.MAX_VALUE ? -1 : ans;
}

// 由于字符串t的长度范围不能确定,所以没法建立dp表,只能傻缓存
public static int process3(int[][] stickers, String t, HashMap<String, Integer> dp) {
   if (dp.containsKey(t)) {
      return dp.get(t);
   }
   char[] target = t.toCharArray();
   int[] tcounts = new int[26];
   for (char cha : target) {
      tcounts[cha - 'a']++;
   }
   int N = stickers.length;
   int min = Integer.MAX_VALUE;
   for (int i = 0; i < N; i++) {
      int[] sticker = stickers[i];
      if (sticker[target[0] - 'a'] > 0) {
         StringBuilder builder = new StringBuilder();
         for (int j = 0; j < 26; j++) {
            if (tcounts[j] > 0) {
               int nums = tcounts[j] - sticker[j];
               for (int k = 0; k < nums; k++) {
                  builder.append((char) (j + 'a'));
               }
            }
         }
         String rest = builder.toString();
         min = Math.min(min, process3(stickers, rest, dp));
      }
   }
   int ans = min + (min == Integer.MAX_VALUE ? 0 : 1);
   dp.put(t, ans);
   return ans;
}

1143. 最长公共子序列

public static int longestCommonSubsequence1(String s1, String s2) {
   if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0) {
      return 0;
   }
   char[] str1 = s1.toCharArray();
   char[] str2 = s2.toCharArray();
   // 尝试
   return process1(str1, str2, str1.length - 1, str2.length - 1);
}

public static int process1(char[] str1, char[] str2, int i, int j) {
   if (i == 0 && j == 0) {
      // str1[0..0]和str2[0..0],都只剩一个字符了
      // 那如果字符相等,公共子序列长度就是1,不相等就是0
      // 这显而易见
      return str1[i] == str2[j] ? 1 : 0;
   } else if (i == 0) {
      // 这里的情况为:
      // str1[0...0]和str2[0...j],str1只剩1个字符了,但是str2不只一个字符
      // 因为str1只剩一个字符了,所以str1[0...0]和str2[0...j]公共子序列最多长度为1
      // 如果str1[0] == str2[j],那么此时相等已经找到了!公共子序列长度就是1,也不可能更大了
      // 如果str1[0] != str2[j],只是此时不相等而已,
      // 那么str2[0...j-1]上有没有字符等于str1[0]呢?不知道,所以递归继续找
      if (str1[i] == str2[j]) {
         return 1;
      } else {
         return process1(str1, str2, i, j - 1);
      }
   } else if (j == 0) {
      // 和上面的else if同理
      // str1[0...i]和str2[0...0],str2只剩1个字符了,但是str1不只一个字符
      // 因为str2只剩一个字符了,所以str1[0...i]和str2[0...0]公共子序列最多长度为1
      // 如果str1[i] == str2[0],那么此时相等已经找到了!公共子序列长度就是1,也不可能更大了
      // 如果str1[i] != str2[0],只是此时不相等而已,
      // 那么str1[0...i-1]上有没有字符等于str2[0]呢?不知道,所以递归继续找
      if (str1[i] == str2[j]) {
         return 1;
      } else {
         return process1(str1, str2, i - 1, j);
      }
   } else { // i != 0 && j != 0
      // 这里的情况为:
      // str1[0...i]和str2[0...i],str1和str2都不只一个字符
      // 看函数开始之前的注释部分
      // p1就是可能性c)
      int p1 = process1(str1, str2, i - 1, j);
      // p2就是可能性b)
      int p2 = process1(str1, str2, i, j - 1);
      // p3就是可能性d),如果可能性d)存在,即str1[i] == str2[j],那么p3就求出来,参与pk
      // 如果可能性d)不存在,即str1[i] != str2[j],那么让p3等于0,然后去参与pk,反正不影响
      int p3 = str1[i] == str2[j] ? (1 + process1(str1, str2, i - 1, j - 1)) : 0;
      return Math.max(p1, Math.max(p2, p3));
   }
}

public static int longestCommonSubsequence2(String s1, String s2) {
   if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0) {
      return 0;
   }
   char[] str1 = s1.toCharArray();
   char[] str2 = s2.toCharArray();
   int N = str1.length;
   int M = str2.length;
   int[][] dp = new int[N][M];
   dp[0][0] = str1[0] == str2[0] ? 1 : 0;
   for (int j = 1; j < M; j++) {
      dp[0][j] = str1[0] == str2[j] ? 1 : dp[0][j - 1];
   }
   for (int i = 1; i < N; i++) {
      dp[i][0] = str1[i] == str2[0] ? 1 : dp[i - 1][0];
   }
   for (int i = 1; i < N; i++) {
      for (int j = 1; j < M; j++) {
         int p1 = dp[i - 1][j];
         int p2 = dp[i][j - 1];
         int p3 = str1[i] == str2[j] ? (1 + dp[i - 1][j - 1]) : 0;
         dp[i][j] = Math.max(p1, Math.max(p2, p3));
      }
   }
   return dp[N - 1][M - 1];
}