【LeetCode热题100】打卡第34天:排序链表乘积最大的子数组

文章目录

  • 【LeetCode热题100】打卡第34天:排序链表&乘积最大的子数组
    • ⛅前言
  • 排序链表
    • 🔒题目
    • 🔑题解
  • 乘积最大的子数组
    • 🔒题目
    • 🔑题解

【LeetCode热题100】打卡第34天:排序链表&乘积最大的子数组

⛅前言

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

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

排序链表

🔒题目

原题链接:148.排序链表

image-20230710091853625

🔑题解

  • 解法一:暴力枚举(超时,30个示例,过了29个,一个超时)

    public class Solution {// Node.val的最小值private int MIN_VALUE = -100000;public ListNode sortList(ListNode head) {ListNode newHead = new ListNode(MIN_VALUE);while (head != null) {ListNode i = newHead;// 从 newHead 中定位比 head 大的前一个节点while (i.next != null && i.next.val < head.val) {i = i.next;}ListNode tempNode = new ListNode(head.val);if (i.next != null){// 非尾节点tempNode.next = i.next;}i.next = tempNode;head = head.next;}return newHead.next;}
    }
    

    复杂度分析:

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

    其中 n n n 为链表中元素的个数

  • 解法二:归并排序

    这一题,要求时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度为 O ( 1 ) O(1) O(1)基于这两点,很容易就联想到分治思想,而又结合排序就很容易想到归并排序,大家对于归并排序一定都熟记于心了吧(●ˇ∀ˇ●),虽然很容易想到,但是换成链表我就发现实现起来是比较困难的,这里先复习一下数组版的归并排序吧(采用自底向上的归并排序)

    public class Test {public static void main(String[] args) {int[] arr = {4, 2, 1, 1, 5, -1, 1, 7, 5};// 归并排序mergeSort(arr, 0, arr.length - 1);// 输出结果System.out.println(Arrays.toString(arr));}private static void mergeSort(int[] arr, int l, int r) {if (l == r) {// 区间中只有一个元素,无需划分return;}// 计算区间中间索引(向下取整,往左逼近,mid在左)int mid = (r - l) / 2 + l;// 划分左侧子数组mergeSort(arr, l, mid);// 划分右侧子数组mergeSort(arr, mid + 1, r);// 合并区间merge(arr, l, mid, r);}private static void merge(int[] arr, int l, int mid, int r) {int[] temp = new int[arr.length];int i = l;int j = mid + 1;int k = 0;// 比较左右子树组中的元素,将较小值放入temp中(降序排序)while (i <= mid && j <= r) {if (arr[i] < arr[j]) {temp[k++] = arr[i++];} else {temp[k++] = arr[j++];}}// 左侧子数组还有剩余while (i <= mid) {temp[k++] = arr[i++];}// 右侧子数组还有剩余while (j <= r) {temp[k++] = arr[j++];}// 将 temp 拷贝到 arr 中for (int t = 0; t < k; t++) {arr[l+t] = temp[t];}}
    }
    

    以下代码参考自 K神

    链表实现归并排序的难点在于如何确定中间节点?这里采用一个比较巧妙的技巧,那就是使用快慢指针,在前面(LeetCode热题100打卡33天)我们判断链表是否有换,也是用到了快慢指针,这里同样的可以利用它来确定中间节点

    大致思路:快指针(fast)比慢指针(slow)多走一步,这样fast到达了链表尾部,slow就处在了中间节点的位置,这里需要注意的是已经有左侧边界了,还缺一个右侧边界,如果直接以slow为右侧边界,会导致有节点遗漏,这个在二分查找中就已经讨论过了,这里不再赘述,所以我们需要以 slow.next 为右侧边界,此外在我们选中了 slow.next 为右侧边界,我们还需要将 slow.next置为null,这样能够比较好的用来判断左侧链表是否遍历到头,否则还需要使用一个多余的变量来记录左侧边界值

    image-20230710130741377

    class Solution {public ListNode sortList(ListNode head) {return mergeSort(head);}private ListNode mergeSort(ListNode node) {if (node == null || node.next == null) {return node;}// 定位中间节点(向上取整,往右逼近,mid在右)ListNode fast = node.next;ListNode slow = node;while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;}ListNode mid = slow.next;slow.next = null;// 划分左侧子区间ListNode left = mergeSort(node);// 划分右侧子区间ListNode right = mergeSort(mid);// 合并区间return merge(left, right);}private ListNode merge(ListNode left, ListNode right) {ListNode res = new ListNode();// 比较左右子区间中的元素,将较小值放入temp中(降序排序)ListNode temp = res;while (left != null && right != null) {if (left.val < right.val) {temp.next = left;left = left.next;} else {temp.next = right;right = right.next;}temp = temp.next;}// 将剩余的一方添加到 temp 的尾部temp.next = left != null ? left : right;return res.next;}
    }
    

    复杂度分析:

    • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
    • 空间复杂度: O ( n ) O(n) O(n)

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

    这种方法虽然能过,但是空间复杂度是 O ( n ) O(n) O(n),但这不符合进阶要求,进阶的要求是空间复杂度 O ( 1 ) O(1) O(1),所以递归实现归并是无法实现的,我们就需要使用迭代来实现归并排序:

    略……https://leetcode.cn/problems/sort-list/solution/javadi-gui-die-dai-shuang-zhong-jie-fa-luo-ji-qing/
    

    PS:迭代归并写起来好麻烦,不要折磨自己了,在我提交别人的代码测试后发现迭代归并虽然迭代归并空间复杂度为常数,但是内存消耗居然比递归归并还要多,耗时也要多,感兴趣的可以自行去LeetCode看别人迭代版的归并排序

  • 解法三:快速排序

    同理,先来复习以下数组快排是如何实现的吧😄

    public class Test {public static void main(String[] args) {int[] arr = {4, 2, 1, 1, 5, -1, 1, 7, 5};quickSort(arr, 0, arr.length - 1);System.out.println(Arrays.toString(arr));}private static void quickSort(int[] arr, int l, int r) {if (l >= r) {// 区间中只有一个元素时或无元素时,无需继续划分区间return;}// 划分区间,并获取主元索引int pivot = partition(arr, l, r);// 划分左侧子区间quickSort(arr, l, pivot - 1);// 划分右侧子区间quickSort(arr, pivot + 1, r);}private static int partition(int[] arr, int l, int r) {// 选取主元(以区间末尾元素为主元)int pivot = arr[r];// 左侧区间右边界int i = l - 1;// 划分区间(左侧区间<主元,右侧区间>=主元)for (int j = l; j < r; j++) {// 将比主元小的元素放到 i+1 的左侧if (arr[j] < pivot) {swap(arr, ++i, j);}}// 将主元放到分界点,然后返回主元索引swap(arr, i + 1, r);return i + 1;}private static void swap(int[] arr, int i, int j) {int temp = arr[j];arr[j] = arr[i];arr[i] = temp;}
    }
    
    class Solution {public static ListNode sortList(ListNode head) {return quickSort(head, null);}public static ListNode quickSort(ListNode head, ListNode end) {if (head == end || head.next == end) {// 区间中只有一个元素时或无元素时,无需继续划分区间return head;}// 划分区间,并获取主元节点ListNode pivot = partition(head, end);// 划分左侧子区间ListNode node = quickSort(pivot, head);// 划分右侧子区间head.next = quickSort(head.next, end);return node;}private static ListNode partition(ListNode head, ListNode end) {// 选头节点的值作为主元int pivot = head.val;// 左区间右边界ListNode i = head;// 右区间右边界ListNode j = head;// 遍历整个区间,并进行划分(左侧区间<主元,右侧区间>=主元)ListNode p = head.next; // 从第二个节点开始遍历while (p != end) {// 临时存储 p.next,防止链表断裂ListNode tempNode = p.next;// 将所有比主元小的元素都放到 i 的左侧,所有比主元大的元素都放到 j 的右侧if (p.val < pivot) {// 头插p.next = i;i = p;} else {// 尾插j.next = p;j = p;}p = tempNode;}j.next = end;return i;}
    }
    

    复杂度分析:

    • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
    • 空间复杂度: O ( 1 ) O(1) O(1)

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

乘积最大的子数组

🔒题目

原题链接:152.乘积最大的子数组

image-20230710092107109

🔑题解

  • 解法一:暴力枚举(能过)

    class Solution {public int maxProduct(int[] nums) {int maxProduct = Integer.MIN_VALUE;for (int i = 0; i < nums.length; i++) {int product = 1;for (int j = i; j < nums.length; j++) {product *= nums[j];maxProduct = Math.max(maxProduct, product);}}return maxProduct;}
    }
    

    复杂度分析:

    • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
    • 空间复杂度: O ( 1 ) O(1) O(1)
  • 解法二:动态规划

    解法一使用暴力枚举,每次都需要重新去计算左侧最大值,计算左侧最大值的过程其实是可以使用一个数组存储的,这样就不需要每次都去计算一遍左侧最大值,但是这样也带来了一个问题,那就是当前最大值,不一定是由左侧最大值更新过来的,有可能是由左侧最小值更新过来的,所以我们还需要使用一个数组去记录左侧最小值,总结起来,无非以下几种情况:

    max[i]:从第 0 个元素到 第 i 个元素(必须包含第i个元素),能够形成最大的连续乘积

    min[i]:从第 0 个元素到 第 i 个元素(必须包含第i个元素),能够形成最小的连续乘积

    1. 当前最大值由左侧最大值更新得到,max[i]=Math.max(nums[i],nums[i]*max[i-1])

    2. 当前最大值由左侧最小值更新得到,max[i]=Math.max(max[i],nums[i]*max[i-1])

      综合起来,就可以得到:max[i] = Math.max(max[i - 1] * nums[i], Math.max(nums[i], min[i - 1] * nums[i]))

      同理,我们可以得到:min[i] = Math.min(min[i - 1] * nums[i], Math.min(nums[i], max[i - 1] * nums[i]))

    最后我们只需要通过Math.max(ans, max[i])更新最大连续乘积即可

    class Solution {public int maxProduct(int[] nums) {int len = nums.length;int[] max = new int[len];max[0] = nums[0];int[] min = new int[len];min[0] = nums[0];// 更新计算出max数组和min数组for (int i = 1; i < len; ++i) {max[i] = Math.max(max[i - 1] * nums[i], Math.max(nums[i], min[i - 1] * nums[i]));min[i] = Math.min(min[i - 1] * nums[i], Math.min(nums[i], max[i - 1] * nums[i]));}// 更新计算出最大连续乘积int maxProduct = max[0];for (int i = 1; i < len; ++i) {maxProduct = Math.max(maxProduct, max[i]);}return maxProduct;}
    }
    

    我们可以发现在更新max和min数组时,其实也是可以顺便更新ans的,所以可以对上面代码进行简化

    class Solution {public int maxProduct(int[] nums) {int len = nums.length;int[] max = new int[len];max[0] = nums[0];int[] min = new int[len];min[0] = nums[0];int maxProduct = max[0];// 更新计算出max数组和min数组,同时更新ansfor (int i = 1; i < len; ++i) {max[i] = Math.max(max[i - 1] * nums[i], Math.max(nums[i], min[i - 1] * nums[i]));min[i] = Math.min(min[i - 1] * nums[i], Math.min(nums[i], max[i - 1] * nums[i]));maxProduct = Math.max(maxProduct, max[i]);}return maxProduct;}
    }
    

    复杂度分析:

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

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

    代码优化:空间优化

    我们发现每次更新,其实max和min后面的元素都没有用到,我们只用到了上一次的状态,这就我们的状态方程只有上一个状态有关,这就说明我们可以仅使用有关变量去记录上一个状态

    class Solution {public int maxProduct(int[] nums) {int len = nums.length;int max = nums[0];int min = nums[0];int maxProduct = nums[0];for (int i = 1; i < len; ++i) {int preMax = max;int preMin = min;max = Math.max(preMax * nums[i], Math.max(nums[i], preMin * nums[i]));min = Math.min(preMin * nums[i], Math.min(nums[i], preMax * nums[i]));maxProduct = Math.max(maxProduct, max);}return maxProduct;}
    }
    

    复杂度分析:

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

COT、COT-SC、TOT 大预言模型思考方式||底层逻辑:prompt设定

先讲一下具体缩写的意思 COT-chain of thoughts COT-SC (Self-consistency) Tree of thoughts:Deliberate problem solving with LLM 我理解其实不复杂 1. 最简单的是&#xff1a;直接大白话问一次 &#xff08;IO&#xff09; 2. 进阶一点是&#xff1a;思维链&#xff0c;…

DolphinScheduler使用问题记录

1.资源中心 功能板块 出现 storage not startup #问题原因 提示&#xff1a;“storage not startup”&#xff0c;顾名思义&#xff1a;未启用存储 #解决方式 1. 修改两个 common.properties 文件&#xff1a; api-server/conf/common.properties worker-server/conf/common.p…

Azure Kinect DK 在设备管理器找不到此设备

参考 Azure Kinect DK 在设备管理器找不到此设备_Thomas_yx的博客-CSDN博客 type-c------------------type-c 接电脑&#xff0c;数据传输 圆------------------usb 电脑线

【算法基础】搜索与图论

DFS 全排列问题 842. 排列数字 - AcWing题库 #include<bits/stdc.h> using namespace std; const int N10; int n; int path[N]; bool st[N]; void dfs(int x) {if(x>n){for(int i1;i<n;i) cout<<path[i]<<" ";cout<<endl;return ;…

Linux(centos 7)将 ens33 改为 eth0

背景&#xff1a; 先说明一下 eth0 与 ens33 的关系&#xff0c;目前的主流网卡为使用以太网络协定所开发出来的以太网卡&#xff08;Ethernet)&#xff0c;因此我们 Linux 就称呼这种网络接口为 ethN (N为数字)。 举个例子&#xff1a;就是说主机上面有一张以太网卡&#xff0…

element-ui 使用 el-descriptions

<el-descriptions :column"2" border size"mini" style"margin-top: 10px;" :labelStyle"{width: 123px}" :contentStyle"{width:42%}"><el-descriptions-item label"选择项目"><el-select size&…

Linux Ubuntu安装RabbitMQ服务

文章目录 前言1.安装erlang 语言2.安装rabbitMQ3. 内网穿透3.1 安装cpolar内网穿透(支持一键自动安装脚本)3.2 创建HTTP隧道 4. 公网远程连接5.固定公网TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址 前言 RabbitMQ是一个在 AMQP(高级消息队列协议)基…

Scratch 多场景收集物品

Scratch 多场景收集物品 本程序开始运行时4种物品各复制10次并移动到随机位置&#xff0c;交通工具角色跟随鼠标&#xff0c;碰到上述4种物品后删除物品&#xff0c;物品清空后切换到下一个背景、更换交通工具角色并重新生成4种物品。交通工具角色的切换通过判断背景变量的值来…

ChatGLM使用记录

ChatGLM ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数。结合模型量化技术&#xff0c;用户可以在消费级的显卡上进行本地部署&#xff08;INT4 量化级别下最低只需 6GB 显存&#xff0…

DNS详细解析

文章目录 DNS是什么以及作用下载DNS服务named.conf DNS查询DNS缓存机制解析过程递归查询和迭代查询 DNS服务器的类型DNS域名DNS服务器的类型搭建dns服务器缓存域名服务器主域名服务器从域名服务器排错反向解析 CDN介绍 DNS转发介绍配置 DNS劫持 DNS 是什么以及作用 DNS&#…

收款单签字时,报”结算信息表体中本方银行账户、现金账户、票据号 (商业汇票号)不能同时为空,签字操作失败“,能否取消这个校验??

大概整理&#xff0c;如有不当&#xff0c;欢迎留言指出&#xff0c;谢谢&#xff01; 收款单签字时&#xff0c;报”结算信息表体中本方银行账户、现金账户、票据号 (商业汇票号)不能同时为空&#xff0c;签字操作失败“&#xff0c;能否取消这个校验&#xff1f;&#xff1f…

磁盘擦写次数计算

1.让机器能有外网 2,安装工具 sudo apt-get install smartmontools 3,输入查询命令 sudo smartctl -x /dev/sda |egrep Device Model|User Capacity|Sector Size|173|Logical Sectors Written|Percentage Used Endurance Indicator 4,计算擦写次数 计算方法&#xff1a;25…