算法数据结构(三十七)----状态压缩技巧

题目一

链接:https://leetcode-cn.com/problems/can-i-win

在 "100 game" 这个游戏中,两名玩家轮流选择从 1 到 10 的任意整数,累计整数和,先使得累计整数和达到或超过 100 的玩家,即为胜者。

如果我们将游戏规则改为 “玩家不能重复使用整数” 呢?

例如,两个玩家可以轮流从公共整数池中抽取从 1 到 15 的整数(不放回),直到累计整数和 >= 100。

给定一个整数 maxChoosableInteger (整数池中可选择的最大数)和另一个整数 desiredTotal(累计和),判断先出手的玩家是否能稳赢(假设两位玩家游戏时都表现最佳)?

你可以假设 maxChoosableInteger 不会大于 20, desiredTotal 不会大于 300。

// 1~choose 拥有的数字
	// total 一开始的剩余
	// 返回先手会不会赢
	public static boolean canIWin0(int choose, int total) {
		if (total == 0) {
			return true;
		}
		if ((choose * (choose + 1) >> 1) < total) {
			return false;
		}
		int[] arr = new int[choose];
		for (int i = 0; i < choose; i++) {
			arr[i] = i + 1;
		}
		// arr[i] != -1 表示arr[i]这个数字还没被拿走
		// arr[i] == -1 表示arr[i]这个数字已经被拿走
		// 集合,arr,1~choose
		return process(arr, total);
	}

	// 当前轮到先手拿,
	// 先手只能选择在arr中还存在的数字,
	// 还剩rest这么值,
	// 返回先手会不会赢
	public static boolean process(int[] arr, int rest) {
		if (rest <= 0) {
			return false;
		}
		// 先手去尝试所有的情况
		for (int i = 0; i < arr.length; i++) {
			if (arr[i] != -1) {
				int cur = arr[i];
				arr[i] = -1;
				boolean next = process(arr, rest - cur);
				arr[i] = cur;
				if (!next) {
					return true;
				}
			}
		}
		return false;
	}
// 暴力尝试
	public static boolean canIWin1(int choose, int total) {
		if (total == 0) {
			return true;
		}
		if ((choose * (choose + 1) >> 1) < total) {
			return false;
		}
		return process1(choose, 0, total);
	}

	// 当前轮到先手拿,
	// 先手可以拿1~choose中的任何一个数字
	// status   i位如果为0,代表没拿,当前可以拿
	//          i位为1,代表已经拿过了,当前不能拿
	// 还剩rest这么值,
	// 返回先手会不会赢
	public static boolean process1(int choose, int status, int rest) {
		if (rest <= 0) {
			return false;
		}
		for (int i = 1; i <= choose; i++) {
			if (((1 << i) & status) == 0) { // i 这个数字,是此时先手的决定!
				if (!process1(choose, (status | (1 << i)), rest - i)) {
					return true;
				}
			}
		}
		return false;
	}
// 暴力尝试改动态规划
	public static boolean canIWin2(int choose, int total) {
		if (total == 0) {
			return true;
		}
		if ((choose * (choose + 1) >> 1) < total) {
			return false;
		}
		int[] dp = new int[1 << (choose + 1)];
		// dp[status] == 1  true
		// dp[status] == -1  false
		// dp[status] == 0  process(status) 没算过!去算!
		return process2(choose, 0, total, dp);
	}

	// 为什么明明status和rest是两个可变参数,却只用status来代表状态(也就是dp)
	// 因为选了一批数字之后,得到的和一定是一样的,所以rest是由status决定的,所以rest不需要参与记忆化搜索
	public static boolean process2(int choose, int status, int rest, int[] dp) {
		if (dp[status] != 0) {
			return dp[status] == 1 ? true : false;
		}
		boolean ans = false;
		if (rest > 0) {
			for (int i = 1; i <= choose; i++) {
				if (((1 << i) & status) == 0) {
					if (!process2(choose, (status | (1 << i)), rest - i, dp)) {
						ans = true;
						break;
					}
				}
			}
		}
		dp[status] = ans ? 1 : -1;
		return ans;
	}

