24-5-31背包题笔记

零钱兑换 

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

 

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3 
解释:11 = 5 + 5 + 1
示例 2:

输入:coins = [2], amount = 3
输出:-1
示例 3:

输入:coins = [1], amount = 0
输出:0

Java代码:外循环硬币类型遍历,内循环数额增大遍历

class Solution {
    public int coinChange(int[] coins, int amount) {
        int n = coins.length;
        int MAX = 0x3f3f3f3f;
        int[][] dp = new int[n + 1][amount + 1];
        for (int[] ints : dp) {
            Arrays.fill(ints, MAX);
        }

        for (int i = 0; i <= n; i++) {
            dp[i][0] = 0;
        }

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= amount; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j >= coins[i - 1]) {
                    dp[i][j] =min(dp[i][j], dp[i][j - coins[i - 1]] + 1);
                }
            }
        }
        return dp[n][amount] == 0x3f3f3f3f ? -1 : dp[n][amount];
    }
}

 优化:从最小的数额往后遍历,内循环各种类型的硬币遍历一次

class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, amount + 1);
        dp[0] = 0;

        for (int i = 0; i < dp.length; i++) {
            for (int coin : coins) {
                if (i - coin < 0) {
                    continue;
                }
                
                dp[i] =min(dp[i], dp[i - coin] + 1);
            }
        }
        
        return dp[amount] == amount + 1 ? -1 : dp[amount];
    }
}

零钱兑换Ⅱ

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。

假设每一种面额的硬币有无限个。 

题目数据保证结果符合 32 位带符号整数。

 

示例 1:

输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:

输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。
示例 3:

输入:amount = 10, coins = [10] 
输出:1

区别: dp[i][j] =min(dp[i][j], dp[i][j - coins[i - 1]] + 1);改 dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]];

class Solution {
    public int change(int amount, int[] coins) {
        if (coins == null) {
            return 0;
        }

        int n = coins.length;
        int[][] dp = new int[n + 1][amount + 1];

        for (int i = 0; i <= n; i++) {
            dp[i][0] = 1;
        }

        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= amount; j++) {
                if (j >= coins[i - 1]) {
                    dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]];
                }
                else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        
        return dp[n][amount];
    }
}

最后一块石一澜的重量Ⅱ(没看懂题意)

状态压缩常见操作

与位运算相关的运算符共有 6 种,&,|,^,~,>>,<< 符号,

x & y ,会将两个十进制数在二进制下进行与运算,然后返回其十进制下的值。

| 符号,x | y ,会将两个十进制数在二进制下进行或运算,然后返回其十进制下的值。

^ 符号,x ^ y ,会将两个十进制数在二进制下进行异或运算,然后返回其十进制下的值。

<< 符号,x << y 左移操作,将 x 在二进制下的每一位向左移动 y 位,最右边用 0 填充。

>> 符号,x >> y 右移操作,将 x 在二进制下的每一位向右移动 y 位,最左边用 0 填充。

~ 符号,~x ,按位取反操作,将 x 在二进制下的每一位取反,返回其十进制下的值

 

用位运算可以表示集合的常见操作,如下,其中 A,B 表示两个集合,c 表示某个元素。

c 插入 A :A |= (1 << c) A

删除 c :A &= ~(1 << c) A

置空 :A = 0

并集 :A | B

交集 :A & B

全集 :(1 << n) - 1

补集 :((1 << n) - 1) ^ A

子集 :(A & B) == B

判断是否是 2 的幂 :A & (A - 1) == 0

最低位的 1 变为 0 :n &= (n - 1)

最低位的 1:A & (-A),

最低位的 1 一般记为 lowbit(A),表示 A 的二进制表达式中最低位的 1 所对应的值。

最高位的 1: int p = lowbit(A) while(p != A) {     A -= p;     p = lowbit(A); } return p;

枚举 A 的子集: for(subset = (A - 1) & A; subset != A; subset = (subset - 1) & A) {     ... }

枚举全集的子集: for(i = 0; i <= (1 << n) - 1; ++i) {     ... }

安卓系统手势解锁

我们都知道安卓有个手势解锁的界面,是一个 3 x 3 的点所绘制出来的网格。用户可以设置一个 “解锁模式” ,通过连接特定序列中的点,形成一系列彼此连接的线段,每个线段的端点都是序列中两个连续的点。如果满足以下两个条件,则 k 点序列是有效的解锁模式:

解锁模式中的所有点互不相同 。
假如模式中两个连续点的线段需要经过其他点的 中心 ,那么要经过的点 必须提前出现 在序列中(已经经过),不能跨过任何还未被经过的点。
例如,点 5 或 6 没有提前出现的情况下连接点 2 和 9 是有效的,因为从点 2 到点 9 的线没有穿过点 5 或 6 的中心。
然而,点 2 没有提前出现的情况下连接点 1 和 3 是无效的,因为从圆点 1 到圆点 3 的直线穿过圆点 2 的中心。
以下是一些有效和无效解锁模式的示例:

无效手势:[4,1,3,6] ,连接点 1 和点 3 时经过了未被连接过的 2 号点。
无效手势:[4,1,9,2] ,连接点 1 和点 9 时经过了未被连接过的 5 号点。
有效手势:[2,4,1,3,6] ,连接点 1 和点 3 是有效的,因为虽然它经过了点 2 ,但是点 2 在该手势中之前已经被连过了。
有效手势:[6,5,4,1,9,2] ,连接点 1 和点 9 是有效的,因为虽然它经过了按键 5 ,但是点 5 在该手势中之前已经被连过了。
给你两个整数,分别为 ​​m 和 n ,那么请返回有多少种 不同且有效的解锁模式 ,是 至少 需要经过 m 个点,但是 不超过 n 个点的。

两个解锁模式 不同 需满足:经过的点不同或者经过点的顺序不同。

 

示例 1:

输入:m = 1, n = 1
输出:9
示例 2:

输入:m = 1, n = 2
输出:65

 状态压缩法

class Solution {
    bool check(int s, int i, int j) {
        int xi = i / 3, yi = i % 3;
        int xj = j / 3, yj = j % 3;
        int m = (xi + xj) / 2 * 3 + (yi + yj) / 2;
        return (xi + xj) % 2 || (yi + yj) % 2 || s & (1 << m);
    }

public:
    int numberOfPatterns(int m, int n) {
        // f[s][i]: 经过点集为 s,且最后经过的点是 i
        int f[1 << 9][9];
        memset(f, 0, sizeof(f));
        for (int i = 0; i < 9; i++) {
            f[1 << i][i] = 1;
        }
        for (int s = 1; s < 1 << 9; s++) {
            for (int i = 0; i < 9; i++) {
                if (s >> i & 1) {//遍历到i点时更新i为最后点的情况
                    for (int j = 0; j < 9; j++) {//遍历更新上个点是j点情况
                        if (j != i && s >> j & 1) {
                            if (check(s, j, i)) {
                                f[s][i] += f[s ^ (1 << i)][j];//继承去除i点的情况
                            }
                        }
                    }
                }
            }
        }
        int res = 0;
        for (int s = 1; s < 1 << 9; s++) {
            int c = __builtin_popcount(s);
            if (c >= m && c <= n) {//点数在m和n内
                for (int i = 0; i < 9; i++) {
                    res += f[s][i];
                }
            }
        }
        return res;
    }
};

DFS:替换了状态压缩中的bool check(int s, int i, int j)函数,多了skip数组来确定可行路径。dfs函数中带起始点,路径深度及深度限制,点访问情况,总方案数和skip数组。

class Solution {
public:
    
    int numberOfPatterns(int m, int n) {
        int ans =0;
        int skip[10][10];
        bool visited[10];
        memset(skip,0,sizeof(skip[0][0])*10*10);
        memset(visited,false,sizeof(visited));
        
        skip[1][3] = skip[3][1] =2;//当数字 1 跳到数字 3 时,中间经过的数字是 2
        skip[4][6] = skip[6][4] =5;
        skip[7][9] = skip[9][7] =8;
        skip[1][7] = skip[7][1] =4;
        skip[2][8] = skip[8][2] =5;
        skip[3][9] = skip[9][3] =6;
        skip[1][9] = skip[9][1] =skip[3][7] =skip[7][3] =5;

        visited[0] =true;
        int total =0;
        visited[1] =true;
        DFSHelper(1, visited, m, n, 1, total, skip);
        visited[1] =false;
        ans += 4 * total;//1 3 7 9
        total =0;
        visited[2] =true;
        DFSHelper(2, visited, m, n, 1, total, skip);
        visited[2] =false;
        ans += 4 * total;//2 4 6 8
        total =0;
        visited[5] =true;
        DFSHelper(5, visited, m, n, 1, total, skip);
        ans += total;//5
        visited[5] =false;
        return ans;
    }
    void DFSHelper(int start, bool visited[10], int m, int n, int depth, int &total, int skip[10][10]) {
        if (depth >=m) {
            total ++;
        }
        if (depth >=n) {
            return;
        }

        
        int ret =0;
        for (int i=1;i<=9;i++) {
            if (visited[i] ==false && visited[skip[start][i]]) {//可以到i
                visited[i] =true;
                DFSHelper(i, visited, m, n, depth + 1, total, skip);
                visited[i] =false;
            }
        }
        
    } 
};

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值