【LeetCode热题100】打卡第36天:多数元素打家劫舍

news2024/12/29 10:45:13

文章目录

  • 【LeetCode热题100】打卡第36天:多数元素&打家劫舍
    • ⛅前言
  • 多数元素
    • 🔒题目
    • 🔑题解
  • 打家劫舍
    • 🔒题目
    • 🔑题解

【LeetCode热题100】打卡第36天:多数元素&打家劫舍

⛅前言

大家好,我是知识汲取者,欢迎来到我的LeetCode热题100刷题专栏!

精选 100 道力扣(LeetCode)上最热门的题目,适合初识算法与数据结构的新手和想要在短时间内高效提升的人,熟练掌握这 100 道题,你就已经具备了在代码世界通行的基本能力。在此专栏中,我们将会涵盖各种类型的算法题目,包括但不限于数组、链表、树、字典树、图、排序、搜索、动态规划等等,并会提供详细的解题思路以及Java代码实现。如果你也想刷题,不断提升自己,就请加入我们吧!QQ群号:827302436。我们共同监督打卡,一起学习,一起进步。

博客主页💖:知识汲取者的博客

LeetCode热题100专栏🚀:LeetCode热题100

Gitee地址📁:知识汲取者 (aghp) - Gitee.com

题目来源📢:LeetCode 热题 100 - 学习计划 - 力扣(LeetCode)全球极客挚爱的技术成长平台

PS:作者水平有限,如有错误或描述不当的地方,恳请及时告诉作者,作者将不胜感激

多数元素

🔒题目

原题链接:169.多数元素

image-20230712234116208

🔑题解

  • 解法一:哈希表

    这个思路很简单,使用一个哈希表来映射数组元素与数组元素出现的次数即可

    class Solution {
        public int majorityElement(int[] nums) {
            int target = nums.length / 2;
            Map<Integer, Integer> map = new HashMap<>(16);
            for (int i = 0; i < nums.length; i++) {
                map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
                Integer value = map.get(nums[i]);
                if (value > target) {
                    return nums[i];
                }
            }
            // 根据题意,这里必不可达,所以这里返回啥都无所谓
            return -1;
        }
    }
    

    复杂度分析:

    • 时间复杂度: O ( n ) O(n) O(n)
    • 空间复杂度: O ( n ) O(n) O(n)

    其中 n n n 为数组中元素的个数

  • 解法二:排序

    这个相比较哈希表,就更加简单粗暴了🤣我感觉简单题写的采用意思,可以一题多解,拓展思路,对于中等以上的题,很多都只有那一种方式才显得更加优雅(可能是我题写的少吧)

    class Solution {
        public int majorityElement(int[] nums) {
            Arrays.sort(nums);
            return nums[nums.length / 2];
        }
    }
    

    复杂度分析:

    • 时间复杂度:最好 O ( n l o g n ) O(nlogn) O(nlogn),最坏 O ( n 2 ) O(n^2) O(n2),Arrays.sort底层是快排
    • 空间复杂度: O ( l o g n ) O(logn) O(logn),快排需要logn的栈空间

    其中 n n n 为数组中元素的个数

  • 解法三:Boyer-Moore投票算法(摩尔投票算法)

    这个算法可以说是所有求众数问题的究极解法了🤣,这个算法的诞生就是为了求众数,大家可以自行百度了解一下Boyer-Moore投票算法的诞生。算法的思想也很简单,这里我就详解讲解一下(●’◡’●)

    我们数分为两类,一类是众数,一类不是众数,遇到众数+1,遇到非众数-1。在+1和-1的过程中,可能会出现如下几种情况:

    情况一:count由负数 ==> 0;情况二:count由0 ==> 负数;情况三:count由正数 ==> 0;情况四:count由0 ==> 正数

    由于这群数中,众数的个数>=n/2,所以在遍历的过程中count一定会存在一种状态也就是 0,为什么呢?因为在+1和-1的消耗中,count最开始是0,最终count一定会是一个>=1的值。

    count状态为0可以有两种由来,一种是从正数过来,一种是从负数过来,显然从负数过来的才是众数,最后状态0,也一定是从负数过来的,为什么呢?因为众数的数量要多,count最后一定是正数

    以下是算法的三个示例,大家可以对比着代码看一下

    PS:你可能会感到疑惑,为什么count没有负数❓我们可以把它看成相反数,因为遇到不同的count会-1

    nums    1 1 1 2 2
    mode  0 1 1 1 1 1
    count 0 1 2 3 2 1   
    
    nums    2 2 1 1 1
    mode  0 2 2 2 2 1
    count 0 1 2 1 0 1   
    
    nums    2 1 2 1 1
    mode  0 2 2 2 2 1
    count 0 1 0 1 0 1  
    
    class Solution {
        public int majorityElement(int[] nums) {
            int count = 0;
            int mode = 0;
            for (int num : nums) {
                if (count == 0) {
                    // 状态为0
                    mode = num;
                }
                count += (num == mode) ? 1 : -1;
            }
            return mode;
        }
    }
    

    复杂度分析:

    • 时间复杂度: O ( n ) O(n) O(n)
    • 空间复杂度: O ( 1 ) O(1) O(1)

    其中 n n n 为数组中元素的个数

    趁热打铁:使用摩尔投票算法做完这题,可以接着写这题 【229.多数元素 II】

    略……这里直接给出一种万能解法
    
    /**
     * @author ghp
     * @title 摩尔投票算法万能解法(GPT写的,仅供参考)
     * @description 求解n个数中,次数大于 n/k 的数
     */
    public class Solution {
        private int k = 3;
        public List<Integer> majorityElement(int[] nums) {
            List<Integer[]> majority = new ArrayList<>();
            for (int i = 0; i < k - 1; i++) {
                majority.add(new Integer[]{0, 0});
            }
            for (int num : nums) {
                boolean exist = false;
                Integer idx = null;
                for (int i = 0; i < k - 1; i++) {
                    if (majority.get(i)[0] == 0 && idx == null) {
                        idx = i;
                    }
                    if (majority.get(i)[1] != null && num == majority.get(i)[1]) {
                        majority.get(i)[0] += 1;
                        exist = true;
                        break;
                    }
                }
                if (!exist) {
                    if (idx != null) {
                        majority.get(idx)[0] += 1;
                        majority.get(idx)[1] = num;
                    } else {
                        for (int i = 0; i < k - 1; i++) {
                            majority.get(i)[0] -= 1;
                        }
                    }
                }
            }
            Map<Integer, Integer> checks = new HashMap<>();
            for (Integer[] items : majority) {
                if (items[0] != 0) {
                    checks.put(items[1], 0);
                }
            }
    
            for (int num : nums) {
                if (checks.containsKey(num)) {
                    checks.put(num, checks.get(num) + 1);
                }
            }
            List<Integer> result = new ArrayList<>();
            int s = nums.length / k;
            for (Map.Entry<Integer, Integer> entry : checks.entrySet()) {
                if (entry.getValue() > s) {
                    result.add(entry.getKey());
                }
            }
            return result;
        }
    }
    

