数组基础知识

1,073 阅读4分钟

一,单数组的双指针法

从两端到中间的双指针法

例如,对于字符串abc,我们要对字符串进行反转,将字符串反转为cba

single_array_p.png

可以使用一个初始位置在头部的指针pLeft,另一个起始位置在尾部的指针pRight,将两个指针同时向中间移动。

single_array_p_move.png 当为奇数个数组的时候,两个指针在中位相遇,当为偶数时候,两指针会在两个字符的中间位置相遇。 下面我们来简单实现上面这个过程:

public char[] reverseString(char[] source) {
       int pLeft = 0;
       int pRight = source.length - 1;
       while (pLeft<= pRight) {
           char temp = source[pLeft];
           source[pLeft] = source[pRight];
           source[pRight] = temp;
           pLeft++;
           pRight--;
       }
       return source;
   }

快慢指针

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

例如,以数组[2,2,3]为例,查找val = 2的值,原地移除。

fast_low_p.png

首先来说下快慢指针在这里的作用:

  • 慢指针用于快指针将后面元素移动到前面时候定位。
  • 快指针用于向后扫描值为val位置。
public int removeElement(int[] nums, int val) {
        int fast = 0;
        int slow = 0;
        while (fast <= nums.length - 1) {
            if (nums[fast] != val) {
                nums[slow] = nums[fast];
                slow++;
            }
            fast++;
        }
        return slow;
    }

二,双数组的双指针法

例如:存在两个有序数组,nums1,nums2,将两个有序数组合并成一个新的有序数组。

对于数组nums1,nums2, 分别对这两数组设置指针,移动指针,取小的数字作为当前位上的值。 这里,我们使用一个num1Copy作为原始nums1数组的拷贝,将原来数组nums1作为存放合并结果的数组。

public void merge(int[] nums1, int m, int[] nums2, int n) {


        int[] num1Copy = new int[m + n]; //用于存放nums1原始数据
        System.arraycopy(nums1, 0, num1Copy, 0, m);
        int p1 = 0; //num1Copy 指针
        int p2 = 0; //nums2 指针
        int p = 0;//nums1 指针
        while (p1 < m && p2 < n) {
            if (num1Copy[p1] > nums2[p2]) {
                nums1[p++] = nums2[p2++];
            } else {
                nums1[p++] = num1Copy[p1++];
            }
        }
        if (p1 < m) {
            System.arraycopy(num1Copy, p1, nums1, p, m - p1);
        }
        if (p2 < n) {
            System.arraycopy(nums2, p2, nums1, p, n - p2);
        }
    }

三,二分法

二分法介绍

我们首先来看一个数组的查找问题,对于升序数组nums,在数组中查找target,并返回targetnums中的索引位置。如果未找到返回-1。 首先我们用最简单的无脑暴力法,从index=0的位置开始,往后依次对比,如果对比成功返回当前位置,如果不成功返回-1.

public static int search(int[] nums, int target) {
       for (int index = 0; index < nums.length; index++) {
           if (nums[index] == target) {
               return index;
           }
       }
       return -1;
   }

从平均时间复杂度来看,这段代码复杂度为O(n)。 再回到题目,数组nums是升序的,这一点是我们在代码中没有利用到的条件,如果我们第一次先去比较数组中间的数字nums[middle],如果nums[middle]比我们的目标值大,则说明我们还得往靠左找,往右找肯定是没有;如果nums[middle]比我们的目标值小,则说明我们要往右搜索。 我们每次的判断,都是将数组一分为二,每次一分为二的去将目标值所在范围尽可能缩小。 例如,nums=[1,2,3,4,5,6],要查找的target = 5,查找targetnums中的位置。

二分法模板1

令初始时,left = 0, right = length-1,

model1_1.jpg 此时,num[mid]=3,我们要查找的数值5比3大,说明我们还要往后查找才有可能找到5.此时我们要将left指针往右移动,移动到mid右侧,此时剩余的查找范围为[mid+1,right];

model1_2.jpg 移动完left指针之后,此时计算mid,获取到nums[mid]=5,刚好是我们要查找的值,完成。 下面还提供了了个gif图,可以连起来看看。

model_1.gif 总结 :

  • nums[mid] = target,此时mid即为查找位置。
  • nums[mid]不是查找目标时候,如果nums[mid]>target,则说明查找目标在左侧,并且nums[mid]!=target则,我们的右指针可以移动到mid左侧。
  • nums[mid]不是查找目标时候,如果nums[mid]<target,则说明查找目标在右侧,并且nums[mid]!=target则,我们的左指针可以移动到mid侧。
