动态规划(六)

114 阅读2分钟

拆数组1

给定一个正数数组arr,

请把arr中所有的数分成两个集合,尽量让两个集合的累加和接近

返回:

最接近的情况下,较小集合的累加和

public static int right(int[] arr) {
   if (arr == null || arr.length < 2) {
      return 0;
   }
   int sum = 0;
   for (int num : arr) {
      sum += num;
   }
   return process(arr, 0, sum / 2);
}

// arr[i...]可以自由选择,请返回累加和尽量接近rest,但不能超过rest的情况下,最接近的累加和是多少?
public static int process(int[] arr, int i, int rest) {
   if (i == arr.length) {
      return 0;
   } else { // 还有数,arr[i]这个数
      // 可能性1,不使用arr[i]
      int p1 = process(arr, i + 1, rest);
      // 可能性2,要使用arr[i]
      int p2 = 0;
      if (arr[i] <= rest) {
         p2 = arr[i] + process(arr, i + 1, rest - arr[i]);
      }
      return Math.max(p1, p2);
   }
}

public static int dp(int[] arr) {
   if (arr == null || arr.length < 2) {
      return 0;
   }
   int sum = 0;
   for (int num : arr) {
      sum += num;
   }
   sum /= 2;
   int N = arr.length;
   int[][] dp = new int[N + 1][sum + 1];
   for (int i = N - 1; i >= 0; i--) {
      for (int rest = 0; rest <= sum; rest++) {
         int p1 = dp[i + 1][rest];
         int p2 = 0;
         if (arr[i] <= rest) {
            p2 = arr[i] + dp[i + 1][rest - arr[i]];
         }
         dp[i][rest] = Math.max(p1, p2);
      }
   }
   return dp[0][sum];
}

拆数组2

给定一个正数数组arr,请把arr中所有的数分成两个集合

如果arr长度为偶数,两个集合包含数的个数要一样多

如果arr长度为奇数,两个集合包含数的个数必须只差一个

请尽量让两个集合的累加和接近

返回:

最接近的情况下,较小集合的累加和

public static int right(int[] arr) {
    if (arr == null || arr.length < 2) {
        return 0;
    }
    int sum = 0;
    for (int num : arr) {
        sum += num;
    }
    if ((arr.length & 1) == 0) {
        // 如果是偶数,选一半的数,返回最接近sum / 2的这一半个数的总和
        return process(arr, 0, arr.length / 2, sum / 2);
    } else {
        // 如果是奇数,二者取更接近 sum / 2 的
        return Math.max(process(arr, 0, arr.length / 2, sum / 2), process(arr, 0, arr.length / 2 + 1, sum / 2));
    }
}

// arr[i....]自由选择,挑选的个数一定要是picks个,累加和<=rest, 离rest最近的返回
public static int process(int[] arr, int i, int picks, int rest) {
    if (i == arr.length) {
        return picks == 0 ? 0 : -1; // 如果最后挑选的不是pick个,答案无效,返回-1
    } else {
        int p1 = process(arr, i + 1, picks, rest);
        // 就是要使用arr[i]这个数
        int p2 = -1;
        int next = -1;
        if (arr[i] <= rest) {
            next = process(arr, i + 1, picks - 1, rest - arr[i]);
        }
        if (next != -1) {
            p2 = arr[i] + next;
        }
        return Math.max(p1, p2);
    }
}

public static int dp(int[] arr) {
    if (arr == null || arr.length < 2) {
        return 0;
    }
    int sum = 0;
    for (int num : arr) {
        sum += num;
    }
    sum /= 2;
    int N = arr.length;
    int M = (N + 1) / 2;
    int[][][] dp = new int[N + 1][M + 1][sum + 1];
    for (int i = 0; i <= N; i++) { // 默认都是无效解
        for (int j = 0; j <= M; j++) {
            for (int k = 0; k <= sum; k++) {
                dp[i][j][k] = -1;
            }
        }
    }
    for (int rest = 0; rest <= sum; rest++) { // 最下层的状况填好
        dp[N][0][rest] = 0;
    }
    for (int i = N - 1; i >= 0; i--) { // i依赖更大的值,所以从大到小填
        for (int picks = 0; picks <= M; picks++) { // 由于是依赖小的,所以从小到大
            for (int rest = 0; rest <= sum; rest++) { // rest也是依赖更小的值
                int p1 = dp[i + 1][picks][rest];
                // 就是要使用arr[i]这个数
                int p2 = -1;
                int next = -1;
                if (picks - 1 >= 0 && arr[i] <= rest) { // 不加picks - 1 >= 0,会越界,所以补上
                    next = dp[i + 1][picks - 1][rest - arr[i]];
                }
                if (next != -1) {
                    p2 = arr[i] + next;
                }
                dp[i][picks][rest] = Math.max(p1, p2);
            }
        }
    }
    if ((arr.length & 1) == 0) {
        return dp[0][arr.length / 2][sum];
    } else {
        return Math.max(dp[0][arr.length / 2][sum], dp[0][(arr.length / 2) + 1][sum]);
    }
}

N皇后问题

N皇后问题是指在N*N的棋盘上要摆N个皇后,

要求任何两个皇后不同行、不同列, 也不在同一条斜线上

给定一个整数n,返回n皇后的摆法有多少种。

n=1,返回 1

n=2 或 3,2 皇后和 3 皇后问题无论怎么摆都不行,返回 0

n=8,返回92

public static int num1(int n) {
   if (n < 1) {
      return 0;
   }
   int[] record = new int[n];
   return process1(0, record, n);
}

// 当前来到i行,一共是0~N-1行
// 在i行上放皇后,所有列都尝试
// 必须要保证跟之前所有的皇后不打架
// int[] record record[x] = y 之前的第x行的皇后,放在了y列上
// 返回:不关心i以上发生了什么,i.... 后续有多少合法的方法数
public static int process1(int i, int[] record, int n) {
   if (i == n) { // 最后一个棋能放上,说明找到了一种方法
      return 1;
   }
   int res = 0;
   // i行的皇后,放哪一列呢?j列,
   for (int j = 0; j < n; j++) {
      if (isValid(record, i, j)) { // 如果可以放的话
         record[i] = j;
         res += process1(i + 1, record, n);
      }
   }
   return res;
}

// record对之前每一个放上的棋子的位置进行记录,i j 为当前棋子位置,返回是否可以放当前棋子
public static boolean isValid(int[] record, int i, int j) {
   for (int k = 0; k < i; k++) {
      // 如果在之前放过的任意棋子的同一列或同一行或同一斜线,就不能放
      if (j == record[k] || Math.abs(record[k] - j) == Math.abs(i - k)) {
         return false;
      }
   }
   return true;
}