打家劫舍

🔒题目

原题链接:198.打家劫舍

image-20230712234140231

🔑题解

  • 解法一:暴力枚举+动态规划(能过,但是提交排名超级低,只有5%)

    暴力枚举实现起来也没有想象中的那么简单,我们需要枚举每一种组合,并且做到全局更新最大可偷金额,这里面运用了一点动态规划的思想在里面,当前房子可偷最大金额,①可以是从上一间房子推导而来,②也可以是从上两间房子+当前房子推导而来,也就是cur = Math.max(pre1, pre2 + nums[j]),因为我们不能连续偷,只能至少间隔一间房子偷。

    这里是pre1是存储着上一间房子的最大可偷金额,pre2记录着前两件房子的最大可偷金额,cur代表当前房子最大可偷金额,通过不断更新,最终通过ans = Math.max(ans, cur)更新就可以得到所有房子可偷金额的最大值

    /**
     * @author ghp
     * @title
     * @description
     */
    class Solution {
        public int rob(int[] nums) {
            int ans = 0;
            // 遍历所有可能出现的起始位置
            for (int i = 0; i < nums.length; i++) {
                int cur = 0;
                int pre1 = 0;
                int pre2 = 0;
                // 从当前起始位置开始偷
                for (int j = i; j < nums.length; j++) {
                    cur = Math.max(pre1, pre2 + nums[j]);
                    pre2 = pre1;
                    pre1 = cur;
                }
                ans = Math.max(ans, cur);
            }
            return ans;
        }
    }
    

    复杂度分析:

    • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
    • 空间复杂度: O ( 1 ) O(1) O(1)

    其中 n n n 为数组中元素的个数

  • 解法二:动态规划

    其实,从上面的题解中,我们就已经大致可以摸清那个状态转移的规律了。前面的题解中,每次我们都要从重新计算 i 到 nums.length 之间的最大值,然后计算 i+1 到 nums.length 之间的最大值,很明显 i 是包括了 i+1 的,所以我们可以使用一个dp数组来记录每一个房间可偷的最大值,则当前房间可偷最大值①可以是从上一间房子推导而来,②也可以是从上两间房子+当前房子推导而来,也就有了dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]),唯一需要注意的就是初始化时要防止NPE,这题应该也算是一个入门级别的 动态规划题了

    class Solution {
        public int rob(int[] nums) {
            if (nums.length == 1){
                return nums[0];
            }
            int n = nums.length;
            int[] dp = new int[n];
            dp[0] = nums[0];
            dp[1] = Math.max(nums[0], nums[1]);
            for (int i = 2; i < n; i++) {
                // 更新当前房间可偷最大值
                dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
            }
            return dp[n - 1];
        }
    }
    

    复杂度分析:

    • 时间复杂度: O ( n ) O(n) O(n)
    • 空间复杂度: O ( n ) O(n) O(n)

    其中 n n n 为数组中元素的个数

    代码优化:空间优化

    解法一中,我们通过使用一个pre1pre2来记录上一间房子可偷最大金额和前两间房子能偷的最大金额,我们可以直到当前房子的状态至于前两个防止的状态有关,所以我们完全可以使用两个变量来记录这两个状态,而不是使用dp数组

    pre1:前一间(也就是第i-1间)房子可偷最大金额;pre2:前两间房子(也就是第i-2间)房子可偷最大金额;cur:当前(第i间)房子可偷最大金额

    class Solution {
        public int rob(int[] nums) {
            if (nums.length == 1) {
                return nums[0];
            }
            int pre1 = Math.max(nums[0], nums[1]);
            int pre2 = nums[0];
            int cur = Math.max(pre1, pre2);
            for (int i = 2; i < nums.length; i++) {
                cur = Math.max(pre1, pre2 + nums[i]);
                pre2 = pre1;
                pre1 = cur;
            }
            return cur;
        }
    }
    

    复杂度分析:

    • 时间复杂度: O ( n ) O(n) O(n)
    • 空间复杂度: O ( 1 ) O(1) O(1)

    其中 n n n 为数组中元素的个数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/749037.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pytorch安装问题【超级简单版】

