详解数组刷题上
- 2019 年 10 月 6 日
- 筆記
详解数组刷题上
本专题内容如下:
一、初始定义及原地修改1.283. 移动零2.27. 移除元素3.26. 删除排序数组中的重复项4.80. 删除排序数组中的重复项 II二、基础思想应用1.75. 颜色分类2.88. 合并两个有序数组3.215. 数组中的第K个最大元素4.167. 两数之和 II – 输入有序数组5.209. 长度最小的子数组
一、初始定义及原地修改
类似题目:
- 283. 移动零
- 27. 移除元素
- 26. 删除排序数组中的重复项
注意的问题
- 如何定义变量?
- 如何从数组中删除?
- 剩余元素的排列是否要保证原有的相对顺序?
- 是否有空间复杂度的要求? O(1)
1.283. 移动零
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12] 输出: [1,3,12,0,0]
说明:
- 必须在原数组上操作,不能拷贝额外的数组。
- 尽量减少操作次数。
实现思路:
- 拿出非0元素
- 将非0元素拿出来,然后空位补0
实现:
循环一次找出不为0的index,并将该Index对应的值依次存储到数组中,后面的全部为0即可!
class Solution { public void moveZeroes(int[] nums) { int j=0; for(int i=0;i<nums.length;i++){ if(nums[i]!=0){ nums[j++]=nums[i]; } } //剩余位置放0 while(j<nums.length){ nums[j++]=0; } } }
2.27. 移除元素
给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3, 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 你不需要考虑数组中超出新长度后面的元素。
实现思路:
找到不等目标值的元素,然后利用不等目标值的个数原地修改数组。
实现:
class Solution { public int removeElement(int[] nums, int val) { int i,j=0; for(i=0;i<nums.length;i++){ if(nums[i]!=val){ nums[j++]=nums[i]; } } return j; } }
3.26. 删除排序数组中的重复项
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2], 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 你不需要考虑数组中超出新长度后面的元素。
实现思路:
找到当前元素与前一元素不等的位置,开始原地修改数组。
实现:
class Solution { public int removeDuplicates(int[] nums) { int i=1,j=0; while(i<nums.length){ if (nums[i]!=nums[i-1]){ nums[++j]=nums[i]; } i++; } return j+1; } }
4.80. 删除排序数组中的重复项 II
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定 nums = [1,1,1,2,2,3], 函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。 你不需要考虑数组中超出新长度后面的元素。
实现思路:
1 2 cur back 1 2 2 pre cur back
当数组中的某一部分满足上述两者之一的时候,就可以移动cur(cur记录的就是该数组的实际大小)。
实现:
class Solution { public int removeDuplicates(int[] nums) { int cur,pre,back; cur=1; pre=cur-1; back=cur+1; while(back<nums.length){ if(nums[cur]!=nums[back]||(nums[cur]==nums[back] && nums[cur]!=nums[pre])){ pre=cur; nums[cur+1]=nums[back]; cur++; } back++; } return cur+1; } }
前面两个元素不修改,直到到第3个元素,也就是index=2开始比较。使用j来存储满足题意的位置,让nums[i]与nums[j-2]
比较,然后赋值即可。
class Solution { public int removeDuplicates(int[] nums) { int i=2,j=2; while(i<nums.length){ if(nums[i]>nums[j-2]){ //这里一定是j-2而不是i-2,如果是i-2会在下面一行代码执行过程中将原来的值覆盖掉,此时结果就不对了,而如果是j-2,就是正确的,因为j中存放的就是未覆盖的值,如果所有元素都是不一样的,那么i与j的指向一致,而当元素并不一样的时候,j-2中就存放了i-2实际元素! nums[j++]=nums[i]; } i++; } return j; } }
二、基础思想应用
基本思想:
- 快速排序
- 三路快排
- 二分查找
- 基数排序
类似题目:
- 75. 颜色分类
- 88. 合并两个有序数组
- 215. 数组中的第K个最大元素
- 167. 两数之和 II – 输入有序数组
- 209. 长度最小的子数组
二分查找法模板:
int l = 0, r = n-1; // 在[l...r]的范围里寻找target:前闭后闭 while( l <= r ){ // 只要还有可以查找的内容。当 l == r时,区间[l...r]依然是有效的 int mid = l + (r-l)/2; if( arr[mid] == target ) return mid; //mid已经判断过了 if( target > arr[mid] ) l = mid + 1; // target在[mid+1...r]中; [l...mid]一定没有target else // target < arr[mid] r = mid - 1; // target在[l...mid-1]中; [mid...r]一定没有target }
注意的问题
- 求mid值是采用(l+r)/2容易整形溢出
- 采用mid = l + (r-l)/2;
1.75. 颜色分类
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意: 不能使用代码库中的排序函数来解决这道题。
示例:
输入: [2,0,2,1,1,0] 输出: [0,0,1,1,2,2]
实现思路:
统计一次红、白、黑的数量,然后再循环一次原地修改!
实现:
class Solution { public void sortColors(int[] nums) { int red=0,white=0,blue=0; for(int i=0;i<nums.length;i++){ switch (nums[i]){ case 0: red++; break; case 1: white++; break; default: blue++; } } for(int i=0;i<nums.length;i++){ if (i<red){ nums[i]=0; }else if(i<red+white){ nums[i]=1; }else{ nums[i]=2; } } } }
三路快排
class Solution { public void sortColors(int[] nums) { int red_white = -1; int white_blue= nums.length; int i=0; int temp; while(i<white_blue){ if(nums[i]==0){ red_white++; temp=nums[red_white]; nums[red_white]=nums[i]; nums[i]=temp; i++; }else if(nums[i]==2){ white_blue--; temp=nums[white_blue]; nums[white_blue]=nums[i]; nums[i]=temp; }else{ i++; } } } }
2.88. 合并两个有序数组
给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
说明:
- 初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
- 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入: nums1 = [1,2,3,0,0,0], m = 3 nums2 = [2,5,6], n = 3 输出: [1,2,2,3,5,6]
实现思路:
从后往前遍历,然后去比较两数组大小,谁大先放谁。
实现:
class Solution { public void merge(int[] nums1, int m, int[] nums2, int n) { int n1=m-1,n2=n-1; for(int i=m+n-1;i>=0;i--){ if((n1>=0 && n2>=0 && nums1[n1]>=nums2[n2]) || (n2<0 && n1>=0)){ nums1[i]=nums1[n1]; n1--; }else if((n1>=0 && n2>=0 && nums1[n1]<nums2[n2]) || (n1<0 && n2>=0)){ nums1[i]=nums2[n2]; n2--; } } } }
3.215. 数组中的第K个最大元素
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2 输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4 输出: 4
说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
实现思路:
直接使用冒泡排序,当冒出k次后,此时的元素就是第K个最大元素。
实现:
class Solution { public int findKthLargest(int[] nums, int k) { int i,j; int n=nums.length; int temp; for (i=0;i<n-1;i++){ for (j=i+1;j<n;j++){ if (nums[i]<nums[j]) { temp=nums[i]; nums[i]=nums[j]; nums[j]=temp; } } k--; if(k==0){ break; } } return nums[i]; } }
交换多次快排
class Solution { public int findKthLargest(int[] nums, int k) { quickSort(nums,0,nums.length-1); return nums[k-1]; } public void quickSort(int[] nums,int left,int right){ if(left>right){ return; } int pivot = nums[left]; int i = left; int j = right; int temp; while(i<j){ while(i<j && nums[j]<=pivot) j--; if(i<j){ temp=nums[i]; nums[i]=nums[j]; nums[j]=temp; } while(i<j && nums[i]>=pivot) i++; if (i<j){ temp=nums[i]; nums[i]=nums[j]; nums[j]=temp; } } //nums[left]=nums[i]; 这种方法这行不能要! nums[i]=pivot; quickSort(nums,left,i-1); quickSort(nums,i+1,right); } }
最后交换快排
class Solution { public int findKthLargest(int[] nums, int k) { quickSort(nums,0,nums.length-1); return nums[k-1]; } public void quickSort(int[] nums,int left,int right){ if(left>right){ return; } int pivot = nums[left]; int i = left; int j = right; int temp; while(i<j){ while(i<j && nums[j]<=pivot) j--; while(i<j && nums[i]>=pivot) i++; if (i<j){ temp=nums[i]; nums[i]=nums[j]; nums[j]=temp; } } nums[left]=nums[i]; //必须要! nums[i]=pivot; quickSort(nums,left,i-1); quickSort(nums,i+1,right); } }
4.167. 两数之和 II – 输入有序数组
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
- 返回的下标值(index1 和 index2)不是从零开始的。
- 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9 输出: [1,2] 解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
实现思路:
二分查找法
实现:
注意二分查找的代码中左右可以相等!
class Solution { public int[] twoSum(int[] numbers, int target) { int[] res=new int[2]; for(int i=0;i<numbers.length;i++){ int second = binarySearch(numbers, i+1, numbers.length-1, target-numbers[i]); if(second>-1){ res[0]=i+1; res[1]=second+1; } } return res; } public int binarySearch(int[] numbers, int start, int end, int target){ int i=start; int j=end; while(i<=j){ int mid=(i+j)/2; if(numbers[mid]== target){ return mid; }else if(numbers[mid]> target){ j=mid-1; }else{ i=mid+1; } } return -1; } }
5.209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。
示例:
输入: s = 7, nums = [2,3,1,2,4,3] 输出: 2 解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。
进阶:
如果你已经完成了O(n) 时间复杂度的解法, 请尝试 O(n log n) 时间复杂度的解法。
实现思路:
循环+二分
实现:
注意二分查找的代码中左右可以相等!
时间复杂度:O(nlogn),空间复杂度O(n)。
思路:
将原先数组所有数字累加求和得到一个新数组,例如:
nums=[2,3,1,2,4,3] parNums=[0,2,5,6,8,12,15]
然后循环parNums,对每一个数组中的index对应的值,利用二分法找到最小的窗口。
举个例子:
nums=[2,3,1,2,4,3] parNums=[0,2,5,6,8,12,15] -------------- i=0时 -------------- 第一轮 left=0,right=6 left<right,计算出mid=3,此时对应的值为6,mid距离i的和也就是6<7, 调整left=mid+1=4。 第二轮 left=4,right=6 left<right,计算出mid=5,此时对应的值为12,mid距离i的和也就是12>7, 调整right=mid-1。 是不是上述可以看作是查找7的二分法,那么后面依次类推即可。 当然left调整也可以是left++,right调整也可以是right--,也可以AC,但是效率会低一些! -------------- ......
循环+二分
public class Solution { public int minSubArrayLen(int s, int[] nums) { int[] parNums = new int[nums.length+1]; for(int i=1;i<parNums.length;i++){ parNums[i]=parNums[i-1]+nums[i-1]; } int min=0; for(int i=0;i<parNums.length;i++){ int left=i; int right=parNums.length-1; while(left<=right){ int mid=left+(right-left)/2; int subSum=parNums[mid]-parNums[i]; if(subSum>=s){ right=mid-1; //可以换成right--; min=min==0?mid-i:Math.min(min,mid-i); }else{ left=mid+1; //可以换成left++; } } } return min; } }