JAVA数组基础——代码随想录 数组

JAVA数组基础

基础知识

  1. 数组在Java中是一种引用数据类型(不是基本数据类型),父类为Object。
  2. 数组的存储位置在JVM(java虚拟机)内存划分中的堆内存中,用new来创建的内存空间,是一串连续的内存地址。每一个元素类型相同,因此占用内存空间大小一样。由于很难在内存空间上找到连续的特大空间,因此数组无法存储大量数据。
  3. 元素查询/检索方便,每个元素效率相同。随机删除/添加元素时效率较低。

初始化

  1. 静态初始化

    int[] arr = new int[]{1,2,3};
    int[] arr = {1,2,3};
    

    指定内容,长度等于内容个数

  2. 动态初始化

    int[] arr = new int[3];
    

    指定长度,默认初始值由数组的字符类型而定

存储

  1. 数组是存放在连续内存空间上的相同类型数据的集合。
  2. 数组下标都是从0开始的。
  3. 内存空间的地址是连续的,所以在删除或者增添元素的时候,就难免要移动其他元素的地址

题目

LeetCode 704. 二分查找

​ 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

  • 一开始的思路

    • 二分查找,把数组分为两半,比较中间值的大小,小于则走前半,大于则走后半,利用递归的思想调用search函数,遇到的问题:递归传递的数组只有一半,不知道怎么去处理这个参数传递。
  • 题解:不用递归,只需要设定循环条件:start < end即可

  • 错误:

    • 在循环外面定义了mid,没有考虑到mid每轮循环都是改变的。

    • 左右怎么移动?start=mid,end=mid?(我一开始简单的想成了这个,但是会导致指针不动。)应该等于mid-1或者mid+1、

    • 循环的结束条件是什么?start != end(当只有一个元素时,那么循环就变成无限循环了!)应该是start <= end

    • 寻找mid的时候进入死循环,应该是int mid = start + (end-start)/2,这样写防止溢出

class Solution {
    public int search(int[] nums, int target) {
        int start = 0;
        int end = nums.length - 1;
        while(start <= end){
            int mid = start + (end-start)/2;
            if(target == nums[mid]){
                return mid;
            }
            else if(target < nums[mid]){
                end = mid - 1;
            }
            else{
                start = mid + 1;
            }
        }
        return -1;
    }
}

总结

  • 这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素

  • 区间的写法:左闭右闭or左闭右开or左开右闭?

    • 定义 target 是在一个在左闭右闭的区间里,**也就是[left, right] **。区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:

      • while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
      • if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
      class Solution {
          public int search(int[] nums, int target) {
              // 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算
              if (target < nums[0] || target > nums[nums.length - 1]) {
                  return -1;
              }
              int left = 0, right = nums.length - 1;
              while (left <= right) {
                  int mid = left + ((right - left) >> 1);
                  if (nums[mid] == target)
                      return mid;
                  else if (nums[mid] < target)
                      left = mid + 1;
                  else if (nums[mid] > target)
                      right = mid - 1;
              }
              return -1;
          }
      }
      
    • 定义 target 是在一个在左闭右开的区间里,也就是[left, right) 。有如下两点:

      • while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
      • if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
      class Solution {
          public int search(int[] nums, int target) {
              int left = 0, right = nums.length;
              while (left < right) {
                  int mid = left + ((right - left) >> 1);
                  if (nums[mid] == target)
                      return mid;
                  else if (nums[mid] < target)
                      left = mid + 1;
                  else if (nums[mid] > target)
                      right = mid;
              }
              return -1;
          }
      }
      

LeetCode 27. 移除元素

​ 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(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],也会被视作正确答案。

  • 一开始的思路

    • O(1)额外空间,思考的是交换,把等于target的元素与数组最后的元素交换,然后最后的数组元素指针往前挪一个,直到前指针大于后指针
    class Solution {
        public int removeElement(int[] nums, int val) {
            int left = 0;
            int right = nums.length - 1;
    
            while(left <= right){
                if(nums[left] == val){
                    nums[left] = nums[right];
                    nums[right] = val;
                    right = right - 1;
                }
                else{
                    left = left + 1;
                }
            }
            return right + 1;
        }
    }
    

    上面的可以解,但是数组顺序会改变,如果数组顺序不改变的话?

    • 暴力解法,两个for循环,第一个for遍历数组,第二个for用于遇到了val值,把后面数组元素移动到前面来
    class Solution {
        public int removeElement(int[] nums, int val) {
            int size = nums.length;
    
            for(int i = 0; i < size; i++){
                if(nums[i] == val){
                    for(int j = i + 1; j < size; j++){
                        nums[j - 1] = nums[j];
                    }
                    i--;
                    size--;
                }
            }
            return size;
        }
    }
    

注意

	1. i--这一步,数组往前移动了一位以后,该判断的是移位以后的第一个,也就是现在i位置的元素,但是for循环会i++,所以这里应该i--
	1. for循环i的终止条件应该是size而不是nums.length,因为nums.length是定值,去掉元素后数组大小会发生变化,所以应该为size。
  • 双指针法

    • 快指针&慢指针指向什么?
      • 新数组就是不含有目标元素的数组。
      • 快指针:判断是否应该加入新数组的元素,即寻找新数组的元素 。
      • 慢指针:指向更新后新数组的第一个元素,即新数组下标的位置
    class Solution {
        public int removeElement(int[] nums, int val) {
            int fast = 0;
            int slow = 0;
            for(fast = 0; fast < nums.length; fast++){
                if(nums[fast] != val){
                    nums[slow] = nums[fast];
                    slow++;
                }
            }
            return slow;
        }
    }
    

