LeetCode13. 罗马数字转整数 / 剑指 Offer 42. 连续子数组的最大和 / 剑指 Offer 43. 1~n 整数中 1 出现的次数

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode13. 罗马数字转整数 / 剑指 Offer 42. 连续子数组的最大和 / 剑指 Offer 43. 1~n 整数中 1 出现的次数相关的知识,希望对你有一定的参考价值。

13. 罗马数字转整数

2021.5.15 每日一题,周六了

题目描述

罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。

字符          数值
I             1
V             5
X             10
L             50
C             100
D             500
M             1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做  XXVII, 即为 XX + V + II 。

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。


示例 1:

输入: "III"
输出: 3
示例 2:

输入: "IV"
输出: 4
示例 3:

输入: "IX"
输出: 9
示例 4:

输入: "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.
示例 5:

输入: "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.

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

思路

没啥说的,模拟就行了,优先匹配两个字符的;
或者另一种思路,发现两个字符的罗马数字,左边都比右边小,因此遍历所给的字符串,添加每一位罗马数字对应的值,如果左边比右边小的情况,就把左边的值减去,再加上右边的值

class Solution {
    public int romanToInt(String s) {
        //列出所有情况
        Map<String, Integer> map = new HashMap<>(){
            {
                put("I", 1);
                put("IV", 4);
                put("V", 5);
                put("IX", 9);
                put("X", 10);
                put("XL", 40);
                put("L", 50);
                put("XC", 90);
                put("C", 100);
                put("CD", 400);
                put("D", 500);
                put("CM", 900);
                put("M", 1000);
            }
        };
        
        int ans = 0;
        for(int i = 0;i < s.length();) {
            //如果包含两个字符,优先匹配两个字符的
            if(i + 1 < s.length() && map.containsKey(s.substring(i, i+2))) {
                ans += map.get(s.substring(i, i+2));
                i += 2;
            } else {
                //包含一个字符
                ans += map.get(s.substring(i, i+1));
                i++;
            }
        }
        return ans;
    }
}

剑指 Offer 42. 连续子数组的最大和

题目描述

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

要求时间复杂度为O(n)。

示例1:

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

简单的动态规划
当然,因为dp[i]只与dp[i - 1]有关,因此可以用两个两个值优化看空间
另外,看了题解,如果原数组可以修改的话,可以直接修改原数组来当做dp数组

class Solution {
    public int maxSubArray(int[] nums) {
        //滑动窗口好像不行
        //动态规划
        //定义dp[i]为以i结尾的连续子数组的最大值
        //dp[i + 1] = (dp[i - 1] + nums[i], nums[i]);

        int l = nums.length;
        int[] dp = new int[l];
        dp[0] = nums[0];
        int max = dp[0];
        for(int i = 1; i < l; i++){
            dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
            max = Math.max(max, dp[i]);
        }
        return max;

    }
}

剑指 Offer 43. 1~n 整数中 1 出现的次数

题目描述

输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。

例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。


示例 1:

输入:n = 12
输出:5
示例 2:

输入:n = 13
输出:6

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

首先,我把1-1000有1的数字列举了一下,想找到规律
1 10 11 12 … 19 21 31 …91 100 101 110 111 112 … 119 121 131 … 191 201 210 211 … 219 221 231 .291 301… 1000 1001
1 有 1 个 1
个位每一位前面加个1,就是10 - 19 ,10个 后面为1,11 - 91,9个, 共 19 个 1
把两位数前面加个1,也就是100 - 199 共100 个1, 后面为1 就是两位数中1的个数,百位是1-9,也就是9*19个
然后后面位数更多的数字也是这么处理,这个就是规律,感觉还是挺正确的
然后就是怎么取到某一个数之前的1的个数了。
比如所给的12,那么1,个位前面加1,也就是小于等于12的,只能是10、11、12了,后面为1,也就是11了,也就是5个1

然后我就想将我的思路转换成代码,根据测试用例改了好久,在一片红色的解答错误之后,写了下面的代码:

class Solution {
    public int countDigitOne(int n) {
        return countone(n);
    }

