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^50 <= prices[i] <= 10^4
解题思路
我们的问题是找到一次买入和一次卖出股票的最佳时机,让赚的钱最多,前提是买入必须在卖出之前。
暴力解法
最简单的想法是试试所有可能的买入和卖出时间:
- 先假设某天买入。
 - 再假设之后的某天卖出,计算这次交易的利润。
 - 比较所有利润,找到最大值。
 
缺点:如果股票有很多天,比较的次数会很多,时间耗不起。
优化解法:动态规划
我们换个思路,快速找到答案。
- 
目标: 我们只要知道每一天卖出股票时,最多能赚多少钱,这样一路算下来就能找到最佳答案。
 - 
关键问题: 要卖出股票,得先买入!所以每天的利润是当天价格减去之前几天的最低价格。
 - 
步骤:
- 用一个变量 
cost记录之前几天的最低价格。 - 用一个变量 
profit记录目前为止的最大利润。 - 每天更新这两个变量:
- 更新最低价格:
cost = min(cost, price) - 更新最大利润:
profit = max(profit, price - cost) 
 - 更新最低价格:
 
 - 用一个变量 
 
代码实现
            
            
              java
              
              
            
          
          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; // 返回最大利润
    }
}
        简单理解
- 
想象成逛商场:
cost就是"见过的最低价格"。- 每天都在看,"今天的价格减去最低价,能赚多少钱?"
 - 把这个"最多赚的钱"记录下来。
 
 - 
不断更新两个变量:
- 最低价 
cost。 - 最大利润 
profit。 
 - 最低价 
 
最后答案就是全程最多能赚的钱。
效率分析
- 速度快:只需要扫一遍数组,时间是 O(N)。
 - 占空间少:只用了两个变量,空间是 O(1)。
 
2. 数字1的个数
给定一个整数 num,计算所有小于等于 num 的非负整数中数字 1 出现的个数。
示例 1:
输入:num = 0
输出:0
        示例 2:
输入:num = 13
输出:6
        提示:
0 <= num < 109
计算数字 1 出现的次数是一个经典问题,解决思路可以分为暴力解法和数学优化解法。
暴力解法
暴力解法比较直接:我们遍历从 0 到 num 的所有数字,统计每个数字中 1 出现的次数。
步骤
- 遍历从 
0到num的所有数字。 - 将每个数字转为字符串,检查其中 
1出现的次数。 - 累加这些次数,最终返回结果。
 
代码
            
            
              java
              
              
            
          
          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⋅log10(n))O(n \cdot \log_{10}(n)),其中 nn 是 
num,因为每个数字的位数需要单独统计。 - 空间复杂度:O(1)O(1),没有使用额外的空间。
 
数学优化解法
暴力法效率较低,我们可以通过数学优化来直接计算每个位置上 1 的贡献。
核心思想
- 我们逐位统计每个数字位上 
1出现的次数。 - 对于十进制表示中的某一位,分为 当前位、低位和高位 :
- 高位:表示当前位左边的所有数字。
 - 当前位:表示当前正在统计的位。
 - 低位:表示当前位右边的所有数字。
 
 
计算公式
计算公式
假设当前位是 dd,位权为 10i10^i,根据高位和低位的不同情况讨论每位上 1 的贡献:
- 
如果当前位为
0当前位不会贡献任何
1,所以计算公式为:贡献=高位×10i
 - 
如果当前位为
1当前位的
1的贡献取决于低位的数值,计算公式为:贡献=高位×10i+低位+1
 - 
如果当前位大于
1当前位上会完整贡献 10i10^i 个
1,计算公式为:贡献=(高位+1)×10i
 
代码实现
            
            
              java
              
              
            
          
          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(log10(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。 
 - 1 位数从 
 - 第 
k位在从start开始的第[(k-1)/digit]个数字中(这里减1是因为第一个数字是第 0 个)。 
3. 确定数字的哪一位
- 找到具体数字后,将其转成字符串,
(k-1) % digit就是k在这个数字中的具体位置。 
代码
Java 代码
            
            
              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'; // 找到具体数位并返回
    }
}
        复杂度分析
- 
时间复杂度:
- 第一部分:找到 
digit所需最多执行O(logk)次循环; - 第三部分:将数字转为字符串,耗时为 
O(logk); - 所以总时间复杂度为 
O(logk)。 
 - 第一部分:找到 
 - 
空间复杂度:
- 转换为字符串的过程需要 
O(logk)的额外空间。 
 - 转换为字符串的过程需要