学习笔记 -java -数组与字符串

二分查找 (Binary Search)

二分查找是一种高效的搜索算法,时间复杂度为 O(log n),适用于有序数组。

可以通过这个Binary and Linear Search Visualization感受一下二分查找,更易于理解

问题分析

1、给定一个升序排列的整数数组 nums 和一个目标值 target

2、需要找到 target 在数组中的索引位置,如果不存在则返回 -1

3、必须使用 O(log n) 时间复杂度的算法

解题思路

  1. 初始化指针:设置两个指针 left 和 right 分别指向数组的开始和结束

  2. 循环查找:当 left <= right 时:

    • 计算中间索引 mid

    • 如果 nums[mid] == target,直接返回 mid

    • 如果 nums[mid] < target,说明目标在右半部分,调整 left

    • 如果 nums[mid] > target,说明目标在左半部分,调整 right

  3. 未找到:如果循环结束仍未找到,返回 -1

Java 实现代码

class Solution {
    public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        
        while (left <= right) {
            int mid = left + (right - left) / 2; // 防止溢出,等同于 (left + right)/2
            
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        
        return -1;
    }
}

关键点解释

  1. 防止整数溢出

    • 计算 mid 时使用 left + (right - left) / 2 而不是 (left + right) / 2

    • 这样可以避免当 left 和 right 都很大时相加导致的整数溢出

  2. 循环条件

    • 使用 left <= right 而不是 <,这样可以确保当 left == right 时也能检查最后一个元素

  3. 边界调整

    • 当 nums[mid] < target 时,调整 left = mid + 1(因为 mid 已经检查过)

    • 当 nums[mid] > target 时,调整 right = mid - 1(同理)

复杂度分析

1、时间复杂度:O(log n),每次都将搜索范围减半

2、空间复杂度:O(1),只使用了常数级别的额外空间

测试用例验证

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 示例1
    int[] nums1 = {-1, 0, 3, 5, 9, 12};
    System.out.println(solution.search(nums1, 9)); // 输出: 4
    
    // 示例2
    System.out.println(solution.search(nums1, 2)); // 输出: -1
    
    // 边界测试
    int[] nums2 = {5};
    System.out.println(solution.search(nums2, 5)); // 输出: 0
    System.out.println(solution.search(nums2, 1)); // 输出: -1
}

通过这个解法,可以高效地解决有序数组的查找问题。记住二分查找的前提是数组必须是有序的,如果无序需要先排序。

移除元素

题目要求我们原地移除数组中所有等于给定值 val 的元素,并返回剩余元素的数量 k。移除后的数组前 k 个元素应为不等于 val 的元素,其余元素可以忽略。

方法思路

双指针法

  1. 初始化指针:使用两个指针,i 用于遍历数组,k 用于记录不等于 val 的元素的位置。

  2. 遍历数组:遍历数组,当遇到不等于 val 的元素时,将其放到 k 的位置,并递增 k

  3. 返回结果:最后 k 即为剩余元素的数量。

Java代码

class Solution {
    public int removeElement(int[] nums, int val) {
        int k = 0; // 指针,记录不等于val的元素的位置
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != val) {
                nums[k] = nums[i];
                k++;
            }
        }
        return k;
    }
}

代码解释

  1. 初始化指针 kk 从0开始,表示下一个不等于 val 的元素应该放置的位置。

  2. 遍历数组:使用 i 遍历数组的每个元素。如果当前元素 nums[i] 不等于 val,则将其复制到 nums[k] 的位置,并递增 k

  3. 返回结果:遍历完成后,k 的值即为剩余元素的数量,数组的前 k 个元素即为所有不等于 val 的元素。

示例验证

示例1

输入nums = [3,2,2,3]val = 3

执行过程

i=0nums[0]=3(等于val),跳过。

i=1nums[1]=2(不等于val),nums[0]=2k=1

i=2nums[2]=2(不等于val),nums[1]=2k=2

i=3nums[3]=3(等于val),跳过。

输出k=2,数组前2个元素为 [2,2]

示例2

输入nums = [0,1,2,2,3,0,4,2]val = 2

执行过程

i=0nums[0]=0(不等于val),nums[0]=0k=1

i=1nums[1]=1(不等于val),nums[1]=1k=2

i=2nums[2]=2(等于val),跳过。i=3nums[3]=2(等于val),跳过。

i=4nums[4]=3(不等于val),nums[2]=3k=3

i=5nums[5]=0(不等于val),nums[3]=0k=4

i=6nums[6]=4(不等于val),nums[4]=4k=5

i=7nums[7]=2(等于val),跳过。

输出k=5,数组前5个元素为 [0,1,3,0,4]

复杂度分析

时间复杂度:O(n),其中 n 是数组的长度。我们只需遍历一次数组。

