当前位置: 首页 > news >正文

每日算法-250408

记录今天解决的两道 LeetCode 算法题,主要涉及二分查找的应用。


1283. 使结果不超过阈值的最小除数

题目描述

Problem Description for 1283

思路

核心思路是 二分查找

解题过程

  1. 为什么可以使用二分?
    关键在于单调性。对于一个固定的数组 nums,当除数 divisor 增大时,每个元素 num / divisor (向上取整) 的值是 非递增 的,因此它们的总和也是 非递增 的。
    我们要求的是满足 sum <= threshold最小 divisor

    • 如果当前除数 mid 计算出的总和 sum 大于 threshold,说明 mid 太小了,我们需要增大除数。因此,真正的答案一定在 [mid + 1, right] 区间。
    • 如果当前除数 mid 计算出的总和 sum 小于等于 threshold,说明 mid 是一个可能的解,但可能存在更小的除数也满足条件。因此,我们尝试在 [left, mid - 1] 区间寻找更小的解,同时记录 mid 作为潜在答案。
  2. 二分查找的目标?
    我们要查找的是满足条件的 最小 正整数除数。

  3. 查找范围?

    • left:最小可能的除数是 1(题目要求正整数除数)。
    • right:最大可能的除数是多少?如果除数非常大(例如,等于数组中的最大元素),那么每个数除后的结果向上取整至少是 1,总和至少是 nums.length。如果除数是数组中的最大值 max(nums),那么每个 ceil(num / max(nums)) 都是 1,总和为 nums.length。因此,一个合理的上界是数组中的最大元素值。如果阈值 threshold 非常小(比如等于 nums.length),那么除数可能需要等于最大元素值。
  4. check 函数
    需要一个辅助函数 check(nums, divisor) 来计算在当前除数 divisor 下,数组元素的除法结果(向上取整)之和。计算 ceil(x / divisor) 可以用 (x + divisor - 1) / divisor 的整数除法实现,或者像原始代码那样判断余数。

复杂度

  • 时间复杂度: O(N log M)
    • 其中 N 是数组 nums 的长度。
    • M 是二分查找的范围上限,即 nums 中的最大元素值。
    • 每次 check 函数需要 O(N) 的时间遍历数组。
    • 二分查找需要 O(log M) 次 check 调用。
  • 空间复杂度: O(1)
    • 我们只需要常数级别的额外空间来存储 left, right, midsum 等变量。

Code

class Solution {
    public int smallestDivisor(int[] nums, int threshold) {
        Arrays.sort(nums);
        int left = 1, right = nums[nums.length - 1];
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (check(nums, mid) < threshold + 1) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }

    private int check(int[] nums, int divisor) {
        int sum = 0;
        for (int x : nums) {
            if (x % divisor == 0) {
                sum += x / divisor;
            } else {
                sum += x / divisor + 1;
            }
        }
        return sum;
    }
}

1170. 比较字符串最小字母出现频次

题目描述

Problem Description for 1170

思路

结合 预处理二分查找

解题过程

  1. 计算频率 f(s)
    首先,需要实现一个函数 f(s),用于计算给定字符串 s 中字典序最小的字符的出现次数。遍历字符串,找到最小字符,并统计其出现次数。

  2. 预处理 words
    words 数组中的每个字符串 W,计算其 f(W),并将这些频率值存储在一个新的整数数组 wFreq 中。

  3. 排序 wFreq
    为了能够高效地查找满足 f(queries[i]) < f(W)W 的数量,我们将 wFreq 数组进行升序排序。

  4. 二分查找
    对于每个 queries[i]:
    a. 计算 qVal = f(queries[i])
    b. 我们需要在已排序的 wFreq 数组中找到 第一个 大于 qVal 的元素的位置 idx
    c. wFreq 数组中从 idx 到末尾的所有元素都满足 f(W) > qVal。因此,满足条件的 W 的数量就是 wFreq.length - idx
    d. 查找 “第一个大于 qVal 的元素” 可以通过二分查找实现。具体地,我们可以查找 第一个大于等于 qVal + 1 的元素的位置。