    public int countone(int n){
        //又是1出现的个数,这次变成了十进制
        //1 10 11 12 .. 19 21 31 ..91 100 101 110 111 112 .. 119 121 131 ... 191 201 210 211 .. 219 221 231 .291 301.. 1000 1001
        //列举数据发现规律,好像还是动态规划
        //1 有 1 个 1
        //个位每一位前面加个1,就是10 - 19 ,10个  后面为1,11 - 91,9个, 共 19 个 1
        //把两位数前面加个1,也就是100 - 199 共100 个1, 后面为1 就是两位数中1的个数,百位是1-9,也就是9*19个  
        //这个就是规律,感觉还是挺正确的
        //然后就是怎么取到某一个数之前的1的个数了。
        //比如所给的12,那么1,个位前面加1,也就是小于等于12的,只能是10、11、12了,后面为1,也就是11了,也就是5个1

        //然后试着写一写代码
        //首先得判断有几位
        if(n == 0)
            return 0;
        if(n < 10)
            return 1;
        int ret = 0;
        int t = 100;
        int[] dp = new int[100];    //dp[i]表示i位数中1的个数
        dp[1] = 1;      //1位数,只有1个1
        int i = 1;
        while(t <= n){
            int qian = t / 10;
            int hou = dp[i] * 9;
            dp[i + 1] = qian + hou + dp[i];
            t = t * 10;
            i++;
            ret += dp[i];
        }

        //次数i + 1即为n的位数
        //此时,统计了i位的所有数的1的个数,剩下i+1位的数
        //例如553 前面就是5,后面就是53
        int base = (int)Math.pow(10, i);     //553, i = 2, base = 100
        int qian = n / base;            //qian = 5
        int hou = n % (qian * base);      //hou = 53

        //处理1开头的数
        if(qian > 1)                    //1开头的所有数100 - 199,100个 
            ret += base;
        else{                           //如果是1开头
            ret += hou + 1;
        }
        //处理后面有1的数
        ret += (qian - 1) * dp[i];      //100 - 500
        //剩下500 - 553,即50 - 53,循环吧 
        n = n - qian * base;

        return ret + countone(n);
    }
}

然后过了29/40个例子,还是有问题,应该还是一些细节处理上的问题,但是终于不想改了。思路呢,感觉还是问题不大,主要是细节太多
去看题解了,哈哈哈

学习Krahets大神的思路:
将1 - n 每一个数的个、十位、百位…出现1的次数相加,就是1的总数
设此时计算的位是cur,比它高的位叫高位high,低的位叫低位low

根据此时当前位cur的值不同,分为三种情况:
当cur = 0时,例如n = 2304,计算十位出现1的次数,此时,能出现1的数字范围为0010 - 2219,不看十位固定的1,即000-229共230个数,此时计算公式为23 * 10 = 230,即只看高位23
当cur = 1时,例如2314,此时,出现1的数字范围是0010 - 2314,即000-234,共235个数,计算公式为23 * 10 + 4 + 1 =235,即高低位都看
当cur = 其他 时,此时,例如2354,出现1的数字范围0010 - 2319,即000 - 239共240个数,计算公式为(23 + 1) * 10 = 240,即只看高位23

class Solution {
    public int countDigitOne(int n) {
        //代码
        int res = 0;
        int base = 1;
        int high = n / 10;
        int low = 0;
        int cur = n % 10;
		//注意这个条件,high为0的时候,cur是最高位,肯定不能为0,因此这样写条件能保证所有位都被遍历到
        while(high > 0 || cur > 0){
            if(cur == 0)
                res += high * base;
            else if(cur == 1)
                res += high * base + low + 1;
            else
                res += (high + 1) * base;
            
            low = base * cur + low;
            cur = high % 10;
            base *= 10;
            high = high / 10;
        }
        return res;
    }
}

然后就这样过了,马马虎虎吧
这个题怎么说呢,刚开始自己想的时候思路可能有点偏了吧,然后就一直迷失在细节的修改上,到最后也没改出来正确的,但是感觉思路虽然复杂了点,应该还是没问题的
而题解这个思路呢,就是从排列组合的角度,值得学习!!!

以上是关于LeetCode13. 罗马数字转整数 / 剑指 Offer 42. 连续子数组的最大和 / 剑指 Offer 43. 1~n 整数中 1 出现的次数的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode12. 整数转罗马数字 / 剑指 Offer 40. 最小的k个数 / 剑指 Offer 41. 数据流中的中位数

leetcode-13.罗马数字转整数

LeetCode13.罗马数字转整数(Python3)

LeetCode:罗马数字转整数13

[leetcode] 13. 罗马数字转整数

LeetCode 13 罗马数字转整数