【每日算法】每天五道算法,最经典 150 题,掌握面试所有知识点(10/150)

序号题目难度
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]; // 将新数组复制回原数组
        }
    }
}

这段代码是方法二中使用额外数组的实现方式。让我解释一下这段代码的含义:

  1. newNums[(i + k) % n] = nums[i];:这行代码的作用是将原数组 nums 中的元素按照右移 k 个位置后的新位置存放到新数组 newNums 中。具体来说,(i + k) % n 是计算右移 k 个位置后的新位置,其中 % n 是为了处理循环的情况,使得超出数组长度的部分回到数组开头。然后将原数组中第 i 个位置的元素存放到新位置上。

  2. 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

  1. 首先,通过 reverse(nums, 0, n - 1) 将整个数组翻转。整个数组翻转后变为:[7, 6, 5, 4, 3, 2, 1]
    这样,数组中的元素顺序完全反转了,但数组的相对位置并没有改变。

  2. 然后,再通过 reverse(nums, 0, k - 1) 将前 k 个元素翻转。前 k = 3 个元素翻转后变为:[5, 6, 7, 4, 3, 2, 1]
    这样,原本在数组末尾的 k 个元素会移动到数组的开头位置。

  3. 最后,通过 reverse(nums, k, n - 1) 将剩余的元素翻转。剩下的元素翻转后不变:[5, 6, 7, 1, 2, 3, 4]
    这样,原本在数组开头的剩余元素和原本在数组末尾的 k 个元素的相对位置会被调整正确。

综合以上三步,就可以完成原地右移的操作,而不需要使用额外的空间。

7、买卖股票的最佳时机

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

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

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

解题思路:

这是一个经典的股票买卖问题,可以通过遍历数组一次来解决。

  1. 初始化两个变量 min_pricemax_profit,分别表示最低价格和最大利润,初始值分别为正无穷大和0。
  2. 遍历数组 prices,对于每一天的股票价格:
    • 如果当前价格比 min_price 小,则更新 min_price 为当前价格。
    • 否则,计算当前价格与 min_price 的差值,如果大于 max_profit,则更新 max_profit
  3. 遍历完成后,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;
    }

上述代码逻辑如下:

  1. 首先,初始化变量 maxProfit 为0,用于记录总利润。
  2. 然后,从第二天开始遍历股票价格数组 prices
  3. 在每一天,判断当前股票价格是否比前一天的价格高,如果是则表示可以获得利润。
  4. 如果可以获得利润,则将利润累加到 maxProfit 中。
  5. 最终返回 maxProfit,即为可以获得的最大利润。

这段代码的主要思想是通过贪心算法,在每次价格上涨时就进行交易,从而获得最大总利润。这种方法可以有效地解决这类股票交易问题,并且时间复杂度较低。

9、跳跃游戏

题目:
给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。

解题思路:

这题同样使用贪心算法
贪心算法通常适用于满足以下两个条件的问题:

  1. 最优子结构:问题的最优解可以通过子问题的最优解来求解。换句话说,整体最优解可以通过局部最优解推导得到。

  2. 贪心选择性质:在每一步选择中,都采取当前状态下的最优决策,即局部最优解,以期望达到全局最优解。

基于这两个条件,贪心算法在某些特定问题上具有高效的求解能力,因为它不需要考虑所有可能的情况,只需选择当前看起来最优的方案。一旦问题具有贪心选择性质和最优子结构,那么可以尝试使用贪心算法来解决。

言归正传
这题,我们可以遍历数组,维护一个变量 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] 处:

解题思路:

这个问题是要求找到到达数组末尾的最少跳跃次数,我们可以使用贪心算法来解决。

思路如下:

  1. 我们维护三个变量:jumps 表示跳跃次数,curEnd 表示当前跳跃范围的边界,maxReach 表示当前能够到达的最远位置。

  2. 我们遍历数组,更新 maxReach 的值为当前位置能够到达的最远位置。同时,当遍历到当前跳跃范围的边界 curEnd 时,表示需要进行下一次跳跃,并将 curEnd 更新为 maxReach

  3. 最终返回的 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」这个问题,找到到达数组末尾的最少跳跃次数。

代码解析:

  1. 首先,我们定义了一个 jump 方法,接收一个整型数组 nums 作为输入参数,并返回到达数组末尾的最少跳跃次数。

  2. jump 方法中,我们首先判断如果数组为空或长度小于等于1,直接返回0,因为不需要跳跃。

  3. 然后,我们初始化三个变量:jumps 表示跳跃次数,curEnd 表示当前跳跃范围的边界,maxReach 表示当前能够到达的最远位置。

  4. 接下来,我们使用一个循环遍历数组 nums,从索引0开始,一直到倒数第二个位置(因为最后一个位置不需要再跳跃)。

  5. 在循环中,我们更新 maxReach 的值为当前位置能够跳到的最远位置,即 maxReach = Math.max(maxReach, i + nums[i])

  6. 在每次遍历时,我们还检查当前位置是否等于当前跳跃范围的边界 curEnd,如果是,表示需要进行下一次跳跃,此时我们增加 jumps 计数并将 curEnd 更新为 maxReach

  7. 最终,当循环结束后,我们返回 jumps,即为到达数组末尾的最少跳跃次数。

  8. main 方法中,我们定义了一个示例数组 nums,调用 jump 方法计算最少跳跃次数,并输出结果。

通过以上步骤,这段代码实现了使用贪心算法找到到达数组末尾的最少跳跃次数。

  • 22
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值