704. 二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
public class BinarySearch {
public int binarysearch(int[] nums,int target){
int left=0;
int right = nums.length-1;
while(left<=right){
int mid = (left+right)/2;
if(target < nums[mid]){
right = mid-1;
} else if (target > nums[mid]) {
left = mid+1;
} else if (target == nums[mid]) {
return mid;
}
}
return -1;
}
public static void main(String[] args) {
BinarySearch binarysearch = new BinarySearch();
int[] nums = {1, 2, 3, 4, 5, 6, 7, 8};
int target = 5;
int res = binarysearch.binarysearch(nums,target);
System.out.println(res);
}
}
写错了,left = mid;导致程序卡死;
解题思路:
初始化左右指针:
我们定义两个指针,left
指向数组的开头,right
指向数组的末尾,表示当前要查找的区间。
-
计算中间位置:
在每次迭代中,我们计算中间位置mid = (left + right) / 2
,即取当前查找范围的中间元素。 -
比较中间元素与目标值:
- 如果
nums[mid]
等于target
,则说明找到了目标值,返回中间位置mid
。 - 如果
target
小于nums[mid]
,则目标值只可能出现在左半部分,将right
移动到mid - 1
。 - 如果
target
大于nums[mid]
,则目标值只可能出现在右半部分,将left
移动到mid + 1
。
- 如果
-
循环条件:
继续重复上述过程,直到left
超过right
,表示整个数组已经遍历完毕且未找到目标值,此时返回-1
。 -
代码简述:
- 通过
while (left <= right)
循环控制查找范围,逐步缩小查找的区间。 - 每次循环中通过
mid
找到中间元素,进行比较并移动left
或right
,从而实现查找范围的缩小。 - 如果找到目标值则返回其下标,否则返回
-1
。
- 通过
27. 移除元素
题目:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1: 给定 nums = [3,2,2,3], val = 3, 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 你不需要考虑数组中超出新长度后面的元素。
示例 2: 给定 nums = [0,1,2,2,3,0,4,2], val = 2, 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
你不需要考虑数组中超出新长度后面的元素。
public class Delete {
public int deletenum_for(int[] nums, int target){
int size = nums.length;
for(int i=0;i<size;i++){
if(nums[i] == target){
for(int j=i;j<size-1;j++){
nums[j] = nums[j+1];
}
size--;
i--;
}
}
return size;
}
//(版本一)快慢指针法
public int removeElement(int[] nums, int target){
int low = 0;
for(int i=0;i<nums.length;i++){
if(nums[i] != target){
nums[low] = nums[i];
low ++;
}
}
return low;
}
// # 相向双指针法
//# 时间复杂度 O(n)
//# 空间复杂度 O(1)
public int removeElement_two(int[] nums, int target){
int left =0;
int right = nums.length-1;
while(left<=right){
if(nums[left]!=target)
{
left++;
} else if (nums[right]==target) {
right--;
}else {
nums[left] = nums[right];
left++;
right--;
}
}
return left;
}
public static void main(String[] args) {
Delete bs = new Delete();
int[] nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
System.out.println(bs.removeElement_two(nums, 5));
}
}
出现问题:
方法 1:deletenum_for
(嵌套循环删除法)
问题:
-
使用了两个嵌套的
for
循环来删除元素。内层循环会将删除的元素后面的所有元素向前移动。虽然逻辑是正确的,但返回的是size
(即nums.length - 1
),这实际上会导致删除后的数组长度错误。 -
该方法的时间复杂度是 O(n²),因为每次删除元素都需要移动后面的元素,效率较低。
方法 2:removeElement
(快慢指针法)
问题:
-
存在边界条件问题。在
for
循环中,应该遍历整个数组,即i < nums.length
,而不是i < nums.length - 1
。 -
指针
low
的递增应该在赋值之后进行,而不是在之前,否则会导致未正确覆盖数组元素。
方法 3:removeElement_two
(相向双指针法)
问题:
- 循环条件应该是
while (left <= right)
,否则可能会漏掉最后一个元素的比较。 - 在元素交换后,左指针
left
应该递增。
方法 1:嵌套循环删除法(deletenum_for
)
思路:
- 我们通过两层循环,第一层遍历数组中的每一个元素,当遇到需要删除的目标值时,通过内层循环将后续所有元素向前移动。
方法 2:快慢指针法(removeElement
)
思路:
- 通过快慢指针来实现删除的操作。快指针负责遍历整个数组,慢指针则用于记录不等于
val
的元素。 - 当快指针遍历到不等于
val
的元素时,将该元素赋值给慢指针所指向的位置,并且慢指针递增。 - 最终,慢指针的值即为移除元素后数组的新长度。
复杂度分析:
- 时间复杂度:O(n),遍历数组一次。
- 空间复杂度:O(1),只使用常量空间。
- 代码简洁性:逻辑清晰,操作简便。
方法 3:相向双指针法(removeElement_two
)
思路:
- 通过双指针从数组两端向中间收缩。左指针
left
从数组的起始位置开始,右指针right
从数组的末尾开始。 - 当左指针遇到目标元素时,我们将右指针的元素替换到左指针的位置,并同时移动两个指针,直到左右指针相遇。
- 这样实现了在遍历过程中对数组元素的原地移除,最终返回左指针的值作为新的数组长度。
复杂度分析:
- 时间复杂度:O(n),每个元素最多遍历一次。
- 空间复杂度:O(1),不使用额外空间。
977.有序数组的平方
给你一个按 非递减顺序 排序的整数数组 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]
import java.util.Arrays;
public class SortedSquares {
public int[] sortedSquares(int[] nums){
for(int i=0;i<nums.length;i++){
nums[i] = nums[i]*nums[i];
}
int size = nums.length;
for(int i=0;i<nums.length;i++){
int max = 0;
for(int j=0;j<size;j++){
if(max<nums[j]){
max = nums[j];
}
}
size--;
nums[size] = max;
}
return nums;
}
public int[] sortedSquares_two(int[] nums){
int left = 0;
int right = nums.length-1;
int[] result = new int[nums.length];
int j = nums.length-1;
for(int i=0;i<nums.length;i++){
if(nums[left]*nums[left]<nums[right]*nums[right]){
result[j] = nums[right]*nums[right];
right--;
}else{
result[j] = nums[left]*nums[left];
left++;
}
j--;
}
return result;
}
public static void main(String[] args) {
SortedSquares bs = new SortedSquares();
int[] nums = {-10, 2, 3, 4, 5, 6, 7, 8, 9, 10};
System.out.println(bs.sortedSquares(nums));
}
}
方法 1:直接排序法
平方所有元素:我们首先对数组中的每个元素进行平方操作。由于数组中的负数平方后会变成正数,导致平方后的数组顺序不再有序。
排序数组:对平方后的数组使用排序算法(如 Java 提供的 Arrays.sort())进行排序。
返回结果:返回排序后的平方数组。
时间复杂度:
平方操作:O(n),其中 n 是数组的长度。
排序操作:O(n log n)。
总时间复杂度:O(n log n)。
方法 2:双指针法(推荐)
分析平方特性:由于数组是非递减排序的,所以数组的负数部分平方后可能会变成比正数部分的元素还大的值。因此,平方后的最大值要么出现在数组的最左边,要么出现在最右边。
双指针遍历:我们可以设置两个指针,一个从数组的左边(left),一个从右边(right)开始。每次比较两个指针对应元素的平方,将较大的平方值放入新数组的末尾。
填充结果数组:从结果数组的最后一个位置开始填充,直到左右指针相遇。
时间复杂度:
平方操作与数组遍历:O(n),只需要遍历数组一次,进行平方操作并填充结果数组。
总时间复杂度:O(n)。