LeetCode100天Day14-轮转数组与买卖股票最佳时机

LeetCode100天Day14-轮转数组与买卖股票最佳时机:数组旋转与暴力搜索

摘要:本文详细解析了LeetCode中两道经典数组题目------"轮转数组"和"买卖股票的最佳时机"。通过数组克隆和索引映射实现轮转,以及使用暴力搜索寻找最大利润,帮助读者掌握数组操作和最优化问题的基本技巧。

目录

  • TOC

1. 轮转数组(Rotate Array)

1.1 题目描述

给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

示例 1

复制代码
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转1步: [7,1,2,3,4,5,6]
向右轮转2步: [6,7,1,2,3,4,5]
向右轮转3步: [5,6,7,1,2,3,4]

示例 2

复制代码
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右轮转1步: [99,-1,-100,3]
向右轮转2步: [3,99,-1,-100]

1.2 解题思路

这道题使用数组克隆和索引映射的方法:

  1. 克隆原数组作为备份
  2. 计算每个位置的新索引
  3. 将原数组的元素按照新索引放入目标位置

解题步骤

  1. 克隆nums到nums1
  2. 遍历数组,计算每个元素的新位置
  3. 使用公式:(原索引 + k) % 数组长度 得到旋转后的索引
  4. 或者反向:原索引 = (新索引 - k) % 数组长度

1.3 代码实现

java 复制代码
class Solution {
    public void rotate(int[] nums, int k) {
        int[] nums1 = nums.clone();
        for(int i = 0;i < nums.length;i++){
            nums[i] = nums1[( nums.length + i - k%nums.length) %nums.length ];
        }
    }
}

1.4 代码逐行解释

第一部分:克隆数组
java 复制代码
int[] nums1 = nums.clone();

功能:创建原数组的副本

操作 说明
nums.clone() 浅克隆数组,复制所有元素
nums1 原数组的备份,保留原始数据

为什么要克隆

复制代码
如果不克隆,直接修改nums:
nums = [1, 2, 3, 4, 5]
k = 2

i=0: nums[0] = nums[?]
     但nums[?]可能已经被修改了!

克隆后:
nums1 = [1, 2, 3, 4, 5]  (备份,不变)
nums = [?, ?, ?, ?, ?]  (目标,从nums1读取)
第二部分:计算新索引
java 复制代码
for(int i = 0;i < nums.length;i++){
    nums[i] = nums1[( nums.length + i - k%nums.length) %nums.length ];
}

索引映射公式

java 复制代码
原索引 → 新索引
原索引j → 新索引i = (j + k) % n

反向:新索引i → 原索引j
j = (i - k) % n
j = (i - k + n) % n  // 处理负数
j = (n + i - k%n) % n  // 最终公式

公式详解

部分 说明
k % nums.length k可能大于数组长度,需要取模
i - k % nums.length 计算原索引(可能为负)
nums.length + i - k % nums.length 加上数组长度,确保非负
% nums.length 最终取模,得到有效索引

1.5 执行流程详解

示例1nums = [1,2,3,4,5,6,7], k = 3

复制代码
初始状态:
nums = [1, 2, 3, 4, 5, 6, 7]
nums1 = [1, 2, 3, 4, 5, 6, 7]
n = 7
k % n = 3 % 7 = 3

i=0:
  原索引 = (7 + 0 - 3) % 7 = 4 % 7 = 4
  nums[0] = nums1[4] = 5
  nums = [5, 2, 3, 4, 5, 6, 7]

i=1:
  原索引 = (7 + 1 - 3) % 7 = 5 % 7 = 5
  nums[1] = nums1[5] = 6
  nums = [5, 6, 3, 4, 5, 6, 7]

i=2:
  原索引 = (7 + 2 - 3) % 7 = 6 % 7 = 6
  nums[2] = nums1[6] = 7
  nums = [5, 6, 7, 4, 5, 6, 7]

