Always keep a be|

dr4w

园龄:1年粉丝:1关注:2

力扣刷题

题目2

题目描述

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。

假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:

  • 更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
  • 返回 k

用户评测:

评测机将使用以下代码测试您的解决方案:

int[] nums = [...]; // 输入数组
int val = ...; // 要移除的值
int[] expectedNums = [...]; // 长度正确的预期答案。
                            // 它以不等于 val 的值排序。

int k = removeElement(nums, val); // 调用你的实现

assert k == expectedNums.length;
sort(nums, 0, k); // 排序 nums 的前 k 个元素
for (int i = 0; i < actualLength; i++) {
    assert nums[i] == expectedNums[i];
}

如果所有的断言都通过,你的解决方案将会 通过

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,_,_]
解释:你的函数函数应该返回 k = 2, 并且 nums 中的前两个元素均为 2。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。

示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3,_,_,_]
解释:你的函数应该返回 k = 5,并且 nums 中的前五个元素为 0,0,1,3,4。
注意这五个元素可以任意顺序返回。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。

常规接法

class Solution {
    public int removeElement(int[] nums, int val) {
        int len = nums.length;
        int index = 0;
        int count = 0;
        for (int i = 0;i < len; i++ ){
            if(nums[i] != val){
                nums[index] = nums[i];
                index ++;
            }
            else {
                count ++;
            }
        }
        return len - count;
    }
}

进阶解法

标签:交换移除
主要思路是遍历数组 nums,遍历指针为 i,总长度为 ans
在遍历过程中如果出现数字与需要移除的值不相同时,则 i 自增 1 ,继续下一次遍历
如果相同的时候,则将 nums[i]nums[ans-1] 交换,即当前数字和数组最后一个数字进行交换,交换后就少了一个元素,故而 ans 自减 1
这种思路在移除元素较少时更适合使用,最极端的情况是没有元素需要移除,遍历一遍结束即可
时间复杂度:O(n),空间复杂度:O(1)

class Solution {
    public int removeElement(int[] nums, int val) {
        int len = nums.length;
        for (int i = 0;i<len;){
            if (nums[i] == val){
                nums[i] = nums[len-1];
                len --;
            }
            else {
                i++;
            }
        }
        System.out.println(len);
        for (int i = 0;i<len;i++){
            System.out.println(nums[i]);
        }
        return len;
    }
}

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

题目描述

给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

  • 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
  • 返回 k

判题标准:

系统会用下面的代码来测试你的题解:

int[] nums = [...]; // 输入数组
int[] expectedNums = [...]; // 长度正确的期望答案

int k = removeDuplicates(nums); // 调用

assert k == expectedNums.length;
for (int i = 0; i < k; i++) {
    assert nums[i] == expectedNums[i];
}

如果所有断言都通过,那么您的题解将被 通过

示例 1:

输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。

示例 2:

输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

提示:

  • 1 <= nums.length <= 3 * 104
  • -104 <= nums[i] <= 104
  • nums 已按 非严格递增 排列
解法

遍历数组,设置一个值代表当前循环中所在相同子序列的值,当遍历到下一个相同子序列时,更新count,更新该值

class Solution {
    public int removeDuplicates(int[] nums) {
        int len = nums.length;
        int count = 1;
        int current_num = nums[0];
        if (len == 1){
            return 1;
        }
        for (int i = 0; i< len; i++){
            if(nums[i] > current_num){
                current_num = nums[i];
                nums[count] = nums[i];
                count ++;
            }
        }
        //new nums_show().nums_print(nums);
        return count;
    }
}

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

题目描述

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
} 

示例 1:

输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3。 不需要考虑数组中超出新长度后面的元素。

示例 2:

输入:nums = [0,0,1,1,1,1,2,3,3]
输出:7, nums = [0,0,1,1,2,3,3]
解释:函数应返回新长度 length = 7, 并且原数组的前七个元素被修改为 0, 0, 1, 1, 2, 3, 3。不需要考虑数组中超出新长度后面的元素。

提示:

  • 1 <= nums.length <= 3 * 104
  • -104 <= nums[i] <= 104
  • nums 已按升序排列
解题思路

设置flagcurrent_num值,遍历数组,若当前值大于current_num,则更新current_num和数组,且置flag=0,若其等于current_num,若flag=0,则置其为1,更新数组,否则不做操作。

class Solution {
    public int removeDuplicates(int[] nums) {
        int len = nums.length;
        int count = 1;
        if(len == 1){
            return 1;
        }
        int current_num = nums[0];
        int flag = 0;
        for (int i = 1; i < len ; i++){
            if(nums[i] == current_num && flag ==0){
                nums[count] = nums[i];
                count ++;
                flag = 1;
            }
            else if(nums[i] > current_num){
                flag = 0;
                current_num = nums[i];
                nums[count] = nums[i];
                count ++;
            }
        }
        //new nums_show().nums_print(nums);
        return count;
    }
}

简化之后

将上述代码的current_num等价为索引为count-2的位置,如果相等说明已经有两个相同的数字了,不再进行操作

