LeetCode刷题记录

LeetCode刷题记录

88.合并两个有序数组

题目

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

解题思路

逆向双指针:观察可知,nums1的后半部分是空的,可以直接覆盖而不会影响结果。因此可以指针设置为从后向前遍历,每次取两者之中的较大者放进 nums1的最后面。严格来说,在此遍历过程中的任意一个时刻,nums1数组中有 m − p1 − 1 个元素被放入nums1的后半部,nums2数组中有 n − p2 − 1 个元素被放入 nums1 的后半部,而在指针 p1 的后面,nums1 数组有 m + n − p1 − 1个位置。由于 m + n − p1 −1 ≥ m − p1 − 1 + n − p2 − 1 等价于 p2 ≥ −1 永远成立,因此 p1 后面的位置永远足够容纳被插入的元素,不会产生 p1 的元素被覆盖的情况。

代码实现

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int p1 = m -1, p2 = n-1;
        int tail = m + n -1;
        //如果 p1到达 -1,则说明 nums2中可能还存在小 nums1[0]的元素,如果 p2到达 -1,则说明nums1中剩余元素无需处理
        while (p1 >= 0 && p2 >= 0) {
            nums1[tail--] = (nums1[p1] > nums2[p2]) ? nums1[p1--] : nums2[p2--];
        }
        // 如果 nums2 中还有剩余元素,直接复制到 nums1 中
        while (p2 >= 0) {
            nums1[tail--] = nums2[p2--];
        }
    }
}

测试用例

nums1 = [1,2,3,0,0,0]
m = 3
nums2 = [2,5,6,7]
n = 4

27.移除元素

题目

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

解题思路

双指针:如果左指针 left 指向的元素等于 val,此时将右指针 right 指向的元素复制到左指针 left 的位置,然后右指针 right 左移一位。
如果赋值过来的元素恰好也等于 val,可以继续把右指针 right 指向的元素的值赋值过来(左指针 left 指向的等于 val 的元素的位置继续被覆盖),直到左指针指向的元素的值不等于 val 为止。当左指针 left 和右指针 right 重合的时候,左右指针遍历完数组中所有的元素。这样的方法两个指针在最坏的情况下合起来只遍历了数组一次。避免了需要保留的元素的重复赋值操作。(例如序列 [1,2,3,4,5],当 val为 1 时,我们需要把每一个元素都左移一位)

代码实现

class Solution {
    public int removeElement(int[] nums, int val) {
        int left = 0;
        int right = nums.length;
        while(left < right){
            //判断左指针是否和val相等,如果相等把右指针位置的元素赋值到左指针位置
            //此时左指针并不移动,右指针往右移动一位,这样可以避免右指针原始位置的元素也与val相等
            if(nums[left] == val){
                //先赋值再移动指针
                nums[left] = nums[--right];
            }else{
                left++;
            }
        }
        return left;
    }
}

测试用例

nums = [0,1,2,2,3,0,4,2]
val = 2

26.删除有序数组中的重复项

题目

给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。
考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:
更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
返回 k 。

解题思路

快慢指针:首先注意数组是有序的,那么重复的元素一定会相邻。要求删除重复元素,实际上就是将不重复的元素移到数组的左侧。
考虑用 快慢指针,快指针fast,慢指针slow,算法流程如下:
1.从下标为1开始比较 fast和 fast前一个位置的元素是否相等。
如果不相等,将 fast 位置的元素复制到 slow 位置上,slow 后移一位
不管是否相等 都把fast向后移一位
重复上述过程,直到 fast 等于数组长度。
返回 slow,即为新数组长度。

代码实现

class Solution {
    public int removeDuplicates(int[] nums) {
        int n = nums.length;
        if(n == 0){
            return 0;
        }
        //从下标 1 开始
        int fast = 1, slow = 1;
        while(fast < n){
            //如果不相等,将 fast 位置的元素复制到 slow 位置上,slow 后移一位
            if(nums[fast] != nums[fast -1]){
                nums[slow++] = nums[fast];
            }
            fast++;
        }
        return slow;
    }
}