i=3:
  原索引 = (7 + 3 - 3) % 7 = 7 % 7 = 0
  nums[3] = nums1[0] = 1
  nums = [5, 6, 7, 1, 5, 6, 7]

i=4:
  原索引 = (7 + 4 - 3) % 7 = 8 % 7 = 1
  nums[4] = nums1[1] = 2
  nums = [5, 6, 7, 1, 2, 6, 7]

i=5:
  原索引 = (7 + 5 - 3) % 7 = 9 % 7 = 2
  nums[5] = nums1[2] = 3
  nums = [5, 6, 7, 1, 2, 3, 7]

i=6:
  原索引 = (7 + 6 - 3) % 7 = 10 % 7 = 3
  nums[6] = nums1[3] = 4
  nums = [5, 6, 7, 1, 2, 3, 4]

最终输出: [5, 6, 7, 1, 2, 3, 4]

示例2nums = [-1,-100,3,99], k = 2

复制代码
初始状态:
nums = [-1, -100, 3, 99]
nums1 = [-1, -100, 3, 99]
n = 4
k % n = 2 % 4 = 2

i=0:
  原索引 = (4 + 0 - 2) % 4 = 2
  nums[0] = nums1[2] = 3
  nums = [3, -100, 3, 99]

i=1:
  原索引 = (4 + 1 - 2) % 4 = 3
  nums[1] = nums1[3] = 99
  nums = [3, 99, 3, 99]

i=2:
  原索引 = (4 + 2 - 2) % 4 = 4 % 4 = 0
  nums[2] = nums1[0] = -1
  nums = [3, 99, -1, 99]

i=3:
  原索引 = (4 + 3 - 2) % 4 = 5 % 4 = 1
  nums[3] = nums1[1] = -100
  nums = [3, 99, -1, -100]

最终输出: [3, 99, -1, -100]

1.6 算法图解

复制代码
原始数组: [1, 2, 3, 4, 5, 6, 7]
索引:       0  1  2  3  4  5  6
k = 3

向右旋转3步:

步骤1: 旋转1步
[1, 2, 3, 4, 5, 6, 7]
                    ↓
[7, 1, 2, 3, 4, 5, 6]

步骤2: 旋转2步
[7, 1, 2, 3, 4, 5, 6]
                 ↓
[6, 7, 1, 2, 3, 4, 5]

步骤3: 旋转3步
[6, 7, 1, 2, 3, 4, 5]
              ↓
[5, 6, 7, 1, 2, 3, 4]

索引映射关系:
新索引0 ← 原索引4  (nums[4]=5 → nums[0])
新索引1 ← 原索引5  (nums[5]=6 → nums[1])
新索引2 ← 原索引6  (nums[6]=7 → nums[2])
新索引3 ← 原索引0  (nums[0]=1 → nums[3])
新索引4 ← 原索引1  (nums[1]=2 → nums[4])
新索引5 ← 原索引2  (nums[2]=3 → nums[5])
新索引6 ← 原索引3  (nums[3]=4 → nums[6])

1.7 复杂度分析

分析维度 复杂度 说明
时间复杂度 O(n) 遍历数组一次
空间复杂度 O(n) 克隆数组

优化思路:可以使用三次翻转实现O(1)空间

java 复制代码
// 优化版本:三次翻转
class Solution {
    public void rotate(int[] nums, int k) {
        k %= nums.length;
        reverse(nums, 0, nums.length - 1);  // 翻转整个数组
        reverse(nums, 0, k - 1);            // 翻转前k个
        reverse(nums, k, nums.length - 1);  // 翻转剩余部分
    }

    private void reverse(int[] nums, int start, int end) {
        while (start < end) {
            int temp = nums[start];
            nums[start] = nums[end];
            nums[end] = temp;
            start++;
            end--;
        }
    }
}

翻转过程

复制代码
原始: [1, 2, 3, 4, 5, 6, 7], k = 3

