JAVA刷题之数组的总结和思路分享

简介: JAVA刷题之数组的总结和思路分享

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱

ʕ̯•͡˔•̯᷅ʔ大家好,我是xiaoxie.希望你看完之后,有不足之处请多多谅解,让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客

本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如需转载还请通知˶⍤⃝˶

个人主页xiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客

系列专栏:xiaoxie的刷题系列专栏——CSDN博客●'ᴗ'σσணღ*

我的目标:"团团等我💪( ◡̀_◡́ ҂)"

( ⸝⸝⸝›ᴥ‹⸝⸝⸝ )欢迎各位→点赞👍 + 收藏⭐️ + 留言📝+关注(互三必回)!

数组篇

1. 在排序数组中查找元素的第一个和最后一个位置

力扣链接

我相信大家一看到这题是有序的数组,有点基础的同学们都会想到二分查找法,这一题有思路很容易,但提交时却老是无法通过,这就是因为没有考虑好边界问题了,现在博主为大家介绍两种二分查找法

1.普通二分查找法

做好这一题的关键就在于找到边界

题就是查找 target 在 nums 中的区间,即查找 target 在 nums 中的左右边界。

仔细想的话,要找 target 在 nums 数组中的左右边界,无非存在 3 种情况:

target 在 nums[0] ~ nums[n-1] 中,nums 中存在 target。例如 nums = [5,7,7,8,8,10],target = 8,返回 [3,4]。

target 在 nums[0] ~ nums[n-1] 中,nums 中不存在 target。例如 nums = [5,7,7,8,8,10],target = 6,返回 [-1,-1]。

target < nums[0] 或者 target > nums[n-1]。例如 nums = [5,7,7,8,8,10], target = 4,返回 [-1,-1]。

用两个二分查找,一个二分查找查找左边界,另一个查找右边界,分情况讨论上述的 3 种情况。

 
class Solution {
    public int[] searchRange(int[] nums, int target) {
        int start = lowerBound(nums, target);
        if (start == nums.length || nums[start] != target)
            return new int[]{-1, -1};
        // 如果 start 存在,那么 end 必定存在
        int end = lowerBound(nums, target + 1) - 1;
        return new int[]{start, end};
    }
 
    private int lowerBound(int[] nums, int target) {
        int left = 0, right = nums.length - 1; // 闭区间 [left, right]
        while (left <= right) { // 区间不为空
            int mid = left + (right - left) / 2;
            if (nums[mid] < target)
                left = mid + 1; // 范围缩小到 [mid+1, right]
            else
                right = mid - 1; // 范围缩小到 [left, mid-1]
        }
        return left; // right+1
    }
 

2.红蓝二分查找法

相信大家一直对二分查找法的边界问题一直有困扰,一般来说二分查找就这些东西,在边界和细节上搞人,只要每次做题小心点,就没啥问题。现在博主就为大家介绍另外一种二分查找方法,它不需要考虑那么多问题,只要在最后返回值时多多注意就好了

class Solution {
    public int searchRangeLeft(int[] nums, int target) {
        int left = -1;
        int right = nums.length;
        while (left + 1 != right) {
            int mid = (left + right) / 2;
            if (nums[mid] <= target) {
                left = mid;
            } else {
                right = mid;
            }
        }
        return left;
    }
    