题目二

 TSP问题 有N个城市,任何两个城市之间的都有距离,任何一座城市到自己的距离都为0。所有点到点的距 离都存在一个N*N的二维数组matrix里,也就是整张图由邻接矩阵表示。现要求一旅行商从k城市 出发必须经过每一个城市且只在一个城市逗留一次,最后回到出发的k城,返回总距离最短的路的 距离。参数给定一个matrix给定k

 

public static int t1(int[][] matrix) {
		int N = matrix.length; // 0...N-1
		// set
		// set.get(i) != null i这座城市在集合里
		// set.get(i) == null i这座城市不在集合里
		List<Integer> set = new ArrayList<>();
		for (int i = 0; i < N; i++) {
			set.add(1);
		}
		return func1(matrix, set, 0);
	}

	// 任何两座城市之间的距离,可以在matrix里面拿到
	// set中表示着哪些城市的集合,
	// start这座城一定在set里,
	// 从start出发,要把set中所有的城市过一遍,最终回到0这座城市,最小距离是多少
	public static int func1(int[][] matrix, List<Integer> set, int start) {
		int cityNum = 0;
		for (int i = 0; i < set.size(); i++) {
			if (set.get(i) != null) {
				cityNum++;
			}
		}
		if (cityNum == 1) {
			return matrix[start][0];
		}
		// cityNum > 1  不只start这一座城
		set.set(start, null);
		int min = Integer.MAX_VALUE;
		for (int i = 0; i < set.size(); i++) {
			if (set.get(i) != null) {
				// start -> i i... -> 0
				int cur = matrix[start][i] + func1(matrix, set, i);
				min = Math.min(min, cur);
			}
		}
		set.set(start, 1);
		return min;
	}
//暴力递归
public static int t2(int[][] matrix) {
		int N = matrix.length; // 0...N-1
		// 7座城 1111111
		int allCity = (1 << N) - 1;
		return f2(matrix, allCity, 0);
	}

	// 任何两座城市之间的距离,可以在matrix里面拿到
	// set中表示着哪些城市的集合,
	// start这座城一定在set里,
	// 从start出发,要把set中所有的城市过一遍,最终回到0这座城市,最小距离是多少
	public static int f2(int[][] matrix, int cityStatus, int start) {
		// cityStatus == cityStatux & (~cityStaus + 1)

		if (cityStatus == (cityStatus & (~cityStatus + 1))) {
			return matrix[start][0];
		}

		// 把start位的1去掉,
		cityStatus &= (~(1 << start));
		int min = Integer.MAX_VALUE;
		// 枚举所有的城市
		for (int move = 0; move < matrix.length; move++) {
			if ((cityStatus & (1 << move)) != 0) {
				int cur = matrix[start][move] + f2(matrix, cityStatus, move);
				min = Math.min(min, cur);
			}
		}
		cityStatus |= (1 << start);
		return min;
	}
public static int t3(int[][] matrix) {
		int N = matrix.length; // 0...N-1
		// 7座城 1111111
		int allCity = (1 << N) - 1;
		int[][] dp = new int[1 << N][N];
		for (int i = 0; i < (1 << N); i++) {
			for (int j = 0; j < N; j++) {
				dp[i][j] = -1;
			}
		}
		return f3(matrix, allCity, 0, dp);
	}

	// 任何两座城市之间的距离,可以在matrix里面拿到
	// set中表示着哪些城市的集合,
	// start这座城一定在set里,
	// 从start出发,要把set中所有的城市过一遍,最终回到0这座城市,最小距离是多少
	public static int f3(int[][] matrix, int cityStatus, int start, int[][] dp) {
		if (dp[cityStatus][start] != -1) {
			return dp[cityStatus][start];
		}
		if (cityStatus == (cityStatus & (~cityStatus + 1))) {
			dp[cityStatus][start] = matrix[start][0];
		} else {
			// 把start位的1去掉,
			cityStatus &= (~(1 << start));
			int min = Integer.MAX_VALUE;
			// 枚举所有的城市
			for (int move = 0; move < matrix.length; move++) {
				if (move != start && (cityStatus & (1 << move)) != 0) {
					int cur = matrix[start][move] + f3(matrix, cityStatus, move, dp);
					min = Math.min(min, cur);
				}
			}
			cityStatus |= (1 << start);
			dp[cityStatus][start] = min;
		}
		return dp[cityStatus][start];
	}