测试用例

[0,0,1,1,1,2,2,3,3,4]

80.删除有序数组中的重复项 II

题目

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

解题思路

在解决本题前,可先尝试解决[删除有序数组中的重复项]
当且仅当nums[slow-2] == nums[fast]时才表示当前fast位置的元素需要被删除
特别地,数组的前两个数必然可以被保留,因此对于长度不超过 2的数组,我们无需进行任何处理,对于长度超过 2的数组,我们直接将双指针的初始值设为 2即可。

代码实现

class Solution {
    public int removeDuplicates(int[] nums) {
        int n = nums.length;
        if(n <= 2){
            return n;
        }
        // 从下标为2的位置开始
        int fast = 2, slow = 2;
        while(fast < n){
            if(nums[slow - 2] != nums[fast]){
                nums[slow++] = nums[fast]; 
            }
            fast++;
        }
        return slow;
    }
}

测试用例

[0,0,1,1,1,1,2,3,3]

169.多数元素

题目

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。

解题思路

Boyer-Moore 投票算法:如果我们把众数记为 +1,把其他数记为 −1,将它们全部加起来,显然和大于 0,从结果本身我们可以看出众数比其他数多。算法详解

代码实现

class Solution {
    public int majorityElement(int[] nums) {
        int count = 0;
        int candidate = nums[0];
        for(int num : nums){
            if(count == 0){
                candidate = num;
            }
            count += (candidate == num) ? 1 : -1;
        }
        return candidate;
    }
}

测试用例

[2,2,1,1,1,2,2]

189.轮转数组

题目

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

解题思路

数组翻转:当我们将数组的元素向右移动 k次后,尾部 k mod n个元素会移动至数组头部,其余元素向后移动 k mod n个位置。
该方法为数组的翻转:我们可以先将所有元素翻转,这样尾部的 k mod n个元素就被移至数组头部,然后我们再翻转 [0,k mod n−1] 区间的元素和 [k mod n,n−1] 区间的元素即能得到最后的答案。

操作 结果
原始数组 1 2 3 4 5 6 7
翻转所有元素 7 6 5 4 3 2 1
翻转 [0,k mod n−1] 区间的元素 5 6 7 4 3 2 1
翻转 [k mod n,n−1] 区间的元素 5 6 7 1 2 3 4

代码实现

