欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 美食 > 每日算法一练:剑指offer——贪心算法与找规律

每日算法一练:剑指offer——贪心算法与找规律

2025/2/1 9:14:27 来源:https://blog.csdn.net/m0_53926113/article/details/144822525  浏览:    关键词:每日算法一练:剑指offer——贪心算法与找规律

1. 买卖芯片的最佳时机

        数组 prices 记录了某芯片近期的交易价格,其中 prices[i] 表示的 i 天该芯片的价格。你只能选择 某一天 买入芯片,并选择在 未来的某一个不同的日子 卖出该芯片。请设计一个算法计算并返回你从这笔交易中能获取的最大利润。

如果你不能获取任何利润,返回 0。

示例 1:

输入:prices = [3, 6, 2, 9, 8, 5]
输出:7
解释:在第 3 天(芯片价格 = 2)买入,在第 4 天(芯片价格 = 9)卖出,最大利润 = 9 - 2 = 7。

示例 2:

输入:prices = [8, 12, 15, 7, 3, 10]
输出:7
解释:在第 5 天(芯片价格 = 3)买入,在第 6 天(芯片价格 = 10)卖出,最大利润 = 10 - 3 = 7。

提示:

  • 0 <= prices.length <= 10^5
  • 0 <= prices[i] <= 10^4

解题思路

        我们的问题是找到一次买入和一次卖出股票的最佳时机,让赚的钱最多,前提是买入必须在卖出之前。

暴力解法

最简单的想法是试试所有可能的买入和卖出时间:

  1. 先假设某天买入。
  2. 再假设之后的某天卖出,计算这次交易的利润。
  3. 比较所有利润,找到最大值。

缺点:如果股票有很多天,比较的次数会很多,时间耗不起。

优化解法:动态规划

我们换个思路,快速找到答案。

  1. 目标: 我们只要知道每一天卖出股票时,最多能赚多少钱,这样一路算下来就能找到最佳答案。

  2. 关键问题: 要卖出股票,得先买入!所以每天的利润是当天价格减去之前几天的最低价格。

  3. 步骤:

    • 用一个变量 cost 记录之前几天的最低价格。
    • 用一个变量 profit 记录目前为止的最大利润。
    • 每天更新这两个变量:
      • 更新最低价格:cost = min(cost, price)
      • 更新最大利润:profit = max(profit, price - cost)

代码实现

class Solution {public int bestTiming(int[] prices) {int cost = Integer.MAX_VALUE; // 记录最低价格int profit = 0; // 记录最大利润for (int price : prices) {cost = Math.min(cost, price); // 更新最低价格profit = Math.max(profit, price - cost); // 计算当前利润}return profit; // 返回最大利润}
}

简单理解

  1. 想象成逛商场:

    • cost 就是“见过的最低价格”。
    • 每天都在看,“今天的价格减去最低价,能赚多少钱?”
    • 把这个“最多赚的钱”记录下来。
  2. 不断更新两个变量:

    • 最低价 cost
    • 最大利润 profit

最后答案就是全程最多能赚的钱。

效率分析

  • 速度快:只需要扫一遍数组,时间是 O(N)。
  • 占空间少:只用了两个变量,空间是 O(1)。

2. 数字1的个数

给定一个整数 num,计算所有小于等于 num 的非负整数中数字 1 出现的个数。

示例 1:

输入:num = 0
输出:0

示例 2:

输入:num = 13
输出:6

提示:

  • 0 <= num < 109

        计算数字 1 出现的次数是一个经典问题,解决思路可以分为暴力解法和数学优化解法。

暴力解法

暴力解法比较直接:我们遍历从 0num 的所有数字,统计每个数字中 1 出现的次数。

步骤

  1. 遍历从 0num 的所有数字。
  2. 将每个数字转为字符串,检查其中 1 出现的次数。
  3. 累加这些次数,最终返回结果。

代码

class Solution {public int countDigitOne(int num) {int count = 0;for (int i = 0; i <= num; i++) {count += countOnesInNumber(i);}return count;}private int countOnesInNumber(int n) {int count = 0;while (n > 0) {if (n % 10 == 1) {count++;}n /= 10;}return count;}
}

复杂度分析

