LeetCode 371. 两整数之和(异或操作) / 639. 解码方法 II(动态规划)/ 437. 路径总和 III

371. 两整数之和

2021.9.26 每日一题

题目描述

给你两个整数 a 和 b ,不使用 运算符 + 和 - ​​​​​​​,计算并返回两整数之和。

示例 1:

输入:a = 1, b = 2
输出:3

示例 2:

输入:a = 2, b = 3
输出:5

提示:

-1000 <= a, b <= 1000

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

思路

从低位到高位进行计算,然后记录进位add,因为有负数,所以要32位全部进行计算
因为负数是补码形式,所以直接加也没问题

class Solution {
    public int getSum(int a, int b) {
        //用二进制算
        int sum = 0;
        int add = 0;
        for(int i = 0; i < 32; i++){
            int x = a & 1;
            int y = b & 1;
            int temp = x ^ y ^ add;
            add = ((x & y) | (x & add) | (y & add));
            a >>= 1;
            b >>= 1;
            sum = (temp << i) | sum;
        }
        return sum;
    }
}

然后发现异或能得到当前位,相与能得到进位,
所以先异或得到当前位,然后相与得到进位add
如果add等于0了,就说明计算停止了

class Solution {
    public int getSum(int a, int b) {
        //用二进制算
        int sum = 0;    //当前位
        int add = 0;    //进位
        while(b != 0){
            sum = a ^ b;            //异或得到当前位
            add = (a & b) << 1;      //然后相与得到进位
            b = add;
            a = sum;
        }
        return a;
    }
}

639. 解码方法 II

2021.9.27 每日一题

题目描述

一条包含字母 A-Z 的消息通过以下的方式进行了编码:

‘A’ -> 1
‘B’ -> 2

‘Z’ -> 26
要 解码 一条已编码的消息,所有的数字都必须分组,然后按原来的编码方案反向映射回字母(可能存在多种方式)。例如,“11106” 可以映射为:

“AAJF” 对应分组 (1 1 10 6)
“KJF” 对应分组 (11 10 6)
注意,像 (1 11 06) 这样的分组是无效的,因为 “06” 不可以映射为 ‘F’ ,因为 “6” 与 “06” 不同。

除了 上面描述的数字字母映射方案,编码消息中可能包含 '’ 字符,可以表示从 ‘1’ 到 ‘9’ 的任一数字(不包括 ‘0’)。例如,编码字符串 "1" 可以表示 “11”、“12”、“13”、“14”、“15”、“16”、“17”、“18” 或 “19” 中的任意一条消息。对 “1*” 进行解码,相当于解码该字符串可以表示的任何编码消息。

给你一个字符串 s ,由数字和 ‘*’ 字符组成,返回 解码 该字符串的方法 数目 。

由于答案数目可能非常大,返回对 109 + 7 取余 的结果。

示例 1:

输入:s = “"
输出:9
解释:这一条编码消息可以表示 “1”、“2”、“3”、“4”、“5”、“6”、“7”、“8” 或 “9” 中的任意一条。
可以分别解码成字符串 “A”、“B”、“C”、“D”、“E”、“F”、“G”、“H” 和 “I” 。
因此,"
” 总共有 9 种解码方法。

示例 2:

输入:s = “1*”
输出:18
解释:这一条编码消息可以表示 “11”、“12”、“13”、“14”、“15”、“16”、“17”、“18” 或 “19” 中的任意一条。
每种消息都可以由 2 种方法解码(例如,“11” 可以解码成 “AA” 或 “K”)。
因此,“1*” 共有 9 * 2 = 18 种解码方法。

示例 3:

输入:s = “2*”
输出:15
解释:这一条编码消息可以表示 “21”、“22”、“23”、“24”、“25”、“26”、“27”、“28” 或 “29” 中的任意一条。
“21”、“22”、“23”、“24”、“25” 和 “26” 由 2 种解码方法,但 “27”、“28” 和 “29” 仅有 1 种解码方法。
因此,“2*” 共有 (6 * 2) + (3 * 1) = 12 + 3 = 15 种解码方法。

提示:

1 <= s.length <= 10^5
s[i] 是 0 - 9 中的一位数字或字符 ‘*’

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

思路

其实不难,一个很好理解的动态规划,看前一位的字符是什么,然后规划
但是这里要注意数据的范围
只变成long,还是会溢出
只取余,因为有可能乘以15,乘以9这种操作还是会导致溢出
所以要long + 取余,才能保证结果的正确性

