数组
1、数组的认识
1.加一步了解了数组,在Java中,数组不能删除,所谓的删除只是有一个封装好的方法覆盖。同时,在操作数组的时候,我们对当前产生的数组的地址无法删除,只能覆盖,例如:假设int[] a = new int [5]; 在这个代码中,你已经产生了了五个地址,但是你世纪用到的只有三个或者四个,打印出来的地址也是三四个,但是在计算机内存中,产生的依旧是五个地址,对此要理解到位
2、二分法的使用
704. 二分查找
难度简单1021收藏分享切换为英文接收动态反馈
给定一个 n
个元素有序的(升序)整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1
。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
解法:在二分法中,我们必须清除的知道界限的使用,对于本题,我们有两种解法,
其一:
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length;
while(left < right){
int mid = left + ((right - left)/2);
if(nums[mid] > target){
right = mid;
}else if(nums[mid] < target){
left = mid + 1;
}else if(nums[mid] == target){
return mid;
}
}
return -1;
}
//这是通过定义一个左闭右开的区间进行解题,也是较为优解的,通过不断比较中间值,来重新定义左右边界值,然后遍历,找到我们所需要的值
解法二:
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length -1;
while(left <= right){
int mid = left + ((right - left) >> 1);
if(nums[mid] > target){
right = mid - 1;
}else if(nums[mid] < target){
left = mid + 1;
}else if(nums[mid] == target){
return mid;
}
}
return -1;
}
//这是一个左闭右必的区间,我们要清除定义对所取区间的范围,然后才能针对解题
35. 搜索插入位置
难度简单1753收藏分享切换为英文接收动态反馈
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n)
的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length;
while(left < right){
int mid = left + ((right - left)/2);
if(nums[mid] > target){
right = mid;
}else if(nums[mid] < target){
left = mid + 1;
}else if(nums[mid] == target){
return mid;
}
}
return -1;
}
//本题中,我们要自己构造出一个区间,然后进行遍历,找到答案
34. 在排序数组中查找元素的第一个和最后一个位置
难度中等1963收藏分享切换为英文接收动态反馈
给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target
,返回 [-1, -1]
。
你必须设计并实现时间复杂度为 O(log n)
的算法解决此问题。
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
public int[] searchRange(int[] nums, int target) {
int index = binarySeach(nums,target);
if(index==-1){
return new int[]{-1,-1};
}
int left =index;
int right = index;
while(left-1>=0 && nums[left-1]==nums[index]){//向左滑动,找左边界
left--;
}
while(right+1<nums.length && nums[right+1]==nums[index]){//向右滑动,找右边界
right++;
}
return new int[]{left,right};
}
public int binarySeach(int[] nums, int target) {
int left=0,right=nums.length;
while(left<right){
int mid=left+(right-left)/2;//找到一个中位数
if(nums[mid]==target){
return mid;
}else if(nums[mid]>target){
right=mid;
}else if(nums[mid]<target){
left=mid+1;
}
}
return -1;
}
//通过构造一个方法,用二分法找出它是否存在于数组中,如果存在,则继续通过滑动,分别向左右滑动找到我们所需要的边界,知道我们找到答案,则返回一个新的数组,不存在则返回[-1,-1],
69. x 的平方根
难度简单1172收藏分享切换为英文接收动态反馈
给你一个非负整数 x
,计算并返回 x
的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
**注意:**不允许使用任何内置指数函数和算符,例如 pow(x, 0.5)
或者 x ** 0.5
。
示例 1:
输入:x = 4
输出:2
示例 2:
输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
public int mySqrt(int x) {
int left =1;
int ritht = x;
int middle = 0;
while( ritht >= left){
middle = left + (ritht-left)/2;
int temp = x / middle;
if(temp > middle){
left = middle +1;
}else if(temp<middle){
ritht = middle-1;
}else if(temp == middle){
return middle;
}
}
return ritht;
}
367. 有效的完全平方数
难度简单447收藏分享切换为英文接收动态反馈
给定一个 正整数 num
,编写一个函数,如果 num
是一个完全平方数,则返回 true
,否则返回 false
。
进阶:不要 使用任何内置的库函数,如 sqrt
。
示例 1:
输入:num = 16
输出:true
示例 2:
输入:num = 14
输出:false
public boolean isPerfectSquare(int num) {
int left = 1;
int rigtht = num;
while( left <= rigtht){
int middle = left + (rigtht - left) / 2;
int temp = num/middle;
if( temp == middle){
if(num % middle ==0){//必须判定一些特例
return true;
}
left = middle+1;//左右边界都行
}else if( middle > temp){
rigtht = middle -1;
}else if( middle < temp){
left = middle + 1;
}
}
return false;
}
3、移除元素
27. 移除元素
难度简单1537收藏分享切换为英文接收动态反馈
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以**「引用」**方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
示例 1:
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
示例 2:
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
public int removeElement(int[] nums, int val) {
int leftIndex=0;
int rightIndex=nums.length-1;
while(rightIndex >= 0 && nums[rightIndex]==val) rightIndex--;//滑动到一个不等于var值的位置
while(leftIndex<=rightIndex){
if(nums[leftIndex]==val){
nums[leftIndex]=nums[rightIndex];
rightIndex--;
}
leftIndex++;
while(rightIndex >= 0 && nums[rightIndex]==val) rightIndex--;//更新数组
}
return leftIndex;
}
//双指针法:将右边指针的的值覆盖掉左边指针的值,当遇到等于var值的情况,需要向前滑动一位,达成覆盖的效果
public int removeElement(int[] nums, int val) {
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.length; fastIndex++) {
if (nums[fastIndex] != val) {
nums[slowIndex] = nums[fastIndex];
slowIndex++;
}
}
return slowIndex;
}
// 快慢指针
26. 删除有序数组中的重复项
难度简单2890收藏分享切换为英文接收动态反馈
给你一个 升序排列 的数组 nums
,请你** 原地** 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。
由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k
个元素,那么 nums
的前 k
个元素应该保存最终结果。
将最终结果插入 nums
的前 k
个位置后返回 k
。
不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
判题标准:
系统会用下面的代码来测试你的题解:
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 。不需要考虑数组中超出新长度后面的元素。
public int removeDuplicates(int[] nums) {
int left = 0;
int right = 1;
while(right < nums.length){
if(nums[left] == nums[right]){
right++;
}else{
left++;
nums[left] = nums[right];
right++;
}
}
return left+1;
}
283. 移动零
难度简单1769收藏分享切换为英文接收动态反馈
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
public void moveZeroes(int[] nums) {
int left = 0;
for(int right = 0;right<nums.length;right++){
if(nums[right] != 0){
nums[left] = nums[right];
left++;
}
}
for(int right =left; right<nums.length ;right++){
nums[right] =0;
}
}
4、有序数组的平方
977. 有序数组的平方
难度简单648收藏分享切换为英文接收动态反馈
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
示例 2:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]
public int[] sortedSquares(int[] nums) {
int left=0;
int right=nums.length-1;
int[] resule = new int[nums.length];
int index = resule.length-1;
while(left<=right){
if(nums[left]*nums[left]<nums[right]*nums[right]){
resule[index--]=nums[right]*nums[right--];
}else{
resule[index--]=nums[left]*nums[left++];
}
}
return resule;
}
//双指针法:
5、长度最小的子数组
209. 长度最小的子数组
难度中等1420收藏分享切换为英文接收动态反馈
给定一个含有 n
个正整数的数组和一个正整数 target
。
找出该数组中满足其和 ≥ target
的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度**。**如果不存在符合条件的子数组,返回 0
。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target = 4, nums = [1,4,4]
输出:1
示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
public int minSubArrayLen(int target, int[] nums) {
int temp=0;
int left=0;
int sum=0;
int result = Integer.MAX_VALUE;
for(int right=0;right<nums.length;right++){
sum +=nums[right];
while(sum>=target){
int res = right-left+1;
sum -= nums[left];
left++;
result = result > res ? res : result;
}
}
return result == Integer.MAX_VALUE ? 0 :result;
}
//通过滑动窗口,即通过一个窗口,比较窗口内的值是否等于我们所需要的值,然后进行滑动
904. 水果成篮
难度中等372收藏分享切换为英文接收动态反馈
你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits
表示,其中 fruits[i]
是第 i
棵树上的水果 种类 。
你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:
- 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
- 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
- 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits
,返回你可以收集的水果的 最大 数目。
示例 1:
输入:fruits = [1,2,1]
输出:3
解释:可以采摘全部 3 棵树。
示例 2:
输入:fruits = [0,1,2,2]
输出:3
解释:可以采摘 [1,2,2] 这三棵树。
如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。
示例 3:
输入:fruits = [1,2,3,2,2]
输出:4
解释:可以采摘 [2,3,2,2] 这四棵树。
如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。
示例 4:
输入:fruits = [3,3,3,1,2,1,1,2,3,3,4]
输出:5
解释:可以采摘 [1,2,1,1,2] 这五棵树。
public int totalFruit(int[] fruits) {
int left = 0;
int ans = 0;//开始为0
int maxLen = 0;
int[] count = new int[fruits.length];
for(int right = 0; right<fruits.length; right++ ){
if(count[fruits[right]]==0){
ans++;
}
count[fruits[right]]++;
while(ans>2){
count[fruits[left]]--;
if(count[fruits[left]]==0){
ans--;
}
left++;
}
maxLen = Math.max(maxLen,right-left+1);
}
return maxLen;
}
//同样也是用滑动窗口的方法进行处理,在窗口中出现ans>2的情况下,进行处理
6、螺旋矩阵
59. 螺旋矩阵 II
难度中等848收藏分享切换为英文接收动态反馈
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
示例 1:
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
示例 2:
输入:n = 1
输出:[[1]]
public int[][] generateMatrix(int n) {
int[][] res = new int[n][n];
int i,j;
int start = 0;
int count = 1;
int loop = 0;//控制循环次数
while(loop++ < n/2){
//上面从左往右
for(j=start;j<n-loop;j++){
res[start][j] = count++;
}
//右边从上往下
for(i=start;i<n-loop;i++){
res[i][j] = count++;
}
//下面从右往左
for(;j>=loop;j--){
res[i][j] = count++;
}
//左边从下往上
for(; i>=loop ; i--){
res[i][j] = count++;
}
start++;
}
//如果为奇数,为中间赋值
if(n % 2 == 1){
res[start][start] = count;
}
return res;
}
//分别处理数据的四个方向上的数据,并对中间的数据进心处理,但于此同时,但记得控制好边界范围
54. 螺旋矩阵
难度中等1240收藏分享切换为英文接收动态反馈
给你一个 m
行 n
列的矩阵 matrix
,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
public List<Integer> spiralOrder(int[][] matrix) {
if(matrix == null || matrix.length == 0|| matrix[0].length==0 ){
return new LinkedList<>();
}
int m = matrix.length, n = matrix[0].length;
int up = 0, down = m-1, left = 0, right = n-1;
List<Integer> list = new LinkedList<>();
while(left<=right && up<=down){
//上面遍历
for(int i = left;i<=right;i++){
list.add(matrix[up][i]);
}
up++;
//右边遍历
for(int i = up;i<=down;i++){
list.add(matrix[i][right]);
}
right--;
//下面遍历
for(int i = right;i >=left && down >= up;i--){
list.add(matrix[down][i]);
}
down--;
//左边遍历
for(int i = down;i>=up && right >= left ;i--){
list.add(matrix[i][left]);
}
left++;
}
return list;
}
//利用链表,对数据进行处理,但是记得,在下面以及左边的遍历的时候,为了防止数组重新打印,必须加上第二层条件,佛则数据会重复打印