public static int t4(int[][] matrix) {
		int N = matrix.length; // 0...N-1
		int statusNums = 1 << N;
		int[][] dp = new int[statusNums][N];

		for (int status = 0; status < statusNums; status++) {
			for (int start = 0; start < N; start++) {
				if ((status & (1 << start)) != 0) {
					if (status == (status & (~status + 1))) {
						dp[status][start] = matrix[start][0];
					} else {
						int min = Integer.MAX_VALUE;
						// start 城市在status里去掉之后,的状态
						int preStatus = status & (~(1 << start));
						// start -> i
						for (int i = 0; i < N; i++) {
							if ((preStatus & (1 << i)) != 0) {
								int cur = matrix[start][i] + dp[preStatus][i];
								min = Math.min(min, cur);
							}
						}
						dp[status][start] = min;
					}
				}
			}
		}
		return dp[statusNums - 1][0];
	}

 题目三

你有无限的1*2的砖块,要铺满M*N的区域,

不同的铺法有多少种?

public static int ways1(int N, int M) {
		if (N < 1 || M < 1 || ((N * M) & 1) != 0) {
			return 0;
		}
		if (N == 1 || M == 1) {
			return 1;
		}
		int[] pre = new int[M]; // pre代表-1行的状况
		for (int i = 0; i < pre.length; i++) {
			pre[i] = 1;
		}
		return process(pre, 0, N);
	}

	// pre 表示level-1行的状态
	// level表示,正在level行做决定
	// N 表示一共有多少行 固定的
	// level-2行及其之上所有行,都摆满砖了
	// level做决定,让所有区域都满,方法数返回
	public static int process(int[] pre, int level, int N) {
		if (level == N) { // base case
			for (int i = 0; i < pre.length; i++) {
				if (pre[i] == 0) {
					return 0;
				}
			}
			return 1;
		}

		// 没到终止行,可以选择在当前的level行摆瓷砖
		int[] op = getOp(pre);
		return dfs(op, 0, level, N);
	}

	// op[i] == 0 可以考虑摆砖
	// op[i] == 1 只能竖着向上
	public static int dfs(int[] op, int col, int level, int N) {
		// 在列上自由发挥,玩深度优先遍历,当col来到终止列,i行的决定做完了
		// 轮到i+1行,做决定
		if (col == op.length) {
			return process(op, level + 1, N);
		}
		int ans = 0;
		// col位置不横摆
		ans += dfs(op, col + 1, level, N); // col位置上不摆横转
		// col位置横摆, 向右
		if (col + 1 < op.length && op[col] == 0 && op[col + 1] == 0) {
			op[col] = 1;
			op[col + 1] = 1;
			ans += dfs(op, col + 2, level, N);
			op[col] = 0;
			op[col + 1] = 0;
		}
		return ans;
	}

	public static int[] getOp(int[] pre) {
		int[] cur = new int[pre.length];
		for (int i = 0; i < pre.length; i++) {
			cur[i] = pre[i] ^ 1;
		}
		return cur;
	}