LeetCode 977.有序数组的平方

​ 给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
  • 一开始的思路:

    • 先把整个数组平方后再排序
    • 时间复杂度是 O(n + nlogn), 即O(nlogn)的时间复杂度。
  • 题解:

    • 数组其实是有序的,暴力解法并没有利用到这一特点。由于负数的存在,负数平方之后可能就会成为最大数,因此数组平方后的最大值就应该在数组的两端,不是最左边就是最右边,不可能是中间。
    • 所以设置两个指针,一个指向最左一个指向最右,比较两数平方大小,将较大值放入新数组,再把指针往里移动,直到左指针大于右指针
class Solution {
    public int[] sortedSquares(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        int[] newnums = new int[nums.length];
        int nn = nums.length - 1;
        while(left <= right){
            if(nums[left] * nums[left] < nums[right] * nums[right]){
                newnums[nn--] = nums[right] * nums[right];
                right--;
            }
            else{
                newnums[nn--] = nums[left] * nums[left];
                left++;
                }
        }
        return newnums;
    }
}

LeetCode 209.长度最小的子数组

​ 给定一个含有 n 个正整数的数组和一个正整数 target

​ 找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度**。**如果不存在符合条件的子数组,返回 0

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
  • 一开始的思路:

    • 按照数组长度遍历,长度为1时遍历一次找下有没有,然后长度+1继续遍历
    • 后来仔细思考了一下,是要连续子数组,所以只需要遍历一次,第一个元素开头时,计算大于target的长度最小的 连续子数组,然后右移计算第二个元素开头时长度最小的连续子数组,如果是1就可以直接返回。
  • 题解:

    • 滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。用一个for循环来做2个for循环所做的事情。

    • 这个索引下标 j 表示的究竟是滑动窗口里面的终止位置还是起始位置

      • 可以先假设 j 表示的是起始位置,若 j 表示的是起始位置,这个for循环一次一次把索引下标向后移动,这个终止位置要把后面所有的元素遍历一遍,才能返回所有以这个起始 i 为起始位置的集合,然后我们再去判断这个集合里面的所有元素:如果 >= target,去搜集所有 >= target这些集合里面的所有的长度,再取一个最小的。 如果终止位置是一个一个向后移动的话,那么和这个暴力的解法又存在什么区别呢?

      • 此时你会发现:如果这一个for循环里面的这个j表示的是起始位置的话,那终止位置依然要把所有位置都遍历一遍,那么它的思路就和暴力是一样的。因此这一个for循环里面的j一定指向的是终止位置,而起始位置需要我们用动态移动的策略来移动起始位置,这样才能用一个for循环的思路来解决这道题。

    • 应该写if(sum >= target) 还是 while(sum >= target)

      • 看看这个样例: 输入:target = 100, nums = [1,1,1,1,1,100]
      • 02.04.gif
      • 时间复杂度是O(n) 。每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)
    class Solution {
    
        // 滑动窗口
        public int minSubArrayLen(int target, int[] nums) {
            int left = 0;
            int sum = 0;
            int min = nums.length + 1;
            for(int right = 0;right < nums.length; right++){
                sum = sum + nums[right];
                while(sum >= target){
                    int len = right - left + 1;
                    min = min > len? len: min; 
                    sum = sum - nums[left];
                    left++;
                }
            }
            return min == nums.length + 1? 0 : min;
        }
    }
    

LeetCode 59.螺旋矩阵II

​ 给你一个正整数 n ,生成一个包含 1n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix

img

class Solution {
    public int[][] generateMatrix(int n) {
        int[][] matrix = new int[n][n];
        int start = 0;
        int count = 1;
        int loop = 0;
        int i = 0;
        int j = 0;
        while(loop++ < n / 2){

            for(j = start; j < n - loop; j++){
                matrix[start][j] = count++;
            }
            for(i = start; i < n - loop; i++){
                matrix[i][j] = count++;
            }
            for(;j > start; j--){
                matrix[i][j] = count++;
            }
            for(;i > start; i--){
                matrix[i][j] = count++;
            }
            start++;

        }
        if(n % 2 !=0){
            matrix[n / 2][n / 2] = n * n;
        }
        return matrix;
    }
}

题解:

  • 循环不变量:把一个正方形分为四条边,每一条边的代码都设置为一样的循环。即每条边都数组前闭后开区间,只控制每条边的第一个元素而不控制每条边的最后一个元素,把最后一个元素放在下一个循环中作为第一个元素。

  • 转几圈?

    • 转n/2圈,于是涉及到n为奇数或者偶数,n为基数时,转完圈中间最后剩一格,需要最后手动赋值最后一格。
  • 起始位置?

    • 第一圈起始位置为(0, 0),起始位置每圈都要变,即每圈+1,所以for循环里面i, j的起始值也会变,把i, j赋值为0显然不对,应该赋值为start,每圈的起始位置。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值