比题目更值得学习的「正难则反」的思考原则,以及 OJ 的溢出注意点|Java 刷题打卡

简介: 比题目更值得学习的「正难则反」的思考原则,以及 OJ 的溢出注意点|Java 刷题打卡

题目描述



这是 LeetCode 上的 554. 砖墙


Tag : 「哈希表」


你的面前有一堵矩形的、由 n 行砖块组成的砖墙。


这些砖块高度相同(也就是一个单位高)但是宽度不同。每一行砖块的宽度之和相等。


你现在要画一条 自顶向下 的、穿过 最少 砖块的垂线。


如果你画的线只是从砖块的边缘经过,就不算穿过这块砖。


你不能沿着墙的两个垂直边缘之一画线,这样显然是没有穿过一块砖的。


给你一个二维数组 wall ,该数组包含这堵墙的相关信息。


其中,wall[i] 是一个代表从左至右每块砖的宽度的数组。


你需要找出怎样画才能使这条线 穿过的砖块数量最少 ,并且返回 穿过的砖块数量 。

 

示例 1:


网络异常,图片无法展示
|


输入:wall = [[1,2,2,1],[3,1,2],[1,3,2],[2,4],[3,1,2],[1,3,1,1]]
输出:2
复制代码


示例 2:


输入:wall = [[1],[1],[1]]
输出:3
复制代码


提示:


  • n == wall.length
  • 1 <= n <= 10410^4104
  • 1 <= wall[i].length <= 10410^4104
  • 1 <= sum(wall[i].length) <= 2 * 10410^4104
  • 对于每一行 i ,sum(wall[i]) 是相同的
  • 1 <= wall[i][j] <= 2312^{31}231 - 1


哈希表



题目要求穿过的砖块数量最少,等效于通过的间隙最多。


我们可以使用「哈希表」记录每个间隙的出现次数,最终统计所有行中哪些间隙出现得最多,使用「总行数」减去「间隙出现的最多次数」即是答案。


如何记录间隙呢?直接使用行前缀记录即可。


就用示例数据来举 🌰 :


网络异常,图片无法展示
|


  • 第 1 行的间隙有 [1,3,5]
  • 第 2 行的间隙有 [3,4]
  • 第 3 行的间隙有 [1,4]
  • 第 4 行的间隙有 [2]
  • 第 5 行的间隙有 [3,4]
  • 第 6 行的间隙有 [1,4,5]


对间隙计数完成后,遍历「哈希表」找出出现次数最多间隙 4,根据同一个间隙编号只会在单行内被统计一次,用总行数减去出现次数,即得到「最少穿过的砖块数」。


代码:


class Solution {
    public int leastBricks(List<List<Integer>> wall) {
        int n = wall.size();
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0, sum = 0; i < n; i++, sum = 0) {
            for (int cur : wall.get(i)) {
                sum += cur;
                map.put(sum, map.getOrDefault(sum, 0) + 1);
            }
            map.remove(sum); // 不能从两边穿过,需要 remove 掉最后一个
        }
        int ans = n;
        for (int u : map.keySet()) {
            int cnt = map.get(u);
            ans = Math.min(ans, n - cnt);
        }
        return ans;
    }
}
复制代码


  • 时间复杂度:记所有砖块数量为 n,所有砖块都会被扫描。复杂度为 O(n)O(n)O(n)
  • 空间复杂度:O(n)O(n)O(n)


关于是否需要考虑「溢出」的说明



类似的问题,在之前 题解 也说过,这里再提一下 ~


当 Java 发生溢出时,会直接转成负数来处理。因此对于本题不会影响正确性(不重复溢出的话)。


可以通过以下例子来体会:


{
    System.out.println(Integer.MIN_VALUE); // -2147483648
    int a = Integer.MAX_VALUE;
    System.out.println(a); // 2147483647
    a += 1;
    System.out.println(a); // -2147483648
    a -= 1;
    System.out.println(a); //2147483647
}
复制代码


这意味着,如果我们在运算过程中如果只涉及「纯加减运算」,而不涉及「乘除」、「取最大值/最小值」和「数值大小判断」的话,Java 是不需要使用 Long 来确保正确性的,因为最终溢出会被转化回来。


