leetcode经典面试150题
数组/字符串
删除有序数组中的重复项2
题目
给你一个有序数组 nums ,请你原地删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
并需要返回数组的长度
接口
class Solution { public int removeDuplicates(int[] nums) { }}思路
注意提供给我们的数组就是有序的,说明相同的元素是出现在一起的 为了让解法更具有一般性,我们将原问题的「保留 2 位」修改为「保留 k 位」。 由于是保留 k 个相同数字,对于前 k 个数字,我们可以直接保留 对于后面的任意数字,能够保留的前提是:与当前写入的位置前面的第 k 个元素进行比较,不相同则保留——这就是全题目的重点,如果与其前面的第k个数相同,就说明这一个数字出现超过了k个,新读取的数并不能被保留到数组中
解法
遇到操作数组的题目,而且是要删除/移动数组内容的情况,一般来说会使用双指针法。这里也是一样,先设置一个慢指针slow = k(slow指针之前的内容都是有效的数,已经被录入了最终返回的数组,初始时有0~k-1一共k个元素),然后设置快指针fast = k(fast指针每次循环都会向后移动一位,进行数组遍历)
在进行遍历的过程中进行思路中的操作——与当前写入的位置前面的第 k 个元素进行比较:
如果不一样就保留:保留的操作就是将fast指针遍历到的那个数移动到slow指针的位置(即新遍历到的数加入数组),然后由于有效的数多了一个,slow指针需要向后移动一位,即nums[slow] = nums[fast];slow++; 如果一样就舍去:舍去就是不管当前fast遍历到的元素,直接忽略不做操作 最后都需要将fast指针后移一位继续遍历
双指针的代码如下:
class Solution { public int removeDuplicates(int[] nums) { int k = 2; if(nums.length <= k) return nums.length; //初始化双指针 int slow = k,fast = k;//第k+1个数 while(fast < nums.length){ if(nums[fast] != nums[slow-k]){//没有超过k个数 nums[slow] = nums[fast]; slow++; } fast++; } return slow; }}在题解中看到宫水三叶大佬的代码是:
class Solution { public int removeDuplicates(int[] nums) { return process(nums, 2); } int process(int[] nums, int k) { int u = 0; for (int x : nums) {//遍历数组 //如果u < k,代表慢指针(有效的数)比k小,不可能有数被遗弃,所以需要将新遍历到的数纳入有效数组 //如果nums[u - k] != x,代表快指针遍历到的数并不是他前面第k个数,表示并没有超出限额,所以也将新遍历到的数纳入分组 //nums[u++] = x,表示将当前遍历到的数x放入到当前慢指针u的位置,并把慢指针u向后移动一位,即将新遍历到的数纳入分组的代码实现 if (u < k || nums[u - k] != x) nums[u++] = x; } return u; }}这个代码更加简洁,但是方法还是一样的,并不要求掌握,只是学习下别人的代码风格
多数元素
题目
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例
输入:nums = [3,2,3]输出:3接口
class Solution { public int majorityElement(int[] nums) { }}思路
一开始就想到了可以建立一个映射,将每一个数出现的次数用一个HashMap存储下来,每次遍历的时候顺便看下刚刚添加进数的那个HashMap的value是否超过了n/2,我想这就是最简单、直接的思路了,顺便可以复习一下HashMap的语法: Map.put(key,value) Map.get(key) Map.getOrDefault(key,defaultNum) 我的代码如下:
import java.util.HashMap;import java.util.Map;
class Solution { public int majorityElement(int[] nums) { Map<Integer, Integer> countMap = new HashMap<>(); int majorityThreshold = nums.length / 2;
for (int num : nums) { countMap.put(num, countMap.getOrDefault(num, 0) + 1); if (countMap.get(num) > majorityThreshold) { return num; } } // 由于题目保证存在多数元素,这里不需要返回其他值 return -1; // 这行代码实际上不会执行 }}进阶
后面我看到了一行进阶挑战:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
进阶思路
摩尔投票法
所以,如果遍历的过程中当前数组段的vote为0的话,就说明这段数组及以前之前还没有分出胜负,所以在未遍历的数组中,vote肯定是要>0的
而且,因为题目说了一定存在多数元素,所以其实每一段到底是谁赢了并不重要,因为只要多数元素赢的比重大就可以了——如果不是多数元素赢的比重大,那么就会发现并不存在>n/2次出现的元素,不如说右上图中如果数组是:
2213/3354/2那么就不存在多数元素
进阶代码
class Solution { public int majorityElement(int[] nums) { int x = 0, votes = 0; for (int num : nums){ if (votes == 0) x = num; votes += num == x ? 1 : -1; } return x; }}轮转数组
题目
给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。默认读取的是nums数组
接口
class Solution { public void rotate(int[] nums, int k) { }}思路
若k比数组长度长,则将k转化为一个周期内的数 没有越界的就向右移动k位 如果越界,那么就用mod操作判定移动到哪里
代码
class Solution { public void rotate(int[] nums, int k) { k = k % nums.length; int[] ret = new int[nums.length]; for(int i = 0;i < nums.length;i++){ ret[((i+k)%nums.length)] = nums[i]; } for(int i = 0;i < nums.length;i++){ nums[i] = ret[i]; } }}进阶
你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗
进阶思路
如下图所示,我们可以这样来理解向右移动k位:
后k个元素变成了前k个元素——去反转整个数组
但是前、后两段内部在整个数组反转时候也被反转了——那么重新再反转前k个和后n-k个元素

进阶代码
class Solution { public void rotate(int[] nums, int k) { int n = nums.length; k %= n; // 轮转 k 次等于轮转 k%n 次 reverse(nums, 0, n - 1); reverse(nums, 0, k - 1); reverse(nums, k, n - 1); }
private void reverse(int[] nums, int i, int j) { while (i < j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; i++; j--; } }}