  • 时间复杂度:O(n⋅log⁡10(n))O(n \cdot \log_{10}(n)),其中 nn 是 num,因为每个数字的位数需要单独统计。
  • 空间复杂度:O(1)O(1),没有使用额外的空间。

数学优化解法

暴力法效率较低,我们可以通过数学优化来直接计算每个位置上 1 的贡献。

核心思想

  • 我们逐位统计每个数字位上 1 出现的次数。
  • 对于十进制表示中的某一位,分为 当前位、低位和高位
    • 高位:表示当前位左边的所有数字。
    • 当前位:表示当前正在统计的位。
    • 低位:表示当前位右边的所有数字。

计算公式

计算公式

假设当前位是 dd,位权为 10i10^i,根据高位和低位的不同情况讨论每位上 1 的贡献:

  1. 如果当前位为 0
    当前位不会贡献任何 1,所以计算公式为:

    贡献=高位×10i
  2. 如果当前位为 1
    当前位的 1 的贡献取决于低位的数值,计算公式为:

    贡献=高位×10i+低位+1
  3. 如果当前位大于 1
    当前位上会完整贡献 10i10^i 个 1,计算公式为:

    贡献=(高位+1)×10i

代码实现

class Solution {public int countDigitOne(int num) {int count = 0;long place = 1; // 当前位权,1 表示个位,10 表示十位,依次类推while (place <= num) {long high = num / (place * 10); // 高位long current = (num / place) % 10; // 当前位long low = num % place; // 低位if (current == 0) {count += high * place;} else if (current == 1) {count += high * place + low + 1;} else {count += (high + 1) * place;}place *= 10; // 进入下一位}return count;}
}

复杂度分析

  • 时间复杂度:O(log⁡10(n)),遍历每一位,效率远高于暴力法。
  • 空间复杂度:O(1),仅使用了少量变量。

3. 找到第k位数字

        某班级学号记录系统发生错乱,原整数学号序列 [1,2,3,4,...] 分隔符丢失后变为 1234... 的字符序列。请实现一个函数返回该字符序列中的第 k 位数字。

示例 1:

输入:k = 5
输出:5
示例 2:

输入:k = 12
输出:1
解释:第 12 位数字在序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ... 里是 1 ,它是 11 的一部分。

提示:

0 <= k < 231

解题思路:

我们要找的是数字序列(如 123456789101112...)中的第 k 位。可以分三步来确定:

1. 确定第 k 位属于几位数

  • 首先,一位数有 9 个,总共占 9×1=9 位;
  • 两位数有 90 个,总共占 90×2=180 位;
  • 三位数有 900 个,总共占 900×3=2700 位;
  • 以此类推,直到找到 k 所属的范围。

具体做法是:

  • digit=1 开始(表示 1 位数),依次减去每一段的位数数量 count=9×start×digit
  • k ≤ count 时,跳出循环,k 就落在当前的 digit 位数中。

2. 确定 k 属于哪个数字

  • 在找到的位数中,起始数字为 start。例如:
    • 1 位数从 1 开始,start=1
    • 2 位数从 10 开始,start=10
    • 3 位数从 100 开始,start=100
  • k 位在从 start 开始的第 [(k-1)/digit] 个数字中(这里减 1 是因为第一个数字是第 0 个)。

3. 确定数字的哪一位

  • 找到具体数字后,将其转成字符串,(k-1) % digit 就是 k 在这个数字中的具体位置。

代码

Java 代码

class Solution {public int findKthNumber(int k) {int digit = 1; // 当前是几位数long start = 1; // 当前位数的起始数字long count = 9; // 当前位数的总数位数量// 1. 确定 k 属于几位数while (k > count) {k -= count;digit += 1;start *= 10;count = digit * start * 9; // 例如:两位数有 90×2=180 位}// 2. 确定 k 属于哪个数字long num = start + (k - 1) / digit;// 3. 确定 k 是数字的第几位String s = Long.toString(num); // 转为字符串return s.charAt((k - 1) % digit) - '0'; // 找到具体数位并返回}
}

复杂度分析

  1. 时间复杂度

    • 第一部分:找到 digit 所需最多执行 O(logk) 次循环;
    • 第三部分:将数字转为字符串,耗时为 O(logk)
    • 所以总时间复杂度为 O(logk)
  2. 空间复杂度

    • 转换为字符串的过程需要 O(logk) 的额外空间。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com