pytorch安装问题 当前遇到的问题&#xff1a; python3.9无法安装读取coco数据集的 pycocotools-windows,那么需要切换版本到3.6/7/8&#xff0c;但是切换到python 3.6之后&#xff0c;无法安装torchvision和pytorch【在python就叫torch】&#xff0c;显示没有这个版本 pip i…

MS1205N激光测距用高精度时间测量(TDC)电路

MS1205N 是一款高精度时间测量 (TDC) 电路&#xff0c;具有四通 道、多脉冲的采样能力、高速 SPI 通讯、多种测量模式&#xff0c;适合 于激光雷达和激光测距。 主要特点  单精度模式 60ps  双精度模式 30ps  非校准测量范围 3.5ns(0ns) 至 25μs  单…

案例分析:成功的APP开发背后的故事

如今&#xff0c;我们生活在一个信息化时代&#xff0c;在这个信息时代&#xff0c;不管是工作还是生活都离不开手机 APP。因为有了手机 APP&#xff0c;我们的生活变得更加便捷、智能。但随着移动 APP开发的火热&#xff0c;很多企业都想要制作一个自己的 APP。然而在众多的 A…

822. 走方格

链接&#xff1a; 链接 题目&#xff1a; 给定一个 nmnm 的方格阵&#xff0c;沿着方格的边线走&#xff0c;从左上角 (0,0)(0,0) 开始&#xff0c;每次只能往右或者往下走一个单位距离&#xff0c;问走到右下角 (n,m)(n,m) 一共有多少种不同的走法。 输入格式 共一行&#xff…

【Docker】简单的Linux安装Redis

目录 Docker 安装 Redis拉取镜像安装容器修改配置文件容器随docker启动自动运行redis客户端 史上最详细Docker安装Redis &#xff08;含每一步的图解&#xff09;实战 Docker 安装 Redis 拉取镜像 docker pull redis安装容器 创建redis配置文件目录&#xff1a;如果内部没有相…

数据结构初阶--顺序表

目录 一.顺序表的定义 二.顺序表的分类 2.1.静态顺序表 2.2.动态顺序表 三.顺序表的特点 四.顺序表的功能实现 4.1.顺序表的定义 4.2.顺序表的初始化 4.3.顺序表的销毁 4.4.顺序表的容量检查 4.5.顺序表的打印 4.6.顺序表的尾插 4.7.顺序表的头插 4.8.顺序表的尾…

用ChatGPT搞定12 种编程语言:看看它如何表现

众所周知ChatGPT可以写代码&#xff0c;但当有一堆语言一起抛向它时&#xff0c;它的表现如何呢&#xff1f;答案是&#xff1a;还不错&#xff08;但并不完美&#xff09;。 在过去的几个月里&#xff0c;我们已经领教了ChatGPT的编码能力。我对它进行了PHP和WordPress的测试…

前端学习——Web API (Day6)

正则表达式 语法 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice…

SPEC CPU 2017 1.0.5 不同版本CentOS 7 8 安装笔记