步骤1: 翻转整个数组
[7, 6, 5, 4, 3, 2, 1]

步骤2: 翻转前k个
[5, 6, 7, 4, 3, 2, 1]

步骤3: 翻转剩余部分
[5, 6, 7, 1, 2, 3, 4]

1.8 边界情况

nums k 说明 输出
[1,2,3] 0 不旋转 [1,2,3]
[1,2,3] 3 旋转一周 [1,2,3]
[1] 5 单元素 [1]
[1,2] 1 旋转一次 [2,1]

2. 买卖股票的最佳时机(Best Time to Buy and Sell Stock)

2.1 题目描述

给定一个数组 prices,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

示例 1

复制代码
输入:[7,1,5,3,6,4]
输出:5
解释:在第2天(股票价格=1)的时候买入,在第5天(股票价格=6)的时候卖出,最大利润=6-1=5。
注意利润不能是7-1=6,因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2

复制代码
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下,没有交易完成,所以最大利润为0。

2.2 解题思路

这道题使用暴力枚举的方法:

  1. 使用两层循环遍历所有买入和卖出的组合
  2. 外层循环选择买入日期
  3. 内层循环选择卖出日期(必须在买入日期之后)
  4. 计算利润并更新最大值

解题步骤

  1. 初始化maxprofit = 0
  2. 外层循环遍历买入日期i
  3. 内层循环遍历卖出日期j(j > i)
  4. 计算profit = prices[j] - prices[i]
  5. 更新maxprofit

2.3 代码实现

java 复制代码
public class Solution {
    public int maxProfit(int[] prices) {
        int maxprofit = 0;
        for (int i = 0; i < prices.length - 1; i++) {
            for (int j = i + 1; j < prices.length; j++) {
                int profit = prices[j] - prices[i];
                if (profit > maxprofit) {
                    maxprofit = profit;
                }
            }
        }
        return maxprofit;
    }
}

2.4 代码逐行解释

第一部分:初始化
java 复制代码
int maxprofit = 0;

功能:记录最大利润,初始为0

含义 说明
maxprofit = 0 如果找不到利润,返回0
第二部分:双层循环
java 复制代码
for (int i = 0; i < prices.length - 1; i++) {
    for (int j = i + 1; j < prices.length; j++) {
        int profit = prices[j] - prices[i];
        if (profit > maxprofit) {
            maxprofit = profit;
        }
    }
}

循环说明

循环 变量 起始值 结束值 作用
外层 i 0 length-2 买入日期
内层 j i+1 length-1 卖出日期

为什么i < prices.length - 1

复制代码
prices = [7, 1, 5, 3, 6, 4]
索引:     0  1  2  3  4  5

如果i = 5(最后一天):
j从6开始,但数组长度是6,越界!

所以i最大是4(length-2)

利润计算

java 复制代码
int profit = prices[j] - prices[i];
变量 说明
prices[i] 买入价格(第i天)
prices[j] 卖出价格(第j天)
profit 利润 = 卖出价 - 买入价

2.5 执行流程详解

示例1prices = [7,1,5,3,6,4]

复制代码
初始状态:
prices = [7, 1, 5, 3, 6, 4]
maxprofit = 0

i=0, prices[i]=7:
  j=1: prices[1]=1, profit=1-7=-6, maxprofit=0
  j=2: prices[2]=5, profit=5-7=-2, maxprofit=0
  j=3: prices[3]=3, profit=3-7=-4, maxprofit=0
  j=4: prices[4]=6, profit=6-7=-1, maxprofit=0
  j=5: prices[5]=4, profit=4-7=-3, maxprofit=0

i=1, prices[i]=1:
  j=2: prices[2]=5, profit=5-1=4, maxprofit=4
  j=3: prices[3]=3, profit=3-1=2, maxprofit=4
  j=4: prices[4]=6, profit=6-1=5, maxprofit=5
  j=5: prices[5]=4, profit=4-1=3, maxprofit=5

