LeetCode1049. 最后一块石头的重量 II / 牛客:毕业旅行问题(状压DP)

1049. 最后一块石头的重量 II

2021.6.8每日一题
有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。

 
示例 1:

输入:stones = [2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。
示例 2:

输入:stones = [31,26,33,21,40]
输出:5
示例 3:

输入:stones = [1,2]
输出:1

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/last-stone-weight-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

五个月前会做的题,现在想不到咋做!
还是想办法转换成0-1背包问题

class Solution {
    public int lastStoneWeightII(int[] stones) {
        //这怎么动规
        //不会就先想暴力解法,暴力解就是回溯,复杂度n!
        //回溯过程中,变化的是下标和最终结果,数组好像也变,这咋办
        //看了官方题解,说实话,证明看的一知半解
        //但是隐约get到一点,就是相撞的过程并不需要考虑,即具体哪个石头撞并不需要去考虑,而只需要使两堆石头接近就可以
        //而这样两堆石头相撞能否得到绝对值之差呢,显然是可以的
        //因此,这样就可以用动态规划,来找到这个一堆中的最大值,也就是最接近sum / 2的值

        int sum = 0;
        int l = stones.length;
        for(int stone : stones)
            sum += stone;
        int target = sum / 2;
        //i个石头是否能凑出j的重量
        boolean[][] dp = new boolean[l + 1][target + 1];
        //初始化
        dp[0][0] = true;

        for(int i = 1; i <= l; i++){
            for(int j = 0; j <= target; j++){
                dp[i][j] = dp[i - 1][j];
                if(j >= stones[i - 1]){
                    dp[i][j] |= dp[i - 1][j - stones[i - 1]];
                }
            }
        }
        int res = 0;
        for(int j = target; j >= 0; j--){
            if(dp[l][j]){
                res = j;
                break;
            }
        }
        return sum - res * 2;
    }
}

状态压缩

class Solution {
    public int lastStoneWeightII(int[] stones) {
        int sum = 0;
        int l = stones.length;
        for(int stone : stones)
            sum += stone;
        int target = sum / 2;
        //i个石头是否能凑出j的重量
        boolean[] dp = new boolean[target + 1];
        //初始化
        dp[0] = true;

        for(int i = 1; i <= l; i++){
            for(int j = target; j >= 0; j--){
                dp[j] = dp[j];
                if(j >= stones[i - 1]){
                    dp[j] |= dp[j - stones[i - 1]];
                }
            }
        }
        int res = 0;
        for(int j = target; j >= 0; j--){
            if(dp[j]){
                res = j;
                break;
            }
        }
        return sum - res * 2;
    }
}

毕业旅行问题

题目描述

链接:https://www.nowcoder.com/questionTerminal/3d1adf0f16474c90b27a9954b71d125d
来源:牛客网

小明目前在做一份毕业旅行的规划。打算从北京出发,分别去若干个城市,然后再回到北京,每个城市之间均乘坐高铁,且每个城市只去一次。由于经费有限,希望能够通过合理的路线安排尽可能的省一些路上的花销。给定一组城市和每对城市之间的火车票的价钱,找到每个城市只访问一次并返回起点的最小车费花销。

输入描述:
城市个数n(1<n≤20,包括北京)

城市间的车票价钱 n行n列的矩阵 m[n][n]


输出描述:
最小车费花销 s
示例1
输入
4
0 2 6 5
2 0 4 4
6 4 0 2
5 4 2 0
输出
13

说明
共 4 个城市,城市 1 和城市 1 的车费为0,城市 1 和城市 2 之间的车费为 2,城市 1 和城市 3 之间的车费为 6,
城市 1 和城市 4 之间的车费为 5,依次类推。
假设任意两个城市之间均有单程票可购买,且票价在1000元以内,无需考虑极端情况。
思路

最小路径问题,好久没做过了,来复习一下,最简单的办法就是深度优先搜索吧
字节这题也太难了点吧…
状态压缩dp,有点难理解,学习一下

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int[][] dist = new int[n][n];
        for (int i = 0; i < dist.length; i++) {
            for (int j = 0; j < n; j++) {
                dist[i][j] = in.nextInt();
            }
        }
        in.close();
        //随便挑一个作为起点,为方便计算,就选择0城市就好,
        //那么对应的事件可以理解为从0号城市出发去(1号、2号、3号城市)最后回到0号城市,其中中间的1、2、3之间的顺序不清楚怎么去的。
        //但是可以发现事件最小消耗为min(从0->1的距离+从1号到{2号、3号}最后回到0的距离、
        // 从0->2的距离+从2号到{1号、3号}最后回到0的距离、
        // 从0->3的距离+从1号到{1号、2号}最后回到0的距离)。
        // 用bit第几位是否为1表示当前事件有哪几号城市。{2号、3号}——> 110;
        //具体可以再细分,这样就可以用状态压缩的动态规划来求解
        
        //事件的最大值,也就是最大是n-1位都为1
        int V = 1 << (n - 1);
        //表示从i城市出发,经过j中各个城市回到i城市的最短路径
        int[][] dp = new int[n][V];
		//初始化,从0号城市到达0号城市的距离是0
        dp[0][0] = 0;
        for (int j = 0; j < V; j++) {
            for (int i = 0; i < n; i++) {
                if (j == 0) {
                    //表示从第i个城市到第j(0)个城市的最小路径。正好就是第i个城市去第0个城市的距离。
                    dp[i][j] = dist[i][j] + dp[0][0];
                } else {
	                //当j不等于0时,枚举所有城市,如果此时经过了其中一个城市,那么dp[i][j]就可以由这个城市转移而来,即先到这个城市,再由这个城市到其他城市
                    dp[i][j] = Integer.MAX_VALUE;
					if(((j >> (i - 1)) & 1) == 0) { 
					/*此式子代表当dp[i][j]当j中的二进制数从右面数第i个数为零时,才进行后面运算,因为i代表以该城市为起点,后面不能再经过该城市*/
	                    for (int k = 1; k < n; k++) {
	                        //表示第k位城市。
	                        int index = (1 << (k - 1));
	                        //当前的dp应该是遍历了j城市集的每一个城市对应的子dp + i到k的距离,求得的其中的最小的那个值;
	                        if ((index & j) > 0) {
	                            //找到其中的一个k;
	
	                            //表示j城市集内除了第k位其他的别的城市
	                            int other = j ^ index;
	                            //从城市i到城市k,再从城市k到其他城市
	                            dp[i][j] = Math.min(dist[i][k] + dp[k][other], dp[i][j]);
	                        }
	                    }
                    }
                }
            }
        }
        //最后输出,从城市0到其他城市
        System.out.println(dp[0][V - 1]);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值