class Solution {
    public static final int MOD = (int)1e9 + 7;
    public int numDecodings(String s) {
        //动态规划一看就是
        int l = s.length();
        if(s.charAt(0) == '0')
            return 0;
        long[] dp = new long[l + 1];
        dp[0] = 1;  //初始化,为了处理方便
        dp[1] = s.charAt(0) == '*' ? 9 : 1;
        for(int i = 2; i <= l; i++){
            char c = s.charAt(i - 1);
            char pre = i == 1 ? ' ' : s.charAt(i - 2);
            //首先处理单个字符的情况
            //如果是0,只能和前面组合
            if(c == '0'){
                //如果前面的数不满足条件,说明0无法被匹配,返回0
                if(pre == '0' || (pre > '2' && pre <= '9'))
                    return 0;
                //如果是1,2,只有一种情况
                else if(pre == '1' || pre == '2')
                    dp[i] = dp[i - 2];
                //如果是*
                else
                    dp[i] = dp[i - 2] * 2 % MOD;
            }else if(c == '*'){
                if(pre == '0' || (pre > '2' && pre <= '9'))
                    dp[i] = dp[i - 1] * 9 % MOD;
                else if(pre == '1')
                    dp[i] = (dp[i - 2] * 9 % MOD + dp[i - 1] * 9 % MOD) % MOD;
                else if(pre == '2')
                    dp[i] = (dp[i - 2] * 6 % MOD + dp[i - 1] * 9 % MOD) % MOD;
                //如果前面也是*
                else
                    dp[i] = (dp[i - 1] * 9 % MOD + dp[i - 2] * 15 % MOD) % MOD;
            //剩余情况,c就是一个数字
            }else{
                //首先单个数字组成的情况
                dp[i] = dp[i - 1];
                //其次,看和前面的情况
                if(pre == '1')
                    dp[i] = (dp[i] + dp[i - 2]) % MOD;
                else if(pre == '2' && c <= '6'){
                    dp[i] = (dp[i] + dp[i - 2]) % MOD;
                }else if(pre == '*'){
                    dp[i] = (dp[i] + dp[i - 2]) % MOD;
                    if(c <= '6')
                        dp[i] = (dp[i] + dp[i - 2]) % MOD;
                }
            }
        }
        return (int)dp[l];
    }
}

学习三叶姐的第二个动规,不是按照两个字符分情况讨论的
而是遍历26个字符,然后判断两个字符是否能组成这些字母来处理的
代码更简洁
但是这里用了一个f[3]来表示三个变量,可能让人有点迷糊,其实用三个变量来表示就更简单易懂了

class Solution {
    int mod = (int)1e9+7;
    public int numDecodings(String s) {
        int n = s.length();
        long[] f = new long[3];
        f[0] = 1;
        for (int i = 1; i <= n; i++) {
            char c = s.charAt(i - 1);
            int t = c - '0';
            long cnt = 0;
            int p1 = (i - 1) % 3, p2 = (i - 2) % 3;
            // 枚举组成什么 item(A -> 1; B -> 2 ...)
            for (int item = 1; item <= 26; item++) { 
                if (item < 10) { // 该 item 由一个字符组成
                    if (c == '*' || t == item) cnt += f[p1];
                } else { // 该 item 由两个字符组成
                    if (i - 2 < 0) break;
                    char prev = s.charAt(i - 2);
                    int u = prev - '0';
                    int a = item / 10, b = item % 10;
                    if ((prev == '*' || u == a) && (t == b || (c == '*' && b != 0))) cnt += f[p2];
                }
            }
            f[i % 3] = cnt % mod;
        }
        return (int)(f[n % 3]);
    }
}

作者:AC_OIer
链接:https://leetcode-cn.com/problems/decode-ways-ii/solution/gong-shui-san-xie-fen-qing-kuang-tao-lun-902h/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这种打表的应该更快,爱了

class Solution {
    int MOD = (int)1e9 + 7;
    static HashMap<String, Integer> one = new HashMap<>(){}, two = new HashMap<>();
    static{
        for(int i=1;i<10;i++)
            one.put(String.format("%d", i), 1);
        one.put("*", 9);
        for(int i=10;i<27;i++)
            two.put(String.format("%d", i), 1);
        for(int i=0;i<7;i++)
            two.put("*"+i, 2);
        for(int i=7;i<10;i++)
            two.put("*"+i, 1);
        two.put("1*", 9);
        two.put("2*", 6);
        two.put("**", 15);
    }
    public int numDecodings(String s) {
        int dp0 = 1, dp1 = one.getOrDefault(s.substring(0,1),0);
        for(int i=1,tmp=0;i<s.length();i++){
            tmp = dp0;
            dp0 = dp1;
            dp1 = (int)(((long)dp1 * one.getOrDefault(s.substring(i,i+1),0) % MOD + (long)tmp * two.getOrDefault(s.substring(i-1,i+1), 0) % MOD) % MOD);
        }
        return dp1;
    }
}

作者:himymBen
链接:https://leetcode-cn.com/problems/decode-ways-ii/solution/pythonjava-dong-tai-gui-hua-by-himymben-9ulz/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

437. 路径总和 III

2021.9.28 每日一题

题目描述

给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。

路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

示例 1:

输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示。

示例 2:

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:3

提示:

二叉树的节点个数的范围是 [0,1000]
-10^9 <= Node.val <= 10^9
-1000 <= targetSum <= 1000

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

思路

一个简单的回溯,记录每个节点处的和,然后回溯的时候将这个和从集合中去掉
因为和可能出现多次,所以不能用set
再考虑为了方便查找,所以用map

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    Map<Integer, Integer> map = new HashMap<>();
    int count = 0;
    int targetSum;
    public int pathSum(TreeNode root, int targetSum) {
        //我想到一个思路就是创建每条路径的前缀和
        //然后每次遍历创建一个集合,然后计算以这个点为结尾的,是否有何为target的路径
        //然后突然想到,每次遍历到一个位置,然后因为target是确定的,可以直接在set集合中查到,所以应该遍历一次就完事了
        //set不行,因为如果有重复的不会记录,所以得map,记录次数
        this.targetSum = targetSum;
        map.put(0, 1);
        dfs(root, 0);
        return count;
    }

    public void dfs(TreeNode node, int sum){
        if(node == null)
            return;
        sum += node.val;
        if(map.containsKey(sum - targetSum) && map.get(sum - targetSum) > 0){
            count += map.get(sum - targetSum);
        }
        map.put(sum, map.getOrDefault(sum, 0) + 1);
        dfs(node.left, sum);
        dfs(node.right, sum);
        map.put(sum, map.get(sum) - 1);
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,我来用中文回复这个链接:https://leetcode-cn.com/tag/dynamic-programming/ 这个链接是 LeetCode 上关于动态规划的题目集合。动态规划是一种常用的算法思想,可以用来解决很多实际问题,比如最长公共子序列、背包问题、最短路径等等。在 LeetCode 上,动态规划也是一个非常重要的题型,很多题目都需要用到动态规划的思想来解决。 这个链接里包含了很多关于动态规划的题目,按照难度从简单到困难排列。每个题目都有详细的题目描述、输入输出样例、题目解析和代码实现等内容,非常适合想要学习动态规划算法的人来练习和提高自己的能力。 总之,这个链接是一个非常好的学习动态规划算法的资源,建议大家多多利用。 ### 回答2: 动态规划是一种算法思想,通常用于优化具有重叠子问题和最优子结构性质的问题。由于其成熟的数学理论和强大的实用效果,动态规划在计算机科学、数学、经济学、管理学等领域均有重要应用。 在计算机科学领域,动态规划常用于解决最优化问题,如背包问题、图像处理、语音识别、自然语言处理等。同时,在计算机网络和分布式系统中,动态规划也广泛应用于各种优化算法中,如链路优化、路由算法、网络流量控制等。 对于算法领域的程序员而言,动态规划是一种必要的技能和知识点。在LeetCode这样的程序员平台上,题目分类和标签设置十分细致和方便,方便程序员查找并深入学习不同类型的算法。 LeetCode动态规划标签下的题目涵盖了各种难度级别和场景的问题。从简单的斐波那契数列、迷宫问题到可以用于实际应用的背包问题、最长公共子序列等,难度不断递进且话题丰富,有助于开发人员掌握动态规划的实际应用技能和抽象思维模式。 因此,深入LeetCode动态规划分类下的题目学习和练习,对于程序员的职业发展和技能提升有着重要的意义。 ### 回答3: 动态规划是一种常见的算法思想,它通过将问题拆分成子问题的方式进行求解。在LeetCode中,动态规划标签涵盖了众多经典和优美的算法问题,例如斐波那契数列、矩阵链乘法、背包问题等。 动态规划的核心思想是“记忆化搜索”,即将中间状态保存下来,避免重复计算。通常情况下,我们会使用一张二维表来记录状态转移过程中的中间值,例如动态规划求解斐波那契数列问题时,就可以定义一个二维数组f[i][j],代表第i项斐波那契数列中,第j个元素的值。 在LeetCode中,动态规划标签下有众多难度不同的问题。例如,经典的“爬楼梯”问题,要求我们计算到n级楼梯的方案数。这个问题的解法非常简单,只需要维护一个长度为n的数组,记录到达每一级楼梯的方案数即可。类似的问题还有“零钱兑换”、“乘积最大子数组”、“通配符匹配”等,它们都采用了类似的动态规划思想,通过拆分问题、保存中间状态来求解问题。 需要注意的是,动态规划算法并不是万能的,它虽然可以处理众多经典问题,但在某些场景下并不适用。例如,某些问题的状态转移过程比较复杂,或者状态转移方程中存在多个参数,这些情况下使用动态规划算法可能会变得比较麻烦。此外,动态规划算法也存在一些常见误区,例如错用贪心思想、未考虑边界情况等。 总之,掌握动态规划算法对于LeetCode的学习和解题都非常重要。除了刷题以外,我们还可以通过阅读经典的动态规划书籍,例如《算法竞赛进阶指南》、《算法与数据结构基础》等,来深入理解这种算法思想。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值