class Solution {
    public int removeDuplicates(int[] nums) {
        int index = 2;
        for (int i = 2; i < nums.length; i++){
            if(nums[i] != nums[index - 2]){
                nums[index] = nums[i];
                index++;
            }
        }
        return index;
    }
}

我们用一个变量 k 记录当前已经处理好的数组的长度,初始时 k=0,表示空数组。

然后我们从左到右遍历数组,对于遍历到的每个元素 x,如果 k<2 或者xnums[k2],我们就将 x 放到 nums[k] 的位置,然后 k 自增 1。否则,x 与 nums[k−2] 相同,我们直接跳过这个元素。继续遍历,直到遍历完整个数组。

这样,当遍历结束时,nums 中前 k 个元素就是我们要求的答案,且 k 就是答案的长度。

169. 多数元素

题目描述

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:

输入:nums = [3,2,3]
输出:3

示例 2:

输入:nums = [2,2,1,1,1,2,2]
输出:2

提示:

  • n == nums.length
  • 1 <= n <= 5 * 104
  • -109 <= nums[i] <= 109

进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。

解法1
class Solution {
    public int removeDuplicates(int[] nums) {Map<Integer, Long> map = Arrays.stream(nums).boxed().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
                                             //将nums转换为流,通过groupingby将流中的元素分组,并统计每个元素的个数。
      long limit = nums.length >> 1;
      for (Map.Entry<Integer,Long>  entry : map.entrySet())
          if (entry.getValue()>limit)
              return entry.getKey();
      return -1;
    }
}
解法2

该众数的个数是大于n2的数,那么经过排序后,其中数必定是众数

候选人(cand_num)初始化为 nums[0],票数 count 初始化为 1。
当遇到与 cand_num 相同的数,则票数 count = count + 1,否则票数 count = count - 1
当票数 count 为 0 时,更换候选人,并将票数 count 重置为 1。
遍历完数组后,cand_num 即为最终答案。

为何这行得通呢?
投票法是遇到相同的则 票数 + 1,遇到不同的则 票数 - 1。
且“多数元素”的个数 > ⌊ n/2 ⌋,其余元素的个数总和 <= ⌊ n/2 ⌋。
因此“多数元素”的个数 - 其余元素的个数总和 的结果 肯定 >= 1。
这就相当于每个 “多数元素” 和其他元素 两两相互抵消,抵消到最后肯定还剩余 至少1个 “多数元素”。

无论数组是 1 2 1 2 1,亦或是 1 2 2 1 1,总能得到正确的候选人。

class Solution {
    public int majorityElement(int[] nums) {
        int count = 1;
        int cand_num = 0;
        cand_num = nums[0];
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] == cand_num)
                count++;
            else count--;
            if (count == 0) {
                cand_num = nums[i];
                count = 1;
            }

        }
        return cand_num;
    }
}

189. 轮转数组

题目描述

给定一个整数数组 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 <= nums.length <= 10^5
  • -2^31 <= nums[i] <= 2^31 - 1
  • 0 <= k <= 10^5

进阶:

  • 尽可能想出更多的解决方案,至少有 三种 不同的方法可以解决这个问题。
  • 你可以使用空间复杂度为 O(1)原地 算法解决这个问题吗?

解法1

观察对比转移前后的数组,分段复制,最后复制回原数组

class Solution {
    public void rotate(int[] nums, int k) {
        k = k % nums.length;
        int [] nums1 = new int[nums.length];
        for (int i = 0; i < k; i++){
            nums1[i] = nums[nums.length-k+i];
        }
        for (int i = k; i < nums.length; i++){
            nums1[i] = nums[i-k];
        }
        for (int i = 0; i < nums.length; i++){
            nums[i] = nums1[i];
        }
        //new nums_show().nums_print(nums);
    }
}

改进代码

class Solution {
    public void rotate(int[] nums, int k) {
        int [] temp = nums.clone();
        for (int i = 0; i < nums.length; i++){
            nums[(i+k)%nums.length] = temp[i];
        }
    }
}

解法2

解法3

方法三:数组翻转

该方法基于如下的事实:当我们将数组的元素向右移动 k 次后,尾部 k mod n 个元素会移动至数组头部,其余元素向后移动 k mod n 个位置。

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