按道理,CPP 本身对于 int 溢出的转化处理也是一样的。


但在 LC 上的 CPP 发生溢出时,不会直接转成负数来处理,而是直接抛出异常。因此同样的代码在 LC 上是无法被正常执行的:


{
    cout << INT_MIN << endl; //-2147483648
    int a = INT_MAX; 
    cout << a << endl; // 2147483647
    a += 1; // 溢出报错
    cout << a << endl;
    a -= 1;
    cout << a << endl;
}
复制代码


这是一般性的,对于 LC 上的同一道题,Java 不需要处理溢出,CPP 需要处理的原因。


其实对应到本题,我这里不使用 long long,是因为猜想「题目」中的条件写错了:


1 <= sum(wall[i]) <= 2 * 10^4
错写成了
1 <= sum(wall[i].length) <= 2 * 10^4
复制代码


因为在给定下面两个条件的情况下:


1 <= n <= 10^4
1 <= wall[i].length <= 10^4
复制代码


1<=sum(wall[i].length)<=2∗1041 <= sum(wall[i].length) <= 2 * 10^41<=sum(wall[i].length)<=2104 十分多余。


针对反例:


[
    [2147483647,2147483647,2147483647,2147483647,1,2], 
    [2,2147483647,2147483647,2147483647,2147483647,1]
]
复制代码


我用官方提供的代码,跑的也是 0,所以如果是以官方的代码为评测标准的话,用 long long 反而是 WA 了 🤣


最后


这是我们「刷穿 LeetCode」系列文章的第 No.554 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先将所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
12天前
|
设计模式 架构师 Java
Java开发工程师转架构师需要学习什么
Java开发工程师转型为架构师需掌握多项技能:精通Java及框架、数据库与分布式系统;熟悉设计模式与架构模式;积累项目经验;提升沟通与领导力;持续学习新技术;培养系统设计与抽象能力;了解中间件及开发工具;并注重个人特质与职业发展。具体路径应结合个人目标与实际情况制定。
39 18
|
25天前
|
监控 Java 调度
【Java学习】多线程&JUC万字超详解
本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
101 6
【Java学习】多线程&JUC万字超详解
|
1月前
|
前端开发 Java 编译器
【前端学java】如何从前端视角快速学习Maven
【8月更文挑战第12天】如何从前端视角快速学习Maven
43 2
【前端学java】如何从前端视角快速学习Maven
|
1月前
|
数据采集 供应链 JavaScript
分享基于Java开发的Java毕业设计实战项目题目
这篇文章分享了67套基于Java开发的毕业设计实战项目题目,覆盖了互联网、企业管理、电子政务、Java基础项目、ERP系统、校园相关、医疗以及其他细分行业等多个领域,并推荐了使用IDEA、Vue和Springboot的技术栈。
|
1月前
|
算法 Java
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
38 6
|
1月前
|
存储 Java 编译器
刷完一千道java笔试题的常见题目分析
这篇文章是关于刷完一千道Java笔试题后的常见题目分析,涵盖了Java基础知识点,如标识符命名规则、抽象类与接口的区别、String类的equals方法、try-catch-finally块的执行逻辑、类与实例方法的区别、this与super关键字的用法、面向对象的基本概念、重写与重载的原则等,并建议结合JVM内存结构图加深理解。
刷完一千道java笔试题的常见题目分析
|
1月前
|
存储 算法 Java
Java零基础(1) - 从零开始学习数组
【8月更文挑战第1天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
34 1
|
1月前
|
Java 测试技术 开发者
Java零基础教学(07):学习正确的命名规范
【8月更文挑战第7天】Java零基础教学篇,手把手实践教学!
90 0
|
2月前
|
存储 Java 开发者
Java编程实践:探索面向对象设计原则
【7月更文挑战第31天】在Java的世界中,面向对象设计(OOP)原则是构建健壮、可维护和可扩展软件的基石。本文将深入探讨这些核心原则,并通过实际代码示例揭示其应用之美。
29 0
|
2月前
|
监控 Java API
Java面试题:解释微服务架构的概念及其优缺点,讨论微服务拆分的原则。
Java面试题:解释微服务架构的概念及其优缺点,讨论微服务拆分的原则。
61 0