复杂度

  • 时间复杂度: O(Lq * N + Lw * M + M log M + N log M)
    • 其中 N 是 queries 的长度,M 是 words 的长度。
    • Lq 和 Lw 分别是 querieswords 中字符串的最大长度。
    • 计算所有 f(queries[i]) 需要 O(Lq * N)。
    • 计算所有 f(W) 需要 O(Lw * M)。
    • wFreq 排序需要 O(M log M)。
    • 对每个 query 进行二分查找需要 O(log M),总共 N 次查找为 O(N log M)。
    • 整体复杂度由这些部分相加决定。如果字符串长度较小,主要由排序和查找决定。
  • 空间复杂度: O(N + M)
    • 需要 O(N) 空间存储 queries 的频率结果(或者直接在结果数组中计算)。
    • 需要 O(M) 空间存储 words 的频率数组 wFreq
    • 排序可能需要 O(log M) 或 O(M) 的额外栈空间或临时空间,但 O(N+M) 通常是主导。

Code

import java.util.Arrays;

class Solution {
    public int[] numSmallerByFrequency(String[] queries, String[] words) {
        int n = queries.length, m = words.length;
        int[] qFreq = new int[n]; // 存储 queries 的 f 值
        int[] wFreq = new int[m]; // 存储 words 的 f 值
        int[] ans = new int[n];   // 存储最终结果

        // 1. 计算 queries 中每个字符串的 f 值
        for (int i = 0; i < n; i++) {
            qFreq[i] = calculateF(queries[i]);
        }

        // 2. 计算 words 中每个字符串的 f 值
        for (int i = 0; i < m; i++) {
            wFreq[i] = calculateF(words[i]);
        }

        // 3. 对 words 的频率数组进行排序
        Arrays.sort(wFreq);

        // 4. 对每个 query 的频率值,在排好序的 wFreq 中进行二分查找
        for (int i = 0; i < n; i++) {
            int targetFreq = qFreq[i];
            // 查找第一个严格大于 targetFreq 的元素的位置
            // 等价于查找第一个大于等于 targetFreq + 1 的元素的位置
            int index = findFirstGreater(wFreq, targetFreq);
            ans[i] = m - index; // 从该位置到数组末尾的元素个数即为所求
        }

        return ans;
    }

    // 计算字符串 s 的 f(s) 值
    private int calculateF(String s) {
        if (s == null || s.isEmpty()) {
            return 0;
        }
        char minChar = 'z' + 1; // 初始化为一个比 'z' 大的字符
        int count = 0;

        for (char c : s.toCharArray()) {
            if (c < minChar) {
                minChar = c;
                count = 1; // 找到了更小的字符,重置计数
            } else if (c == minChar) {
                count++; // 遇到了相同的最小字符,增加计数
            }
        }
        return count;
    }

    // 在升序数组 arr 中查找第一个严格大于 target 的元素的索引
    // 如果所有元素都小于等于 target,返回 arr.length
    private int findFirstGreater(int[] arr, int target) {
        int left = 0, right = arr.length - 1;
        int resultIndex = arr.length; // 默认为数组长度,表示没找到

        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] > target) {
                // arr[mid] 比 target 大,可能是第一个,也可能前面还有
                resultIndex = mid; // 记录当前这个可能的位置
                right = mid - 1;  // 继续向左查找更小的索引
            } else {
                // arr[mid] 小于等于 target,需要向右查找更大的值
                left = mid + 1;
            }
        }
        return resultIndex;
    }
}

相关文章:

  • 使用Java多线程和POI进行Elasticsearch大批量数据导出
  • linux开发环境
  • 物联网外设管理服务平台
  • 吊舱的陀螺稳定系统技术要点!
  • java设计模式-建造者模式
  • 【算法竞赛】树上最长公共路径前缀(蓝桥杯2024真题·团建·超详细解析)
  • 【家政平台开发(27)】商务部信用对接、法律咨询与视频面试功能开发全攻略
  • ADI的BF561双核DSP怎么做开发,我来说一说(六)IDE硬盘设计
  • EasyExcel实现图片导出功能(记录)
  • OpenHarmony-AI调研
  • Proximal Policy Optimization (PPO)2017
  • MySQL详解最新的官方备份方式Clone Plugin
  • 【机器学习】决策树
  • Java的JDK、JRE、JVM关系与作用
  • 【Axure元件分享】移动端滑动拨盘日期选择器
  • WHAT - React 惰性初始化
  • Qwen - 14B 怎么实现本地部署,权重参数大小:21GB
  • 快速上手Vue3国际化 (i18n)
  • DeepSeek和文心一言的区别
  • 搭建hadoop集群模式并运行
  • 新乡建站/长春视频剪辑培训机构
  • 网站上的广告位是怎么做的/百度搜索页面
  • 知名的集团门户网站建设企业/美国疫情最新数据消息
  • 返利导购网站建设需求文档/公司网站设计制作
  • 支付网站费怎么做会计分录/北京seo优化费用
  • 礼品公司网站模板/如何免费注册网站平台