// Min (N,M) 不超过 32
	public static int ways2(int N, int M) {
		if (N < 1 || M < 1 || ((N * M) & 1) != 0) {
			return 0;
		}
		if (N == 1 || M == 1) {
			return 1;
		}
		int max = Math.max(N, M);
		int min = Math.min(N, M);
		int pre = (1 << min) - 1;
		return process2(pre, 0, max, min);
	}

	// 上一行的状态,是pre,limit是用来对齐的,固定参数不用管
	// 当前来到i行,一共N行,返回填满的方法数
	public static int process2(int pre, int i, int N, int M) {
		if (i == N) { // base case
			return pre == ((1 << M) - 1) ? 1 : 0;
		}
		int op = ((~pre) & ((1 << M) - 1));
		return dfs2(op, M - 1, i, N, M);
	}

	public static int dfs2(int op, int col, int level, int N, int M) {
		if (col == -1) {
			return process2(op, level + 1, N, M);
		}
		int ans = 0;
		ans += dfs2(op, col - 1, level, N, M);
		if ((op & (1 << col)) == 0 && col - 1 >= 0 && (op & (1 << (col - 1))) == 0) {
			ans += dfs2((op | (3 << (col - 1))), col - 2, level, N, M);
		}
		return ans;
	}
// 记忆化搜索的解
	// Min(N,M) 不超过 32
	public static int ways3(int N, int M) {
		if (N < 1 || M < 1 || ((N * M) & 1) != 0) {
			return 0;
		}
		if (N == 1 || M == 1) {
			return 1;
		}
		int max = Math.max(N, M);
		int min = Math.min(N, M);
		int pre = (1 << min) - 1;
		int[][] dp = new int[1 << min][max + 1];
		for (int i = 0; i < dp.length; i++) {
			for (int j = 0; j < dp[0].length; j++) {
				dp[i][j] = -1;
			}
		}
		return process3(pre, 0, max, min, dp);
	}

	public static int process3(int pre, int i, int N, int M, int[][] dp) {
		if (dp[pre][i] != -1) {
			return dp[pre][i];
		}
		int ans = 0;
		if (i == N) {
			ans = pre == ((1 << M) - 1) ? 1 : 0;
		} else {
			int op = ((~pre) & ((1 << M) - 1));
			ans = dfs3(op, M - 1, i, N, M, dp);
		}
		dp[pre][i] = ans;
		return ans;
	}

	public static int dfs3(int op, int col, int level, int N, int M, int[][] dp) {
		if (col == -1) {
			return process3(op, level + 1, N, M, dp);
		}
		int ans = 0;
		ans += dfs3(op, col - 1, level, N, M, dp);
		if (col > 0 && (op & (3 << (col - 1))) == 0) {
			ans += dfs3((op | (3 << (col - 1))), col - 2, level, N, M, dp);
		}
		return ans;
	}
// 严格位置依赖的动态规划解
	public static int ways4(int N, int M) {
		if (N < 1 || M < 1 || ((N * M) & 1) != 0) {
			return 0;
		}
		if (N == 1 || M == 1) {
			return 1;
		}
		int big = N > M ? N : M;
		int small = big == N ? M : N;
		int sn = 1 << small;
		int limit = sn - 1;
		int[] dp = new int[sn];
		dp[limit] = 1;
		int[] cur = new int[sn];
		for (int level = 0; level < big; level++) {
			for (int status = 0; status < sn; status++) {
				if (dp[status] != 0) {
					int op = (~status) & limit;
					dfs4(dp[status], op, 0, small - 1, cur);
				}
			}
			for (int i = 0; i < sn; i++) {
				dp[i] = 0;
			}
			int[] tmp = dp;
			dp = cur;
			cur = tmp;
		}
		return dp[limit];
	}

	public static void dfs4(int way, int op, int index, int end, int[] cur) {
		if (index == end) {
			cur[op] += way;
		} else {
			dfs4(way, op, index + 1, end, cur);
			if (((3 << index) & op) == 0) { // 11 << index 可以放砖
				dfs4(way, op | (3 << index), index + 1, end, cur);
			}
		}
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值