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. 数据流中的中位数