空间复杂度:O(1),没有使用额外的存储空间,是原地操作。

其他方法

双指针优化(当要移除的元素很少时)

如果 val 在数组中出现的次数很少,可以将等于 val 的元素与数组末尾的元素交换,并减少数组长度。这样可以减少不必要的复制操作。

class Solution {
    public int removeElement(int[] nums, int val) {
        int i = 0;
        int n = nums.length;
        while (i < n) {
            if (nums[i] == val) {
                nums[i] = nums[n - 1];
                n--;
            } else {
                i++;
            }
        }
        return n;
    }
}
方法思路
  1. 初始化指针i 从0开始,n 为数组长度。

  2. 遍历数组:当 nums[i] 等于 val 时,将 nums[i] 与 nums[n-1] 交换,并减少 n。否则,递增 i

  3. 返回结果n 即为剩余元素的数量。

适用场景

当 val 在数组中出现的次数较少时,此方法可以减少元素的移动次数,提高效率。

总结

本题的关键在于使用双指针技巧,高效地原地修改数组。第一种方法适用于大多数情况,第二种方法在特定情况下(val 出现较少时)更优。理解双指针的思想对解决类似问题非常有帮助。

有序数组的平方

问题理解

题目给定一个非递减顺序排列的整数数组 nums,要求返回每个元素平方后按非递减顺序排列的新数组。

方法思路

由于数组可能包含负数,平方后的数组可能会打乱原有的顺序。我们可以利用双指针法,从数组的两端向中间遍历,比较两端的平方值,将较大的平方值放入结果数组的末尾。

步骤:
  1. 初始化指针left 指向数组起始位置,right 指向数组末尾。

  2. 比较平方值:比较 nums[left] 和 nums[right] 的平方值,将较大的平方值放入结果数组的当前末尾位置。

  3. 移动指针:根据比较结果移动 left 或 right 指针。

  4. 填充结果数组:重复上述步骤直到所有元素处理完毕。

解决代码

class Solution {
    public int[] sortedSquares(int[] nums) {
        int n = nums.length;
        int[] result = new int[n];
        int left = 0, right = n - 1;
        int index = n - 1; // 从后往前填充结果数组
        
        while (left <= right) {
            int leftSquare = nums[left] * nums[left];
            int rightSquare = nums[right] * nums[right];
            
            if (leftSquare > rightSquare) {
                result[index] = leftSquare;
                left++;
            } else {
                result[index] = rightSquare;
                right--;
            }
            index--;
        }
        
        return result;
    }
}

代码解释

  1. 初始化:创建结果数组 result,初始化双指针 left 和 right,以及填充索引 index

  2. 遍历比较:每次比较 left 和 right 指针所指元素的平方值,将较大的值放入 result 的当前 index 位置。

  3. 移动指针:根据比较结果移动指针,left 右移或 right 左移,index 递减。

  4. 返回结果:最终 result 数组即为按非递减顺序排列的平方值数组。

示例验证

示例1

输入nums = [-4,-1,0,3,10]

执行过程

left=0right=4:比较 16 和 100100 较大,放入 result[4]right--

left=0right=3:比较 16 和 916 较大,放入 result[3]left++

left=1right=3:比较 1 和 99 较大,放入 result[2]right--

left=1right=2:比较 1 和 01 较大,放入 result[1]left++

left=2right=2:比较 0 和 0,放入 result[0]输出[0,1,9,16,100]

示例2

输入nums = [-7,-3,2,3,11]

执行过程

left=0right=4:比较 49 和 121121 较大,放入 result[4]right--

left=0right=3:比较 49 和 949 较大,放入 result[3]left++

left=1right=3:比较 9 和 9,放入 result[2]right--

left=1right=2:比较 9 和 49 较大,放入 result[1]left++

left=2right=2:比较 4 和 4,放入 result[0]

输出[4,9,9,49,121]

复杂度分析

时间复杂度:O(n),只需一次遍历数组。

空间复杂度:O(n),需要额外的数组存储结果。

其他方法

直接排序法

  1. 平方后排序:先计算每个元素的平方,然后对结果数组进行排序。

  2. 代码

    class Solution {
        public int[] sortedSquares(int[] nums) {
            for (int i = 0; i < nums.length; i++) {
                nums[i] = nums[i] * nums[i];
            }
            Arrays.sort(nums);
            return nums;
        }
    }
  3. 复杂度

    • 时间复杂度:O(n log n),排序步骤占主导。

    • 空间复杂度:O(1)(原地排序)或 O(n)(额外空间取决于排序算法)。

总结

双指针法利用了原数组有序的特性,避免了排序的开销,是最优解法。直接排序法虽然简单,但在效率上不如双指针法。理解双指针的思想对解决类似问题非常有帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值