i=2, prices[i]=5:
  j=3: prices[3]=3, profit=3-5=-2, maxprofit=5
  j=4: prices[4]=6, profit=6-5=1, maxprofit=5
  j=5: prices[5]=4, profit=4-5=-1, maxprofit=5

i=3, prices[i]=3:
  j=4: prices[4]=6, profit=6-3=3, maxprofit=5
  j=5: prices[5]=4, profit=4-3=1, maxprofit=5

i=4, prices[i]=6:
  j=5: prices[5]=4, profit=4-6=-2, maxprofit=5

循环结束,返回 maxprofit = 5

输出: 5

示例2prices = [7,6,4,3,1]

复制代码
初始状态:
prices = [7, 6, 4, 3, 1]
maxprofit = 0

i=0, prices[i]=7:
  j=1: profit=6-7=-1, maxprofit=0
  j=2: profit=4-7=-3, maxprofit=0
  j=3: profit=3-7=-4, maxprofit=0
  j=4: profit=1-7=-6, maxprofit=0

i=1, prices[i]=6:
  j=2: profit=4-6=-2, maxprofit=0
  j=3: profit=3-6=-3, maxprofit=0
  j=4: profit=1-6=-5, maxprofit=0

i=2, prices[i]=4:
  j=3: profit=3-4=-1, maxprofit=0
  j=4: profit=1-4=-3, maxprofit=0

i=3, prices[i]=3:
  j=4: profit=1-3=-2, maxprofit=0

循环结束,返回 maxprofit = 0

输出: 0

2.6 算法图解

复制代码
prices = [7, 1, 5, 3, 6, 4]
天数:     0  1  2  3  4  5

所有买入卖出组合:

买入0: 7
  卖出1: 1  → 利润: -6
  卖出2: 5  → 利润: -2
  卖出3: 3  → 利润: -4
  卖出4: 6  → 利润: -1
  卖出5: 4  → 利润: -3

买入1: 1
  卖出2: 5  → 利润:  4
  卖出3: 3  → 利润:  2
  卖出4: 6  → 利润:  5 ← 最大
  卖出5: 4  → 利润:  3

买入2: 5
  卖出3: 3  → 利润: -2
  卖出4: 6  → 利润:  1
  卖出5: 4  → 利润: -1

买入3: 3
  卖出4: 6  → 利润:  3
  卖出5: 4  → 利润:  1

买入4: 6
  卖出5: 4  → 利润: -2

最大利润: 5

2.7 复杂度分析

分析维度 复杂度 说明
时间复杂度 O(n²) 两层循环
空间复杂度 O(1) 只使用常数空间

优化思路:可以使用一次遍历优化到O(n)

java 复制代码
// 优化版本:一次遍历
public class Solution {
    public int maxProfit(int[] prices) {
        int minprice = Integer.MAX_VALUE;
        int maxprofit = 0;

        for (int i = 0; i < prices.length; i++) {
            if (prices[i] < minprice) {
                minprice = prices[i];  // 更新最低价格
            } else if (prices[i] - minprice > maxprofit) {
                maxprofit = prices[i] - minprice;  // 更新最大利润
            }
        }

        return maxprofit;
    }
}

一次遍历过程

复制代码
prices = [7, 1, 5, 3, 6, 4]

i=0: price=7
     7 < MAX_VALUE? 是
     minprice = 7

i=1: price=1
     1 < 7? 是
     minprice = 1

i=2: price=5
     5 < 1? 否
     5-1=4 > 0? 是
     maxprofit = 4

i=3: price=3
     3 < 1? 否
     3-1=2 > 4? 否

i=4: price=6
     6 < 1? 否
     6-1=5 > 4? 是
     maxprofit = 5

i=5: price=4
     4 < 1? 否
     4-1=3 > 5? 否

最终: maxprofit = 5

2.8 边界情况