/**
     * 关键属性:
     * * 二分查找的最基础和最基本的形式。
     * * 查找条件可以在不与元素的两侧进行比较的情况下确定(或使用它周围的特定元素)。
     * * 不需要后处理,因为每一步中,你都在检查是否找到了元素。如果到达末尾,则知道未找到该元素。
     * *********************************************************************************
     * 区分语法:
     * * 初始条件:left = 0, right = length-1
     * * 终止:left > right
     * * 向左查找:right = mid-1
     * * 向右查找:left = mid+1
     *
     * @param nums
     * @param target
     * @return
     */
    public static int binarySearch1(int[] nums, int target) {
        if (nums == null || nums.length == 0)
            return -1;

        int left = 0, right = nums.length - 1;
        while (left <= right) {
            // Prevent (left + right) overflow
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        // End Condition: left > right
        return -1;
    }

这种的模板是比较好理解的,下面两种,都可以看做是基于这种模板的变种。

二分法模板2

model2_1.jpg 可以把索引6当做不会达到的位置,我们更改下target的值,mid还是计算leftright的中间值,通过将target与中间值进行比较,如上图,nums[mid]<target时候,left的移动位置还是mid+1

model2_2.jpg 继续比较,发现中间值还是小,left再移动跟right重合了。

model2_3.jpg 此时,我们需要单独判断,剩下的这个位置。发现这个位置不存在数值,未找到我们的目标值。 下面为完整的过程:

model2.gif

/**
    * 关键属性:
    * * 一种实现二分查找的高级方法。
    * * 查找条件需要访问元素的直接右邻居。
    * * 使用元素的右邻居来确定是否满足条件,并决定是向左还是向右。
    * * 保证查找空间在每一步中至少有 2 个元素。
    * * 需要进行后处理。 当你剩下 1 个元素时,循环 / 递归结束。 需要评估剩余元素是否符合条件。
    * *********************************************************************************
    * 区分语法:
    * * 初始条件:left = 0, right = length
    * * 终止:left == right
    * * 向左查找:right = mid
    * * 向右查找:left = mid+1
    *
    * @param nums
    * @param target
    * @return
    */
   public static int binarySearch2(int[] nums, int target) {
       if (nums == null || nums.length == 0)
           return -1;

       int left = 0, right = nums.length;
       while (left < right) {
           // Prevent (left + right) overflow
           int mid = left + (right - left) / 2;
           if (nums[mid] == target) {
               return mid;
           } else if (nums[mid] < target) {
               left = mid + 1;
           } else {
               right = mid;
           }
       }

       // Post-processing:
       // End Condition: left == right
       if (left != nums.length && nums[left] == target) return left;
       return -1;
   }

这种移动方式,最后会剩余下一个元素,需要一个后处理的判断,来确定最后一个元素是否是我们查找的元素。

二分法模板3

nums还是数组[1,2,3,4,5,6],target=7,查找target。左右指针初始位置还是在头部和尾部。

model3_1.jpg 但是移动时,左右指针都只移动到middle中间位置。

model3_2.jpg

model3_3.jpg 移动到最后,只剩下leftright指针分别指向剩余的两个位置,需要单独比较剩余的两个数值,是否是我们在查找的target

model3_4.jpg 下面是移动的全过程:

model3.gif

代码实现:

/**
     * 关键属性:
     * * 实现二分查找的另一种方法。
     * * 搜索条件需要访问元素的直接左右邻居。
     * * 使用元素的邻居来确定它是向右还是向左。
     * * 保证查找空间在每个步骤中至少有 3 个元素。
     * * 需要进行后处理。 当剩下 2 个元素时,循环 / 递归结束。 需要评估其余元素是否符合条件。
     * *********************************************************************************
     * 区分语法:
     * * 初始条件:left = 0, right = length-1
     * * 终止:left + 1 == right
     * * 向左查找:right = mid
     * * 向右查找:left = mid
     *
     * @param nums
     * @param target
     * @return
     */
    public static int binarySearch3(int[] nums, int target) {
        if (nums == null || nums.length == 0)
            return -1;
        //left---mid ----target
        int left = 0, right = nums.length - 1;
        while (left + 1 < right) {
            // Prevent (left + right) overflow
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] < target) {
                left = mid;
            } else {
                right = mid;
            }
        }
        // Post-processing:
        // End Condition: left + 1 == right
        if (nums[left] == target) return left;
        if (nums[right] == target) return right;
        return -1;
    }