class Solution {
    public void rotate(int[] nums, int k) {
        k = k % nums.length; 
        reverse(nums, 0, nums.length - 1);
        reverse(nums, 0, k - 1);
        reverse(nums, k, nums.length - 1);
    }

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

测试用例

[1,2,3,4,5,6,7] , 2

121.买卖股票的最佳时机

题目

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

解题思路

在遍历 prices 数组的过程中,当前元素的价格减之前的史低价格,即为当前元素卖出可得最大利润,跟已存在的最大利润作比较,在一次遍历后即可得最终结果

代码实现


class Solution {
    public int maxProfit(int[] prices) {
        int maxProfit = 0,minPrice = prices[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;
    }
}

测试用例

[7,1,5,3,6,4]

122.买卖股票的最佳时机2

题目

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。

解题思路

相对于[买卖股票的最佳时机],本题的要点在于可以多次买卖,并且可以在同一天同时进行买和卖的操作,所以可以考虑用动态规划法和贪心算法解题。

1.动态规划法
考虑到「不能同时参与多笔交易」,因此每天交易结束后有且只有两种状态:1-有股票,2-无股票
所以定义状态数组dp[i][0]表示第i天交易完后手里没有股票的最大利润,dp[i][1]表示第i天交易完后手里持有一支股票的最大利润(i 从 0 开始)。
对于 dp[i][0],表示的含义是第i天交易后,手里没有股票。第i-1天的状态可能为dp[i-1][0]或者dp[i-1][1],所以从第i-1天的状态过度过来,dp[i][0]的值为dp[i][0]=max⁡{dp[i−1][0],dp[i−1][1]+prices[i]},即(持续第i-1天没有股票的状态)或者(把第i-1天持有的股票卖出)两者的最大值
对于 dp[i][1],表示的含义是第i天交易后,手里有股票。第i-1天的状态可能为dp[i-1][0]或者dp[i-1][1],所以从第i-1天的状态过度过来,dp[i][1]的值为dp[i][1]=max⁡{dp[i−1][1],dp[i−1][0]-prices[i]},即(持续第i-1天有股票的状态)或者(在第i天以当天的价格购入股票)两者的最大值
对于初始状态,根据状态定义我们可以知道第0天交易结束的时候 dp[0][0]=0,dp[0][1]=−prices[0]
因此,我们只要从前往后依次计算状态即可。由于全部交易结束后,持有股票的收益一定低于不持有股票的收益,因此这时候(n-1为最后一个元素的下标) dp[n−1][0]的收益必然是大于 dp[n−1][1]的,最后的答案即为 dp[n−1][0]。
2.贪心算法
关于贪心算法:https://blog.csdn.net/weixin_46272350/article/details/120908253
因为不论买卖次数,所以可以用贪心算法求最大利润。即把每个i-1天值小于i天值的差值进行累加,即得到可能的最大利润

代码实现

//动态规划
class Solution {
    public int maxProfit(int[] prices) {
        int dp0 = 0,dp1 = -prices[0];
        for(int i = 0; i< prices.length; i++){
            int newDp0 = Math.max(dp0, dp1 + prices[i]);
            int newDp1 = Math.max(dp0 - prices[i], dp1);
            dp0 = newDp0;
            dp1 = newDp1;
        }
        return dp0;
    }
}

//贪心
class Solution {
    public int maxProfit(int[] prices) {
        int ans = 0;
        for (int i = 1; i < prices.length; ++i) {
            ans += Math.max(0, prices[i] - prices[i - 1]);
        }
        return ans;
    }
}

测试用例

[7,1,5,3,6,4]

123.买卖股票的最佳时机3

题目

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

解题思路

相对于[买卖股票的最佳时机2],本题的要点在于只能完成两笔交易,所以动态规划的状态变成了5种即:1-未操作,2-第一次买入,3-第一次卖出,4-第二次买入,5-第二次卖出。

1.动态规划法
详解参考[买卖股票的最佳时机2]
第i天的情况下:
因为未操作的情况下,状态值永远为0,所以不作讨论;
对于第一次买入 buy1:可能的值为【第i天未操作即buy1[i-1]】和【以第i天的价格第一次买入即-prices[i]】的最大值
对于第一次卖出 sell1:可能的值为【第i天未操作即sell1[i-1]】和【以第i天的价格第一次卖出即buy1[i-1]+prices[i]】的最大值
对于第二次买入 buy2:可能的值为【第i天未操作即buy2[i-1]】和【以第i天的价格第二次买入即sell1[i-1]-prices[i]】的最大值
对于第二次卖出 sell2:可能的值为【第i天未操作即sell2[i-1]】和【以第i天的价格第二次卖出即buy2[i-1]+prices[i]】的最大值
遍历后取sell2的值即为所求最大利润

代码实现

//动态规划
class Solution {
    public int maxProfit(int[] prices) {
        int buy1 = -prices[0], sell1 = 0, buy2 = -prices[0], sell2 = 0;
        for(int i = 1; i < prices.length; i++){
            buy1 = Math.max(buy1, -prices[i]);
            sell1 = Math.max(buy1 + prices[i], sell1);
            buy2 = Math.max(buy2, sell1 -prices[i]);
            sell2 = Math.max(sell2, buy2 + prices[i]);
        }
        return sell2;
    }
}

测试用例

[3,3,5,0,0,3,1,4]
posted @   脉动丶  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类
点击右上角即可分享
微信分享提示