    public int searchRangeRight(int[] nums, int target) {
        int left = -1;
        int right = nums.length;
        while (left + 1 != right) {
            int mid = (left + right) / 2;
            if (nums[mid] < target) {
                left = mid;
            } else {
                right = mid;
            }
        }
        return right;
    }
    public int[] searchRange(int[] nums, int target) {
        int leftIndex = searchRangeLeft(nums, target);
        int rightIndex = searchRangeRight(nums, target);
        if (leftIndex >= rightIndex && leftIndex < nums.length && nums[leftIndex] == target && nums[rightIndex] == target) {
            return new int[] {rightIndex, leftIndex};
        }
        return new int[] {-1, -1};
    }
}

开始时,L指针和 R指针取在搜索区间界外,L=首个元素下标−1,R=末尾元素下标+1,此时所有元素均未着色;

循环条件始终为 L+1 < R,当 L=R 时跳出循环,此时蓝红区域划分完成,所有元素均已着色;

M指针取值始终为 M =(L+R)/2

L指针和 R指针变化的时候直接变为 M指针,即对 M 指针所指向元素进行染色,无需 +1 或者−1;

本模板唯一变化的地方是判断目标元素最终落在左边蓝色区域还是右边红色区域。以不变应万变,根据情况的不同,变换判断条件,大大的降低了出错的可能。

这样就找到左下标和右下标啦,再因为该题是找不到就返回-1,所以还要再判断,同时红蓝二分查找法并不只是适用于这题哦,它在大部分的题目都适用,目前博主还没有遇到不适用的情况,所以大家可以放心用,在变换判断条件时和普通二分查找法,大大的降低了出错的可能。

2. 删除有序数组中的重复项---快慢指针

这是力扣上的一道简单题,有的同学可能说了,多余的元素,删掉不就得了。

要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖,所以从中我们第一时间想到也是最简单的就是暴力解法

1.暴力解法

这个题目暴力的解法就是两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组。

public int removeDuplicates(int[] nums) {
    if (nums.length == 0) {
        return 0;
    }
    int k = 1; // 记录唯一元素的个数
    for (int i = 1; i < nums.length; i++) {
        boolean isDuplicate = false;
        // 检查当前元素是否已经存在于前面的唯一元素中
        for (int j = 0; j < k; j++) {
            if (nums[i] == nums[j]) {
                isDuplicate = true;
                break;
            }
        }
        // 如果当前元素是新的唯一元素,则加入到唯一元素列表中
        if (!isDuplicate) {
            nums[k] = nums[i];
            k++;
        }
    }
    return k;
}

很明显暴力解法的时间复杂度是O(n^2),这道题目暴力解法在leetcode上是可以过的。但这面试中你写这个解法,显然是不能让面试官满意的。博主这有一个使用双指针,快慢指针的解法

2.快慢指针

双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

定义快慢指针

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置

很多同学这道题目做的很懵,就是不理解 快慢指针究竟都是什么含义,所以一定要明确含义,后面的思路就更容易理解了。要解这题的思路如下

首先我们写记录下数组第一个元素的数值:

int val = nums[0];

然后我们在定义一个快指针和慢指针,指向数组第二个元素:然后快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组;慢指针:指向更新 新数组下标的位置,如下图:

public int removeDuplicates(int[] nums) {
    int val = nums[0]; // 记录当前唯一元素的值
    int slow = 1; // 慢指针,指向下一个唯一元素应该存放的位置
    for (int fast = 1; fast < nums.length; fast++) {
        if (nums[fast] != val) { // 如果当前元素与前一个唯一元素不相同
            nums[slow] = nums[fast]; // 将当前元素存放到慢指针指向的位置
            val = nums[slow]; // 更新当前唯一元素的值
            slow++; // 慢指针向后移动
        }
    }
    return slow; // 返回唯一元素的个数
}

双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法,写成这样你的代码才可以说达到了面试的高度了,所以我们遇到这一类的题型可以多往快慢指针法那边靠靠,我相信你多练习,多学习,一定可以做到更好。

3.长度最小的子数组

这是力扣上的一道中等难度的数组题,还是一样看到这一题,第一个想到的方法就是用两个for 循环的暴力解法

1.暴力解法

public class Solution {
    public int minSubArrayLen(int s, int[] nums) {
        int result = Integer.MAX_VALUE; // 最终的结果
        int sum = 0; // 子序列的数值之和
        int subLength = 0; // 子序列的长度
        for (int i = 0; i < nums.length; i++) { // 设置子序列起点为i
            sum = 0;
            for (int j = i; j < nums.length; j++) { // 设置子序列终止位置为j
                sum += nums[j];
                if (sum >= s) { // 一旦发现子序列和超过了s,更新result
                    subLength = j - i + 1; // 取子序列的长度
                    result = result < subLength ? result : subLength;
                    break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break
                }
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == Integer.MAX_VALUE ? 0 : result;
    }
}

2.滑动窗口

该如何把两个for循环变成一个呢,我想大家想到的一定是双指针,对没错,使用双指针,现在博主来为大家介绍一种双指针方法,滑动窗口。所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。它一样是使用双指针,只不过这种解法更像是一个窗口的移动,所以就称为滑动窗口。

在本题中实现滑动窗口,主要确定如下三点:

  • 窗口内是什么?
  • 如何移动窗口的起始位置?
  • 如何移动窗口的结束位置?

窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。

窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。

窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

解题的关键在于 窗口的起始位置如何移动,如图所示:

代码如下:

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int result = Integer.MAX_VALUE;
        int star = 0,end = 0;
         int sum = 0;
        for(;end < nums.length; end++) {
            sum += nums[end];
            while(sum >= target) {
                int subl = end - star+1;
                result = Math.min(subl,result);//动态更新数值
                sum -= nums[star++];
            } 
        }
        return result == Integer.MAX_VALUE ? 0 : result;
    }
}

好了这就是数组篇的全部内容了,后续博主还会持续更新,链表篇等等内容,大家可以点个关注,不错过后续精彩内容。感谢您的阅读,祝您一天生活愉快


相关文章
|
11月前
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
10月前
|
Java
Java 数组学习笔记
本文整理Java数组常用操作:遍历、求和、查找、最值及二维数组行求和等典型练习,涵盖静态初始化、元素翻倍、去极值求平均等实例,帮助掌握数组基础与应用。
|
12月前
|
存储 Java 索引
java 数组
在 Java 中,数组是一种数据结构,用于存储多个相同类型的数据元素。数组的大小一旦创建后就不能改变,因此它是固定长度的。Java 数组是一种 对象,即使它存储的值是基本类型(如 int、double 等),它也是一个对象引用。
255 0
|
人工智能 Java
Java 中数组Array和列表List的转换
本文介绍了数组与列表之间的相互转换方法,主要包括三部分:1)使用`Collections.addAll()`方法将数组转为列表,适用于引用类型,效率较高;2)通过`new ArrayList&lt;&gt;()`构造器结合`Arrays.asList()`实现类似功能;3)利用JDK8的`Stream`流式计算,支持基本数据类型数组的转换。此外,还详细讲解了列表转数组的方法,如借助`Stream`实现不同类型数组间的转换,并附带代码示例与执行结果,帮助读者深入理解两种数据结构的互转技巧。
1047 1
Java 中数组Array和列表List的转换
|
存储 监控 Java
《从头开始学java,一天一个知识点》之:数组入门:一维数组的定义与遍历
**你是否也经历过这些崩溃瞬间?** - 看了三天教程,连`i++`和`++i`的区别都说不清 - 面试时被追问&quot;`a==b`和`equals()`的区别&quot;,大脑突然空白 - 写出的代码总是莫名报NPE,却不知道问题出在哪个运算符 这个系列就是为你打造的Java「速效救心丸」!我们承诺:每天1分钟,地铁通勤、午休间隙即可完成学习;直击痛点,只讲高频考点和实际开发中的「坑位」;拒绝臃肿,没有冗长概念堆砌,每篇都有可运行的代码标本。明日预告:《多维数组与常见操作》。 通过实例讲解数组的核心认知、趣味场景应用、企业级开发规范及优化技巧,帮助你快速掌握Java数组的精髓。
569 23
|
存储 人工智能 Java
打乱数组内容引发的问题( Java)
本文介绍了两种实现数组随机打乱的方法,并深入探讨了Java中原始数据类型与对象类型的差异。方法一通过自定义随机数交换数组元素位置,方法二借助`Collections.shuffle()`函数完成数组打乱。同时,文章详细解析了`int`和`Integer`的区别,包括声明方式、内存占用、初始化以及对象特性等,并讲解了自动装箱与拆箱的功能,帮助读者更好地理解Java的基础知识。
237 0
|
存储 Java 索引
Java 复制数组
本文介绍了Java中数组的基础知识与常用操作,包括数组的概念、创建、访问元素、遍历、复制、排序和搜索等方法。同时详细讲解了数组的五种赋值方式,并通过代码示例演示了求总和平均值、最大最小值、升序降序排序及Arrays类的常用方法。内容深入浅出,适合初学者学习掌握Java数组的核心功能与应用场景。
402 2
|
存储 Java 数据挖掘
Java 中数组的多种定义方式
本文深入解析了Java中数组的多种定义方式,涵盖基础的`new`关键字创建、直接初始化、动态初始化,到多维数组、`Arrays.fill()`方法以及集合类转换为数组等高级用法。通过理论与实践结合的方式,探讨了每种定义方法的适用场景、优缺点及其背后的原理,帮助开发者掌握高效、灵活的数组操作技巧,从而编写更优质的Java代码。
789 0
|
存储 Java C++
Java数组:静态初始化与动态初始化详解
本文介绍了Java中数组的定义、特点及初始化方式。
1291 12