CentOS 7.9.2009 x86_64 gcc版本 安装成功 runcpu编译报错 gcc版本太低&#xff0c;不识别-fno-tree-loop-vectorize 去掉config/gcc.cfg中 -fno-tree-loop-vectorize编译优化参数。 用例编译中 CentOS 8.3.2011 x86_64 gcc版本 安装失败&#xff0c;需要自行编译tools 手动…

#{} 和 ${} 的区别?

一、区别概述 1.1、主要区别&#xff1a; 1、#{} 是预编译处理&#xff0c;${} 是直接替换&#xff1b;2、${} 存在SQL注入的问题&#xff0c;而 #{} 不存在&#xff1b;Ps&#xff1a;这也是面试主要考察的部分~ 1.2、细节上&#xff1a; 1、${} 可以实现排序查询&#xff…

亚马逊云科技联合Nolibox定制工业设计AIGC解决方案

从机器学习算法到深度学习再到强化学习&#xff0c;AI创新浪潮奔流不息。而AIGC&#xff08;AI-generated Content&#xff0c;人工智能生成内容&#xff09;的到来&#xff0c;更是让AI成为众多企业的得力助手&#xff0c;开拓了文本、图像、音视频等领域的天花板。 在洞悉到…

企业云性能监控是一项关键的任务

企业云性能监控是一项关键的任务&#xff0c;它不仅可以保障企业云服务的稳定性和可靠性&#xff0c;还可以加强企业对云服务的掌控和管理&#xff0c;提供卓越的用户体验。 首先&#xff0c;企业云性能监控可以保障云服务的稳定和可靠。在云计算环境下&#xff0c;企业的核心业…

#1336 .「找树根和孩子」. [树的基本使用](内附封面)

【题目描述】 给定一棵树&#xff0c;输出树的根root&#xff0c;孩子最多的结点max以及他的孩子。 【输入】 第一行&#xff1a;n&#xff08;结点个数≤100&#xff09;&#xff0c;m&#xff08;边数≤200&#xff09;。 以下m行&#xff1a;每行两个结点x和y&#xff0…

Hyperledger Fabric测试网络运行官方Java链码[简约版]

文章目录 启动测试网络使用peer CLI测试链码调用链码 启动测试网络 cd fabric-samples/test-networknetwork.sh的脚本语法是&#xff1a;network.sh <mode> [flag] ./network.sh up./network.sh createChannel在java源码路径下 chmod 744 gradlew vim gradlew :set ffu…

PostgreSQL技术内幕(九)libpq通信协议

libpq通信协议是基于TCP/IP 协议的一套消息通信协议&#xff0c;它允许 psql、JDBC、PgAdmin等客户端程序传递查询给PostgreSQL后端服务器&#xff0c;并接收返回查询的结果。 在这次的直播中&#xff0c;我们为大家介绍了libpq通信协议的实现原理和执行机制&#xff0c;以下内…

发挥AMS、PMS和WMS在框架层服务作用,简化开发过程

Framework底层服务是Android操作系统提供的一组核心服务和功能&#xff0c;用于支持应用程序的开发和运行。这些底层服务提供了许多功能和特性&#xff0c;帮助开发者构建稳定、高效和功能丰富的Android应用程序。 Framework底层服务作用&#xff1a; 管理应用程序的生命周期…

小白到运维工程师自学之路 第五十一集 (三剑客之sed)

一、概述 sed是一个流式文本编辑器&#xff0c;可以对文本进行搜索、替换、删除等操作。它是一个非交 互式的命令行工具&#xff0c;通常用于处理大量的文本数据。sed的工作方式是逐行读取输入文 本&#xff0c;按照预定义的命令对每一行进行处理&#xff0c;并输出结果。它…

[Linux] 最基础简单的线程池 及其 单例模式的实现

本篇文章主要用到线程相关内容, 下面是博主关于线程相关内容的文章: [Linux] 线程同步分析&#xff1a;什么是条件变量&#xff1f;生产者消费者模型是什么&#xff1f;POSIX信号量怎么用&#xff1f;阻塞队列和环形队列模拟生产者消费者模型 [Linux] 线程互斥分析: 多线程的问…

Python接口自动化测试--requests高级进阶

Cookies与会话对象 如果某个响应中包含一些Cookie&#xff0c;你可以快速访问它们&#xff1a; import requests r requests.get(http://www.google.com.hk/) print(r.cookies[NID]) print(tuple(r.cookies)) 要想发送你的cookies到服务器&#xff0c;可以使用 cookies 参…

xShell中使用vim编辑时,无法粘贴外来文本

鼠标右键弹出菜单时&#xff0c;vim直接变成了视图模式了&#xff0c;不能粘贴了。。 好&#xff0c;执行命令 vim ~/.vimrc输入: set mousec即可。 此时便可以粘贴了。