序号 | 题目 | 难度 |
---|---|---|
6 | 轮转数组 | 中等 |
7 | 买卖股票的最佳时机 | 简单 |
8 | 买卖股票的最佳时机 II | 中等 |
9 | 跳跃游戏 | 中等 |
10 | 跳跃游戏 II | 中等 |
6、轮转数组
题目:
给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
解题思路:
方法一:暴力法
旋转k次,每次将数组旋转1个元素。
时间复杂度:O(k*n),空间复杂度:O(1),其中 n 是数组的长度。
完整代码:
public class Solution {
public void rotate(int[] nums, int k) {
int n = nums.length; // 获取数组长度
// 防止 k 大于 n,取余数作为实际需要移动的步数
k = k % n;
for (int i = 0; i < k; i++) { // 移动 k 次
// 保存最后一个元素的值
int prev = nums[n - 1];
for (int j = 0; j < n; j++) {
int temp = nums[j];
nums[j] = prev;
prev = temp;
}
}
}
}
方法二:使用额外的数组
我们可以使用额外的数组来将每个元素放至正确的位置上,也就是原本数组里下标为i的我们把它放到(i+k) % 数组长度 的位置上。
时间复杂度:O(n),空间复杂度:O(n)。
完整代码:
public class Solution {
public void rotate(int[] nums, int k) {
int n = nums.length;
k = k % n;
int[] newNums = new int[n];
for (int i = 0; i < n; i++) {
// 将每个元素放到新数组中正确的位置上
newNums[(i + k) % n] = nums[i];
}
for (int i = 0; i < n; i++) {
nums[i] = newNums[i]; // 将新数组复制回原数组
}
}
}
这段代码是方法二中使用额外数组的实现方式。让我解释一下这段代码的含义:
-
newNums[(i + k) % n] = nums[i];
:这行代码的作用是将原数组nums
中的元素按照右移k
个位置后的新位置存放到新数组newNums
中。具体来说,(i + k) % n
是计算右移k
个位置后的新位置,其中% n
是为了处理循环的情况,使得超出数组长度的部分回到数组开头。然后将原数组中第i
个位置的元素存放到新位置上。 -
for (int i = 0; i < n; i++) { nums[i] = newNums[i]; }
:这段代码的作用是将新数组newNums
中的元素复制回原数组nums
中,完成整个右移操作。通过循环遍历,将新数组中的元素一个一个地复制回原数组对应位置,从而实现右移k
个位置的效果。
这种方法主要是通过额外的数组来存储右移后的元素顺序,然后再复制回原数组来实现整体右移的效果。
方法三:翻转
首先将数组整个翻转,然后将前k个元素翻转,再将后面的元素翻转,就能得到答案。
时间复杂度:O(n),空间复杂度:O(1)。
完整代码:
public class Solution {
public void reverse(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
public void rotate(int[] nums, int k) {
int n = nums.length;
k = k % n;
reverse(nums, 0, n - 1); // 整个数组翻转
reverse(nums, 0, k - 1); // 前k个元素翻转
reverse(nums, k, n - 1); // 后面的元素翻转
}
}
这段代码是使用原地右移的方法来实现数组右移的。具体来说,它采用了三次反转数组的部分来达到右移的效果。
假设初始数组 nums
为:[1, 2, 3, 4, 5, 6, 7]
,然后我们对该数组进行右移操作,即 k = 3
。
-
首先,通过
reverse(nums, 0, n - 1)
将整个数组翻转。整个数组翻转后变为:[7, 6, 5, 4, 3, 2, 1]
。
这样,数组中的元素顺序完全反转了,但数组的相对位置并没有改变。 -
然后,再通过
reverse(nums, 0, k - 1)
将前k
个元素翻转。前k = 3
个元素翻转后变为:[5, 6, 7, 4, 3, 2, 1]
。
这样,原本在数组末尾的k
个元素会移动到数组的开头位置。 -
最后,通过
reverse(nums, k, n - 1)
将剩余的元素翻转。剩下的元素翻转后不变:[5, 6, 7, 1, 2, 3, 4]
。
这样,原本在数组开头的剩余元素和原本在数组末尾的k
个元素的相对位置会被调整正确。
综合以上三步,就可以完成原地右移的操作,而不需要使用额外的空间。
7、买卖股票的最佳时机
题目:
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
解题思路:
这是一个经典的股票买卖问题,可以通过遍历数组一次来解决。
- 初始化两个变量
min_price
和max_profit
,分别表示最低价格和最大利润,初始值分别为正无穷大和0。 - 遍历数组
prices
,对于每一天的股票价格:- 如果当前价格比
min_price
小,则更新min_price
为当前价格。 - 否则,计算当前价格与
min_price
的差值,如果大于max_profit
,则更新max_profit
。
- 如果当前价格比
- 遍历完成后,
max_profit
即为最大利润。
完整代码:
class BestTimeToBuyAndSellStock {
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) {
return 0;
}
int minPrice = Integer.MAX_VALUE;// 初始化最低股票价格为最大值
int maxProfit = 0;// 初始化最大利润为0
for (int price : prices) {
if (price < minPrice) {
minPrice = price;
} else {
maxProfit = Math.max(maxProfit, price - minPrice);
}
}
return maxProfit;
}
}
注意:
minPrice
被初始化为整型的最大值 Integer.MAX_VALUE,以确保第一个股票价格一定会比它小。maxProfit
被初始化为 0,表示开始时没有利润。
8、买卖股票的最佳时机 II
题目:
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
解题思路:
常用方法是使用贪心算法。
贪心算法(Greedy Algorithm)是一种基于贪心策略的算法设计方法。在贪心算法中,每一步都采取当前状态下最优的选择,而不考虑未来可能发生的情况。通过每一步的局部最优选择,最终希望能够得到全局最优解。
在遍历股票价格数组的过程中,我们可以计算相邻两天的价格差,如果价格差为正数,则可以将这部分利润累加到总利润中。
public class BestTimeToBuyAndSellStockII {
public int maxProfit(int[] prices) {
int maxProfit = 0;
for (int i = 1; i < prices.length; i++) {
if (prices[i] > prices[i - 1]) {
maxProfit += prices[i] - prices[i - 1];
}
}
return maxProfit;
}
上述代码逻辑如下:
- 首先,初始化变量
maxProfit
为0,用于记录总利润。 - 然后,从第二天开始遍历股票价格数组
prices
。 - 在每一天,判断当前股票价格是否比前一天的价格高,如果是则表示可以获得利润。
- 如果可以获得利润,则将利润累加到
maxProfit
中。 - 最终返回
maxProfit
,即为可以获得的最大利润。
这段代码的主要思想是通过贪心算法,在每次价格上涨时就进行交易,从而获得最大总利润。这种方法可以有效地解决这类股票交易问题,并且时间复杂度较低。
9、跳跃游戏
题目:
给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。
解题思路:
这题同样使用贪心算法
贪心算法通常适用于满足以下两个条件的问题:
-
最优子结构:问题的最优解可以通过子问题的最优解来求解。换句话说,整体最优解可以通过局部最优解推导得到。
-
贪心选择性质:在每一步选择中,都采取当前状态下的最优决策,即局部最优解,以期望达到全局最优解。
基于这两个条件,贪心算法在某些特定问题上具有高效的求解能力,因为它不需要考虑所有可能的情况,只需选择当前看起来最优的方案。一旦问题具有贪心选择性质和最优子结构,那么可以尝试使用贪心算法来解决。
言归正传
这题,我们可以遍历数组,维护一个变量 maxReach
表示当前能够到达的最远位置。
在遍历过程中,不断更新 maxReach
,如果某个位置超过了 maxReach
,则说明无法到达该位置,返回 false。
完整代码:
以下是用Java编写的实现代码:
public class JumpGame {
public boolean canJump(int[] nums) {
int maxReach = 0;
for (int i = 0; i < nums.length; i++) {
//如果 i 大于 maxReach,意味着当前位置已经超过了当前能够到达的最远位置
if (i > maxReach) {
return false;
}
//如果 i + nums[i] 大于 maxReach,说明从当前位置出发可以到达更远的位置,因此我们更新 maxReach 的值。
maxReach = Math.max(maxReach, i + nums[i]);
if (maxReach >= nums.length - 1) {
return true;
}
}
return false;
}
}
在这段代码中,我们定义了一个名为 JumpGame
的类,并包含了一个 canJump
方法,用于判断是否能够到达最后一个下标。
具体做法如下:
- 我们初始化变量
maxReach
为0,表示当前能够到达的最远位置。 - 遍历数组
nums
,对于每个位置:- 如果当前位置超过了
maxReach
,说明无法到达该位置,返回 false。 - 更新
maxReach
,取当前位置和当前位置可跳跃的最大长度之和的最大值。 - 如果
maxReach
大于等于数组长度减1,则说明可以到达最后一个下标,返回 true。
- 如果当前位置超过了
- 如果遍历结束仍未返回 true,则返回 false。
这个解决方案利用贪心策略,每次选择能够使得到达范围最大化的位置进行跳跃,从而判断是否能够到达最后一个下标。
10、跳跃游戏 II
题目:
给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。
每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:
解题思路:
这个问题是要求找到到达数组末尾的最少跳跃次数,我们可以使用贪心算法来解决。
思路如下:
-
我们维护三个变量:
jumps
表示跳跃次数,curEnd
表示当前跳跃范围的边界,maxReach
表示当前能够到达的最远位置。 -
我们遍历数组,更新
maxReach
的值为当前位置能够到达的最远位置。同时,当遍历到当前跳跃范围的边界curEnd
时,表示需要进行下一次跳跃,并将curEnd
更新为maxReach
。 -
最终返回的
jumps
即为到达数组末尾的最少跳跃次数。
这种贪心算法的思路是尽可能以最少的跳跃次数到达目标位置,每次选择能够跳到的最远位置进行跳跃。通过这样的贪心选择策略,可以保证找到到达目标位置的最少跳跃次数。
完整代码:
public class JumpGameII {
public int jump(int[] nums) {
if(nums == null || nums.length <= 1) {
return 0;
}
int jumps = 0;
int curEnd = 0;
int maxReach = 0;
for(int i = 0; i < nums.length - 1; i++) {
maxReach = Math.max(maxReach, i + nums[i]);
if(i == curEnd) {
jumps++;
curEnd = maxReach;
}
}
return jumps;
}
}
这段 Java 代码解决了「跳跃游戏 II」这个问题,找到到达数组末尾的最少跳跃次数。
代码解析:
-
首先,我们定义了一个
jump
方法,接收一个整型数组nums
作为输入参数,并返回到达数组末尾的最少跳跃次数。 -
在
jump
方法中,我们首先判断如果数组为空或长度小于等于1,直接返回0,因为不需要跳跃。 -
然后,我们初始化三个变量:
jumps
表示跳跃次数,curEnd
表示当前跳跃范围的边界,maxReach
表示当前能够到达的最远位置。 -
接下来,我们使用一个循环遍历数组
nums
,从索引0开始,一直到倒数第二个位置(因为最后一个位置不需要再跳跃)。 -
在循环中,我们更新
maxReach
的值为当前位置能够跳到的最远位置,即maxReach = Math.max(maxReach, i + nums[i])
。 -
在每次遍历时,我们还检查当前位置是否等于当前跳跃范围的边界
curEnd
,如果是,表示需要进行下一次跳跃,此时我们增加jumps
计数并将curEnd
更新为maxReach
。 -
最终,当循环结束后,我们返回
jumps
,即为到达数组末尾的最少跳跃次数。 -
在
main
方法中,我们定义了一个示例数组nums
,调用jump
方法计算最少跳跃次数,并输出结果。
通过以上步骤,这段代码实现了使用贪心算法找到到达数组末尾的最少跳跃次数。