我们以 n=7,k=3 为例进行如下展示:
操作 结果
原始数组 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) {
        int start = 0;
        k = k % nums.length;
        int end = nums.length - 1;
        reverse(nums,start,end);

        reverse(nums,start,k-1);
        reverse(nums,k,end);
        //new nums_show().nums_print(nums);
    }
    public void reverse(int[] nums, int start, int end){
        while (start < end){
            swap(nums,start,end);
            start++;
            end--;
        }
    }
    public void swap(int[] nums, int i, int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

121. 买卖股票的最佳时机

给定一个数组 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。

提示:

  • 1 <= prices.length <= 10^5
  • 0 <= prices[i] <= 10^4

解法1 遍历(超时)

class Solution {
    public int maxProfit(int[] prices) {
        int profile = 0;

        for (int i = 0; i< prices.length;i++) {
            for (int j = i + 1; j < prices.length; j++) {
                if (prices[j] - prices[i] > profile)
                    profile = prices[j] - prices[i];
            }
        }
        return profile;
    }
}

解法2

一次遍历

image-20250227111400055

我们来假设自己来购买股票。随着时间的推移,每天我们都可以选择出售股票与否。那么,假设在第 i 天,如果我们要在今天卖股票,那么我们能赚多少钱呢?

显然,如果我们真的在买卖股票,我们肯定会想:如果我是在历史最低点买的股票就好了!太好了,在题目中,我们只要用一个变量记录一个历史最低价格 minprice,我们就可以假设自己的股票是在那天买的。那么我们在第 i 天卖出股票能得到的利润就是 prices[i] - minprice

因此,我们只需要遍历价格数组一遍,记录历史最低点,然后在每一天考虑这么一个问题:如果我是在历史最低点买进的,那么我今天卖出能赚多少钱?当考虑完所有天数之时,我们就得到了最好的答案。

class Solution {
    public int maxProfit(int[] prices) {
        int min,profile = 0;
        min = prices[0];
        for (int i = 1; i < prices.length; i++){
            if (prices[i] < min){
                min = prices[i];
            }
            if (profile < prices[i] - min){
                profile = prices[i] - min;
            }
        }
        return profile;
    }
}

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

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润

示例 1:

输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3。
最大总利润为 4 + 3 = 7 。

示例 2:

输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4。
最大总利润为 4 。

示例 3:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0。

提示:

  • 1 <= prices.length <= 3 * 104
  • 0 <= prices[i] <= 104

解法

该最大利润,等价于每次上涨都进行买入,然后卖出

class Solution {
    public int maxProfit(int[] prices) {
        int maxprofile = 0;
        for (int i = 0; i < prices.length-1; i++){
            if (prices[i] < prices[i+1]){
                maxprofile += prices[i+1] - prices[i];
            }
        }
        return maxprofile;
    }
}

55. 跳跃游戏

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

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

示例 1:

输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

示例 2:

输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。

提示:

  • 1 <= nums.length <= 10^4
  • 0 <= nums[i] <= 10^5

解法1

我们可以用贪心的方法解决这个问题。

设想一下,对于数组中的任意一个位置 y,我们如何判断它是否可以到达?根据题目的描述,只要存在一个位置 x,它本身可以到达,并且它跳跃的最大长度为 x+nums[x],这个值大于等于 y,即 x+nums[x]≥y,那么位置 y 也可以到达。

换句话说,对于每一个可以到达的位置 x,它使得 x+1,x+2,⋯,x+nums[x] 这些连续的位置都可以到达。

这样以来,我们依次遍历数组中的每一个位置,并实时维护 最远可以到达的位置。对于当前遍历到的位置 x,如果它在 最远可以到达的位置 的范围内,那么我们就可以从起点通过若干次跳跃到达该位置,因此我们可以用 x+nums[x] 更新 最远可以到达的位置。

在遍历的过程中,如果 最远可以到达的位置 大于等于数组中的最后一个位置,那就说明最后一个位置可达,我们就可以直接返回 True 作为答案。反之,如果在遍历结束后,最后一个位置仍然不可达,我们就返回 False 作为答案。

class Solution {
    public boolean canJump(int[] nums) {
        int max = 0;
        for (int i = 0; i < nums.length; i++){
            if(i <= max) { // 判断是否可以到达当前位置
                max = Math.max(max, i + nums[i]);
                if (max >= nums.length - 1)
                    return true;
            }
        }
        return false;
    }
}

换种思路

姜圣担心大家不会做,于是降下了ta的姜圣之力造福大家: 全程只需要使用姜圣的力量定义一个变量,命名为jumping_power; 每前进一步需要消耗一个跳跃力(姜圣力); 而到达一个格子后, 如果当前格子储存的跳跃力较大,则更新为较大的跳跃力; 如果当前格子跳跃力小于储存,则无需更新; 如果出现跳跃力正好消耗完,且此时到达的格子也为0,那么就意味着跳跃过程将无法继续,返回false;

bool canJump(vector<int>& nums) {
    int jumping_power = 1;
    nums[nums.size()-1] = 1;
    for (int x:nums){
        jumping_power--;
        jumping_power = max(x,jumping_power);
        if (jumping_power==0) return false;
    }
    return true;
}

解法2

逆序遍历,设置一个步长,若当前值大于步长,说明从此处到终点可达,否则,不可达,步长加1,进行下次循环。

class Solution {
    public boolean canJump(int[] nums) {
        int step = 1;
        for (int i = nums.length -2; i >= 0; i--){
            if (nums[i] >= step){
                step = 1;
            }
            else {
                step++;
            }
        }
        return step == 1;
    }
}

本文作者:dr4w

本文链接:https://www.cnblogs.com/zMeedA/p/18741411

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   dr4w  阅读(3)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起