数组类题目
双指针
(26)删除数组中的重复项-简单
题目
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
思路
-
暴力解法
遇见重复的直接将后面的元素向前移
- 时间复杂度O(n^2)
代码
class Solution {
public int removeDuplicates(int[] nums) {
int len2 = nums.length;//新数组长度
for(int i = 0; i < len2; i++){
int j = i;
//如果后面有相同元素则向后移动到相同元素最后一个
while(j < len2 - 1 && nums[j] == nums[j+1]) j++;
//把相同元素最后一个后面的数据移动到该数后面,并更新长度
len2 -= move(nums, len2, j+1, i+1);
}
return len2;
}
//将数组fromIndex及后面的元素移动到toIndex处,返回移动长度
public static int move(int[] nums, int newLen, int fromIndex, int toIndex){
if(fromIndex <= toIndex) return 0;
if(fromIndex >= newLen) return fromIndex - toIndex;
for(int i = fromIndex, j = toIndex; i < newLen; i++, j++){
nums[j] = nums[i];
}
return fromIndex - toIndex;
}
}
2.双指针
考虑用 2 个指针,一个在前记作 p,一个在后记作 q,算法流程如下:
比较 p 和 q 位置的元素是否相等。
如果相等,q 后移 1 位
如果不相等,将 q 位置的元素复制到 p+1 位置上,p 后移一位,q 后移 1 位
重复上述过程,直到 q 等于数组长度。
- 时间复杂度:O(n)
代码
class Solution {
public int removeDuplicates(int[] nums) {
int p = 0;
for(int q = 1; q < nums.length; q++){
if(nums[q] != nums[p]){
nums[p+1] = nums[q];
p++;
}
}
return p+1;
}
}
(27)移除元素-简单
题目
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
思路
- 双指针
考虑用 2 个指针,一个在前记作 p,一个在后记作 q,算法流程如下:
1.比较 q 位置的元素是否等于val。
如果相等,q 后移 1 位
如果不相等,将 q 位置的元素复制到 p 位置上,p 后移一位,q 后移 1 位
重复上述过程,直到 q 等于数组长度。
- 时间复杂度:O(n)
代码
class Solution {
public int removeElement(int[] nums, int val) {
int p = 0;
for(int q = 0; q < nums.length; q++){
if(nums[q] != val){
nums[p] = nums[q];
p++;
}
}
return p;
}
}
二分法
(33)搜索旋转排序数组-中等
题目
升序排列的整数数组 nums 在预先未知的某个点上进行了旋转(例如, [0,1,2,4,5,6,7] 经旋转后可能变为 [4,5,6,7,0,1,2] )。
请你在数组中搜索 target ,如果数组中存在这个目标值,则返回它的索引,否则返回 -1
思路
从中间划分必然有一边为有序数组,如果nums[left] <= nums[right]则说明从left到right这一段肯定是有序数组。
- 时间复杂度:log(n)
代码
class Solution {
public int search(int[] nums, int target) {
if(nums == null || nums.length == 0) return -1;
if(nums[0] == target) return 0;
int len = nums.length;
int l = 0, r = len - 1, mid = 0;
while(l <= r){
mid = (l + r) / 2;
if(nums[mid] == target) return mid;
if(nums[l] <= nums[mid]){
if(target < nums[mid] && target >= nums[l]){
r = mid - 1;
}else{
l = mid + 1;
}
}else{
if(target > nums[mid] && target <= nums[r]){
l = mid + 1;
}else{
r = mid - 1;
}
}
}
return -1;
}
}
(34)在排序数组中查找元素的第一个和最后一个位置-中等
题目
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
思路1
先二分法找到目标值,然后在目标值位置向两边遍历到不等于目标值的位置。
- 时间复杂度:log(n)
代码1
class Solution {
public int[] searchRange(int[] nums, int target) {
if(nums == null || nums.length == 0) return new int[]{-1, -1};
if(nums.length == 1 && nums[0] == target) return new int[]{0, 0};
int len = nums.length;
int left = 0, right = len - 1, mid = 0;
int begin = 0, end = 0;
while(left <= right){
mid = (left + right) / 2;
if(nums[mid] == target){
begin = mid;
end = mid;
for(int i = mid; i >= 0; i--){
if(nums[i] == target) begin = i;
else break;
}
for(int i = mid; i < len; i++){
if(nums[i] == target) end = i;
else break;
}
return new int[]{begin, end};
}else{
if(nums[mid] > target) right = mid - 1;
else left = mid + 1;
}
}
return new int[]{-1, -1};
}
}
思路2
仍然是二分法,优化思路一,直接通过二分法找到大于等于目标值的第一个位置和大于目标值的第一个位置。
- 时间复杂度:log(n)
代码2
class Solution {
//先找>=target的第一个
//再找>target的第一个
public int[] searchRange(int[] nums, int target) {
int l=search(nums,target);
int r=search(nums,target+1);
//没找到>=target或找到了但不是=target的
if(l == -1 || nums[l] != target) return new int[]{-1,-1};
//到这说明前面肯定找到target第一个位置了,此时如果没找到>=target+1的说明数组最大元素为target
if(r == -1) r = nums.length;
return new int[]{l,r-1};
}
//找>=target的第一个的下标,注:并不一定等于target,所有主方法里的变量“ l ”需要验证。
public int search(int[] nums,int target){
int l=0,r=nums.length-1;
int index = -1;
while(l<=r){
int mid=(r+l)/2;
if(nums[mid]>=target){
index = mid;
r=mid - 1;
}
else
l=mid+1;
}
return index;
}
}
其它
(31)下一个排列-中等
题目
实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须 原地 修改,只允许使用额外常数空间
思路
1.理解字典序的含义
理解字典序中下一个排序:给定若干个数字,将其组合为一个整数。如何将这些数字重新排列,以得到下一个更大的整数。
-
我们希望下一个数比当前数大,这样才满足“下一个排列”的定义。因此只需要将后面的「大数」与前面的「小数」交换,就能得到一个更大的数。比如 123456,将 5 和 6 交换就能得到一个更大的数 123465。
-
我们还希望下一个数增加的幅度尽可能的小,这样才满足“下一个排列与当前排列紧邻“的要求。为了满足这个要求,我们需要:
在尽可能靠右的低位进行交换,需要从后向前查找
将一个 尽可能小的「大数」 与前面的「小数」交换。比如 123465,下一个排列应该把 5 和 4 交换而不是把 6 和 4 交换
将「大数」换到前面后,需要将「大数」后面的所有数重置为升序,升序排列就是最小的排列。 -
以 123465 为例:首先按照上一步,交换 5 和 4,得到 123564;然后需要将 5 之后的数重置为升序,得到 123546。显然 123546 比 123564 更小,123546 就是 123465 的下一个排列
-
时间复杂度:O(n^2)
class Solution {
public void nextPermutation(int[] nums) {
int len = nums.length;
int index = 0;
for(int i = len-1; i >= 0; i--){
int flag = 0;
for(int j = i; j < len; j++){
//找到了第一个它后面有比它大的数,由于它是第一个这样的数,所以这个数后面的数
//肯定是降序,所以直接遍历完将后面比它大的数中最小的下标找出来,后面再进行交换。
if(nums[i] < nums[j]){
flag = 1;
index = j;
}
}
if(flag == 1){
int temp = nums[i];
nums[i] = nums[index];
nums[index] = temp;
Arrays.sort(nums, i+1, len);
return ;
}
}
Arrays.sort(nums);
}
}