prices 说明 输出
[7,6,4,3,1] 持续下跌 0
[1,2,3,4,5] 持续上涨 4
[1] 单个价格 0
[2,4,1] 先涨后跌 2

3. 两题对比与总结

3.1 算法对比

对比项 轮转数组 买卖股票最佳时机
核心算法 索引映射 暴力枚举
数据结构 数组 数组
时间复杂度 O(n) O(n²)
空间复杂度 O(n) O(1)
应用场景 数组旋转 最优化问题

3.2 数组旋转的技巧

方法一:克隆+索引映射

java 复制代码
int[] nums1 = nums.clone();
for (int i = 0; i < nums.length; i++) {
    nums[i] = nums1[(n + i - k % n) % n];
}

方法二:三次翻转

java 复制代码
reverse(nums, 0, n - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, n - 1);

方法三:环状替换

java 复制代码
int count = 0;
for (int start = 0; count < n; start++) {
    int current = start;
    int prev = nums[start];
    do {
        int next = (current + k) % n;
        int temp = nums[next];
        nums[next] = prev;
        prev = temp;
        current = next;
        count++;
    } while (start != current);
}

3.3 最大利润的解法

暴力枚举

java 复制代码
for (int i = 0; i < n - 1; i++) {
    for (int j = i + 1; j < n; j++) {
        maxprofit = Math.max(maxprofit, prices[j] - prices[i]);
    }
}

一次遍历

java 复制代码
int minprice = Integer.MAX_VALUE;
int maxprofit = 0;
for (int price : prices) {
    minprice = Math.min(minprice, price);
    maxprofit = Math.max(maxprofit, price - minprice);
}

3.4 模运算的应用

计算旋转后的索引

java 复制代码
// 向右旋转k步
新索引 = (原索引 + k) % n

// 向左旋转k步
新索引 = (原索引 - k + n) % n

// 或者
新索引 = (n + 原索引 - k % n) % n

处理k大于n的情况

复制代码
n = 5, k = 7

旋转7步 = 旋转 (7 % 5) = 旋转2步

所以先用 k % n 减少计算量

4. 总结

今天我们学习了两道数组操作题目:

  1. 轮转数组:掌握索引映射实现数组旋转,理解模运算在循环移位中的应用
  2. 买卖股票的最佳时机:掌握暴力枚举解决最优化问题,理解买入卖出的约束

核心收获

  • 数组克隆可以在修改时保留原始数据
  • 模运算可以处理循环移位问题
  • 暴力枚举是最直接的解决方法,但效率较低
  • 一次遍历可以显著优化时间复杂度
  • 最优化问题通常需要遍历所有可能的情况

练习建议

  1. 尝试用三次翻转实现数组旋转
  2. 学习用一次遍历解决买卖股票问题
  3. 思考如何处理多次买卖的情况

参考资源

文章标签

#LeetCode #算法 #Java #数组 #暴力搜索

喜欢这篇文章吗?别忘了点赞、收藏和分享!你的支持是我创作的最大动力!

相关推荐
hele_two2 小时前
快速幂算法
c++·python·算法
l1t2 小时前
利用DeepSeek将python DLX求解数独程序格式化并改成3.x版本
开发语言·python·算法·数独
jllllyuz3 小时前
基于子集模拟的系统与静态可靠性分析及Matlab优化算法实现
算法·matlab·概率论
程序员-King.3 小时前
day143—递归—对称二叉树(LeetCode-101)
数据结构·算法·leetcode·二叉树·递归
BlockChain8883 小时前
字符串最后一个单词的长度
算法·go
爱吃泡芙的小白白3 小时前
深入解析:2024年AI大模型核心算法与应用全景
人工智能·算法·大模型算法
阿崽meitoufa4 小时前
JVM虚拟机:垃圾收集器和判断对象是否存活的算法
java·jvm·算法
ballball~~4 小时前
拉普拉斯金字塔
算法·机器学习
Cemtery1164 小时前
Day26 常见的降维算法
人工智能·python·算法·机器学习