CSDN每日一题学习训练——Java版(数据流的中位数、乘积最大子数组、旋转链表)

版本说明

当前版本号[20231113]。

版本修改说明
20231113初版

目录

数据流的中位数

题目

中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。

例如,

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。

示例:

addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2

进阶:

如果数据流中所有整数都在 0 到 100 范围内,你将如何优化你的算法?
如果数据流中 99% 的整数都在 0 到 100 范围内,你将如何优化你的算法?

解题思路

  1. 使用两个优先队列(最小堆和最大堆)来存储数据。
  2. 初始化时,将最大堆作为较小的一半元素,最小堆作为较大的一半元素。
  3. 添加一个数字时,将其添加到最大堆中,然后将最大堆的顶部元素移动到最小堆中。这样可以保证最小堆的大小始终大于或等于最大堆的大小。
  4. 如果最小堆的大小大于最大堆的大小,将最小堆的顶部元素移动回最大堆中。这样可以保证两个堆的大小之差不超过1。
  5. 查找中位数时,如果两个堆的大小相等,则中位数为两个堆顶元素的平均值;否则,中位数为最大堆的顶部元素。

代码思路

  1. 在初始化时,创建了两个优先队列:minmaxmin是一个最小堆,用于存储较小的一半元素;max是一个最大堆,用于存储较大的一半元素。

       min = new PriorityQueue<>(); // 较小的一半元素队列
       max = new PriorityQueue<>((a, b) -> { // 较大的一半元素队列,按照降序排列
    
  2. addNum(int num)方法用于向数据结构中添加一个新的数字。首先,将数字添加到max堆中。然后,从max堆中移除顶部的元素,并将其添加到min堆中。这样可以确保min堆的大小始终大于或等于max堆的大小。如果min堆的大小比max堆的大小大,则将min堆中的顶部元素移除并添加到max堆中,以保持平衡。

     /**
         * 向数据结构中添加一个数字
         * @param num 要添加的数字
         */
        public void addNum(int num) {
            max.add(num); // 将数字添加到较大的一半元素队列中
            min.add(max.remove()); // 从较大的一半元素队列中取出最大值,并添加到较小的一半元素队列中
            // 如果较小的一半元素队列的大小大于较大的一半元素队列的大小,则将较小的一半元素队列中的最小值移动到较大的一半元素队列中
            if (min.size() > max.size()) {
                max.add(min.remove());
            }
        }
    
  3. findMedian()方法用于查找当前数据结构的中位数。如果max堆和min堆的大小相等,则中位数是两个堆顶元素的平均值;否则,中位数是max堆的顶部元素。

    /**
     * 查找当前数据结构的中位数
     * @return 中位数
     */
    public double findMedian() {
        // 如果两个队列的大小相等,则中位数为两个队列顶部元素的平均值
        if (max.size() == min.size()) {
            return (max.peek() + min.peek()) / 2.0;
        } else {
            // 否则,中位数为较大的一半元素队列的顶部元素
            return max.peek();
        }
    }

参考代码

这段代码是用于实现一个数据结构来查找中位数。该类使用两个优先队列**(最小堆和最大堆)**来实现这个功能。

class MedianFinder {
    PriorityQueue<Integer> min;
    PriorityQueue<Integer> max;
    /** initialize your data structure here. */
    public MedianFinder() {
        min = new PriorityQueue<>();
        max = new PriorityQueue<>((a, b) -> {
            return b - a;
        });
    }
    public void addNum(int num) {
        max.add(num);
        min.add(max.remove());
        if (min.size() > max.size())
            max.add(min.remove());
    }
    public double findMedian() {
        if (max.size() == min.size())
            return (max.peek() + min.peek()) / 2.0;
        else
            return max.peek();
    }
}
/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

乘积最大子数组

题目

给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

示例 1:

输入: [2,3,-2,4]
输出:
6

解释: 子数组 [2,3] 有最大乘积 6。

示例 2:

输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

解题思路

解题思路如下:

  1. 初始化三个变量,max表示当前最大乘积,imax表示以当前元素结尾的最大乘积,imin表示以当前元素结尾的最小乘积。初始值都设为1,因为数组中至少有一个数字。
  2. 遍历数组中的每个元素,对于每个元素nums[i]:
    • 如果nums[i]小于0,说明负数乘以最大值会变成最小值,负数乘以最小值会变成最大值。所以交换imax和imin的值。
    • 更新imax为当前元素与imax的乘积和当前元素中的较大值。这样可以保证imax始终是当前子数组的最大乘积。
    • 更新imin为当前元素与imin的乘积和当前元素中的较小值。这样可以保证imin始终是当前子数组的最小乘积。
    • 更新max为当前最大乘积和imax中的较大值。这样可以保证max始终是整个数组的最大乘积。
  3. 遍历结束后,返回max作为结果。

代码思路

  1. 定义公共方法maxProduct,该方法接收一个整数数组nums作为参数,返回一个整数值。

  2. 在maxProduct方法中,初始化三个变量max、imax和imin,分别表示最大值、当前最大值和当前最小值。其中,max的初始值为Integer.MIN_VALUE,imax和imin的初始值为1。

    public int maxProduct(int[] nums) {
            int max = Integer.MIN_VALUE, imax = 1, imin = 1; // 初始化最大值为最小整数,imax和imin为1
    
  3. 使用for循环遍历整数数组nums,对于每个元素nums[i],执行以下操作:

    for (int i = 0; i < nums.length; i++)
    
    • 如果nums[i]小于0,说明当前元素为负数,需要交换imax和imin的值。

       if (nums[i] < 0) { // 如果当前元素为负数
                      int tmp = imax; // 交换imax和imin的值
                      imax = imin;
                      imin = tmp;
                  }
      
    • 更新imax为当前元素与imax的乘积和当前元素中的较大值。

           imax = Math.max(imax * nums[i], nums[i]); // 更新imax为当前元素与imax的乘积和当前元素中的较大值
      
    • 更新imin为当前元素与imin的乘积和当前元素中的较小值。

                  imin = Math.min(imin * nums[i], nums[i]); // 更新imin为当前元素与imin的乘积和当前元素中的较小值
      
    • 更新max为当前最大值和imax中的较大值。

       max = Math.max(max, imax); // 更新最大值为当前最大值和imax中的较大值
      
  4. 循环结束后,返回最大值max。

return max; // 返回最大值

参考代码

这段代码是用于求解一个整数数组中连续子数组的最大乘积

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

旋转链表

题目

给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。

示例 1:

image-20231113221027303

输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]

示例 2:

image-20231113221152191

输入:head = [0,1,2], k = 4
输出:[2,0,1]

提示:

链表中节点的数目在范围 [0, 500] 内
-100 <= Node.val <= 100
0 <= k <= 2 * 109

解题思路

  1. 首先判断链表是否为空或者k是否为0,如果满足任一条件,则直接返回原链表。
  2. 初始化两个指针cursor和tail,分别指向链表的头节点和尾节点。同时记录链表的长度length。
  3. 遍历链表,找到尾节点tail,并让cursor指向头节点。
  4. 计算需要旋转的次数loop,即length减去k对length取模的结果。
  5. 将尾节点tail的next指针指向头节点head,完成链表的连接。
  6. 再次遍历链表,让cursor和tail分别向后移动loop次,直到cursor到达新的头节点。
  7. 将尾节点tail的next指针设置为null,断开循环连接。
  8. 返回新的头节点cursor。

代码思路

  1. 定义链表节点类ListNode,包含两个成员变量:val表示节点值,next表示指向下一个节点的指针。同时提供一个构造函数,用于初始化节点值。

    // 定义链表节点类
    public class ListNode {
        int val; // 节点值
        ListNode next; // 指向下一个节点的指针
        ListNode(int x) { // 构造函数,初始化节点值
            val = x;
        }
    }
    
  2. 定义解决方案类Solution,包含一个方法rotateRight,用于实现链表的右旋转操作。该方法接收两个参数:head表示链表的头节点,k表示需要旋转的次数。

    public ListNode rotateRight(ListNode head, int k)
    
  3. 在rotateRight方法中,首先判断链表是否为空或k是否为0,如果满足任一条件,则直接返回原链表。

      // 如果链表为空或k为0,直接返回原链表
            if (head == null || k == 0) {
                return head;
            }
    
  4. 定义游标cursor和尾节点tail,并初始化为null。

         // 定义游标和尾节点
            ListNode cursor = head;
            ListNode tail = null;
    
  5. 使用while循环遍历链表,计算链表的长度length。

     while (cursor.next != null) {
                cursor = cursor.next;
                length++;
            }
    
  6. 计算需要旋转的次数loop,即length减去k对length取模的结果。

      // 计算需要旋转的次数
            int loop = length - (k % length);
    
  7. 找到尾节点tail,并将其next指针指向头节点head,完成链表的连接。

     // 找到尾节点并连接头节点
            tail = cursor;
            cursor.next = head;
    
  8. 移动游标cursor到旋转位置,即头节点head。

     // 移动游标到旋转位置
            cursor = head;
    
  9. 使用for循环移动游标cursor和尾节点tail,直到游标cursor到达旋转位置。

      for (int i = 0; i < loop; i++) {
                cursor = cursor.next;
                tail = tail.next;
            }
    
  10. 断开循环连接,即将尾节点tail的next指针设置为null。

     // 断开循环连接,返回新的头节点
            tail.next = null;
    
  11. 返回新的头节点cursor。

       return cursor;
    

参考代码

这段代码是用于实现链表的右旋转操作

public class ListNode {
    int val;
    ListNode next;
    ListNode(int x) {
        val = x;
    }
}
class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if (head == null || k == 0) {
            return head;
        }
        ListNode cursor = head;
        ListNode tail = null;
        int length = 1;
        while (cursor.next != null) {
            cursor = cursor.next;
            length++;
        }
        int loop = length - (k % length);
        tail = cursor;
        cursor.next = head;
        cursor = head;
        for (int i = 0; i < loop; i++) {
            cursor = cursor.next;
            tail = tail.next;
        }
        tail.next = null;
        return cursor;
    }
}
  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

放下华子我只抽RuiKe5

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值