一文通数据结构与算法之——数组+常见题型与解题策略+Leetcode经典题

2 数组

数组是一种基础数据结构,可以用来处理常见的排序和二分搜索问题,典型的处理技巧包括对撞指针、滑动窗口等,数组是数据结构中的基本模块之一。因为字符串是由字符数组形成的,所以二者是相似的。大多数面试问题都属于这个范畴。

数组之中常和双指针、二分查找、排序算法、字符串、矩阵相结合,以及分治算法

2.1 常见题型及解题策略

剑指offer 数组(共14道题目):

【剑指 Offer】12、矩阵中的路径

【剑指 Offer】66、构建乘积数组

27、移除元素

26、删除有序数组中的重复项

80、删除有序数组中的重复项 II

75、颜色分类(双指针)


2.2 字符串操作基础

字符串的带参构造函数,实现传入char[]

//String(char[] ch)
new String(ch);

HashSet的带参构造函数

//HashSet(Collection<? extends E> c) 构造一个包含指定 collection 中的元素的新 set。
new HashSet<>(Arrays.asList('a','e','i','o','u','A','E','I','O','U'));
//Arrays.asList(T... a) 返回一个受指定数组支持的固定大小的 List<T>

Character数组的操作

if (Character.isLetterOrDigit(ch)) {
      list.append(Character.toLowerCase(ch));
}

在字符数组中++操作的灵活运用

nums[i++] = a;//将nums[i] = a,并且i++

防止覆盖的数组后移操作

//从前往后挪的操作会导致数据的覆盖
for(int i = left;i<nums.length;i++){
    nums[i+1] = nums[i];//nums[1,2,2,3,0,0]——>{1,2,2,2,2,2}
}

int last = m+n-1;
while(j>=0){
    nums1[last--] = nums2[j--];
}

Arrays.copyOf(),实现将集合转化成数组

//copyOf(int[] original, int newLength),复制指定的数组,截取或用 0 填充(如有必要),以使副本具有指定的长度
//copyOfRange(int[] original, int from, int to) ,将指定数组的指定范围复制到一个新数组。
//返回新的数组
Arrays.copyOfRange(res, 0, index+1);

ArrayList.toArray()实现将对应的集合list转化成相同数据类型大小的数组

//list中已经存储了元素
Object[] obj = list.toArray();
//不带参数的toArray()方法,是构造的一个Object数组,然后进行数据copy

Integer[] integers = list.toArray(new Integer[list.size()]);
int[][] ans = list.toArray(new int[list.size()][]);//返回转化之后的int[][]二维数组
//带参数的toArray(T[] a) 方法,根据参数数组的类型,构造了一个对应类型的,长度跟ArrayList的size一致的数组
  
 //源码
public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

Lambda表达式Arrays.sort排序操作

Arrays.sort(arrays, (a, b) -> a[0] - b[0]);//谁小谁在前
//相当于
Arrays.sort(arrays, new Comparator<int[]>() {
     @Override
     public int compare(int[] a, int[] b) {
          return a[0]-b[0];
     }
 });

2.3 删除数组元素

  • 掌握数组删除元素的直接覆盖操作
  • 双指针法
2.3.1 题库列表

27、移除元素 (快慢指针)

26、删除有序数组中的重复项 (快慢指针)

80、删除有序数组中的重复项 II

75、颜色分类(双指针,三色旗,小米笔试)

image-20210920171557519

//    快慢指针移除元素
/**
     * 27.移除元素
     * 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于
     * val 的元素,并返回移除后数组的新长度。
     * @param arr
     */
public int remove(int[] arr,int val){
        int slow = 0;
        for (int fast = 0; fast < arr.length; fast++) {
            if(arr[fast]!=val){
                arr[slow] = arr[fast];
                slow++;
            }
        }
        return slow;
    }

/**
     * 26. 原地删除有序数组中的所有重复项
     * 输入:nums = [1,1,2]
     * 输出:2, nums = [1,2]
     *
     * 输入:nums = [0,0,1,1,1,2,2,3,3,4]
     * 输出:5, nums = [0,1,2,3,4]
     * @param arr
     * @return
     */
public int removeDup(int[] arr){
    int slow= 0;
    for (int fast = 1; fast < arr.length; fast++) {
        if(arr[slow]!=arr[fast]){
            arr[++slow] = arr[fast];
        }
    }
    return slow+1;
}


75、颜色分类

采用双指针:加深理解指针确定位置进行交换的操作

 public void sortColors(int[] nums) {
        int p = 0;//作为0的分割指针
        for (int i = 0; i < nums.length; ++i) {//与i++功能一致但是性能相对更好
            if(nums[i]==0){
                int temp = nums[i];
                nums[i] = nums[p];
                nums[p] = temp;
                ++p;
            }
        }
//        2.在从p开始重现交换1 和 2 的位置
        for (int i = p; i < nums.length; ++i) {
            if(nums[i]==1){
                int temp  = nums[i];
                nums[i] = nums[p];
                nums[p] = temp;
                ++p;
            }
        }

    }

2.4 双指针技巧

2.4.1 题库列表

88. 合并两个有序数组如何将数组所有元素整体后移,防止数组覆盖?

167. 两数之和 II - 输入有序数组(有序数列的首尾双指针)

125. 验证回文串

345. 反转字符串中的元音字母

11. 盛最多水的容器经典题目

209. 长度最小的子数组滑动窗口

88 、合并两个有序数组

//逆向双指针解决合并两个排序数组
 public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i = m-1;
        int j = n-1;
        int last = m+n-1;
        while(i>=0 && j>=0){
            if(nums1[i]>nums2[j]){
                nums1[last--] = nums1[i--];
            }else{
                nums1[last--] = nums2[j--];
            }
        }
//        剩下的部分直接复制过去
        while(j>=0){
            nums1[last--] = nums2[j--];
        }
    }

167 、两数之和 II - 输入有序数组

//给定一个已按照 升序排列  的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 
/**
     * 167. 两数之和 II - 输入有序数组
     * 递增数列,输出何为目标值的下标
     * @param numbers
     * @param target
     * @return
     */
    public int[] twoSum(int[] numbers, int target) {
        int left=0,right=numbers.length-1;
        while(left<right){
            int sum = numbers[left]+numbers[right];
            if(sum==target) {
                return new int[]{left+1,right+1};
            }else if(sum<target){
                left++;
            }else{
                right--;
            }
        }
        return null;
    }

125 、验证回文串

// "A man, a plan, a canal: Panama"——>只考虑字母和数字字符,忽略字母的大小写。
public boolean isPalindrome(String s) {
    //1.字符串预处理
        StringBuilder list = new StringBuilder();
        int length = s.length();
        for (int i = 0; i < length; i++) {
            char ch = s.charAt(i);
            if (Character.isLetterOrDigit(ch)) {
                list.append(Character.toLowerCase(ch));
            }
        }
//       2.双指针遍历是否一致
        int i = 0;
        int j = list.length()-1;
        while(i<=j){
            if(list.charAt(i)!=list.charAt(j)){
                return false;
            }else{
                i++;
                j--;
            }
        }
        return true;
    }

345 、反转字符串中的元音字母

//1.将元音字符先存起来
private final static HashSet<Character> vowel = new HashSet<>(
            Arrays.asList('a','e','i','o','u','A','E','I','O','U')
    );

public String reverseVowels(String s) {
    char[] ch = new char[s.length()];
    //1.双指针前后遍历字符串
    int left = 0;
    int right = s.length()-1;
    while(left<=right){
        char cleft = s.charAt(left);
        char cright = s.charAt(right);
        //如果是非元音字符,则直接添加到新的字符数组
        if(!vowel.contains(cleft)){
            ch[left++]=cleft;
        }else if(!vowel.contains(cright)){
            ch[right--] = cright;
        }else{
            //如果是元音字符,则交换位置放
            ch[left++] = cright;
            ch[right--] = cleft;
        }
    }
    return new String(ch);
}

11 、 盛最多水的容器

public int maxArea(int[] height) {
//        1.思路:双指针,移动小的那边
        int left = 0,right = height.length-1;
        int res = 0;
        while(left<right){
            res = height[left] < height[right] ?
                    Math.max(res, (right - left) * height[left++]):
                    Math.max(res, (right - left) * height[right--]);
        }
        return res;
    }

209 、长度最小的子数组

//我们把数组中的右指针右移,直到总和大于等于 target 为止,记录个数。然后左指针右移,直到队列中元素的和小于 target为止,记录个数。重复,直到右指针到达队尾。
public int minSubArrayLen(int target, int[] nums) {
        int i = 0,j=0,sum=0;
        int res = Integer.MAX_VALUE;
        while(j<nums.length){
            //1.右指针滑动
            sum+=nums[j++];
            while(sum>=target){
                //2.固定右指针,滑动左指针,求最小的子数组
                res = Math.min(res,j-i);
                sum-=nums[i++];
            }
        }
        return res==Integer.MAX_VALUE?0:res;
    }

2.5 数组类操作

56 、合并区间

//以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。

public int[][] merge(int[][] intervals) {
//        1.创建一个集合存储答案
        List<int[]> ans = new ArrayList<>();
//        2.判空直接返回
        if (intervals == null || intervals.length == 0){
            return ans.toArray(new int[0][]);
        }
//  	3.将集合按数组一维进行排序
        Arrays.sort(intervals,(a,b)->{a[0]-b[0]});

        for (int i = 0; i < intervals.length; i++) {
            int start = intervals[i][0];
            int end = intervals[i][1];
//       4.如ans中最后一个元素的end>遍历元素的start,要进行合并操作
            if(ans.size()==0 ||ans.get(ans.size()-1)[1]<start){
                ans.add(intervals[i]);
            }else{
                ans.get(ans.size()-1)[1] = Math.max(ans.get(ans.size()-1)[1],end);
            }
        }
        return ans.toArray(new int[ans.size()][]);
    }

2.6 剑指offer 数组题:

总结:越是简单的题目,想考察的越深,不是为了考察简单的思路。

剑指 Offer 04. 二维数组中的查找
//在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
public boolean findNumberIn2DArray(int[][] matrix, int target) {
        if(matrix.length == 0){
            return false;
        }
//从右上角进行遍历,这样就可以直接进行区分,有点类似二叉树的操作
        int i=matrix.length-1,j=0;
        while(i>=0 && j<matrix[0].length){
// 可以进一步二分法进行操作,但是感觉没有必要
            if(matrix[i][j]==target){
                return true;
            }
            if(matrix[i][j]<target){
                j++;
            }else{
                i--;
            }
        }
        return false;
    }
剑指 Offer 11. 旋转数组的最小数字

剑指 Offer 11. 旋转数组的最小数字

//把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 `[3,4,5,1,2]` 为 `[1,2,3,4,5]` 的一个旋转,该数组的最小值为1。
public int minArray(int[] numbers) {
//   最好的解法是必须考虑到输入本身的特点,结合输入的特点进行求解,避免暴力求解
        int low = 0;
        int high = numbers.length-1;
        while(low<high){
            int pivot = low + (high-low)/2;
            if(numbers[pivot]<numbers[high]){
                high = pivot;
            }else if(numbers[pivot]>numbers[high]){
                low = pivot+1;
            }else{
//    比如 [1 3 3],此时 numbers[pivot] = numbers[high]=3
//    如果不另外计算,直接low = pivot+1,就会错过最小值
//    而右边的值总是比中值大。
                high -=1;
            }
        }
        return numbers[low];
    }


剑指 Offer 17. 打印从1到最大的n位数

剑指 Offer 17. 打印从1到最大的n位数

//输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
//这里考察,字符串的加法,因为不论用那种基本数据类型都会出现超过表示范围的风险
//bilibili面试中就出现了字符串加法中按位进位的情况
public class _17_printNumbers {
    int[] res;
    int count=0;
    char[] num,loop={'0','1','2','3','4','5','6','7','8','9'};

    public int[] printNumbers(int len){
//        1.初始化
        res = new int[(int)Math.pow(10,len)-1];
        num = new char[len];
//        2.从个位开始递归
        for (int i = 1; i <=len; i++) {
            dfs(0,i);
        }
        return res;
    }

    public void dfs(int index,int len){
        //3.基线
        if(index==len){
            res[count++]=Integer.parseInt(new String(num,0,len));
            return;
        }
        int start = 0;
        if(index==0){
            start=1;
        }
//        4.每一位下进行递归全排列
        for (int i = start; i <10 ; i++) {
            num[index] = loop[i];
            dfs(index+1,len);
        }
    }

}


【剑指Offer】21、调整数组顺序使奇数位于偶数前面

【剑指Offer】21、调整数组顺序使奇数位于偶数前面

//仿快排收尾双指针
public int[] exchange(int[] nums) {
        int i = 0,j = nums.length-1;
        while(i<j){
            while(i<j && nums[j]%2==0){
                j--;
            }
            while(i<j && nums[i]%2!=0){
                i++;
            }
            swap(nums,i,j);
        }
        return nums;
    }

    public void swap(int[] nums,int i,int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
剑指Offer】29、顺时针打印矩阵

【剑指Offer】29、顺时针打印矩阵

public int[] spiralOrder(int[][] matrix) {
    //如果没有这一步判空,当输入数组为空时,也会直接进入循环,导致outofIndex
        if(matrix.length==0){
            return new int[0];
        }
    
        int left=0,right = matrix[0].length-1;
        int top=0,bottom = matrix.length-1;
    
        int index=0;
        int[] res = new int[matrix.length*matrix[0].length];
        while(true){
//            上层遍历
            for (int i = left; i <=right ; i++) {
                res[index++] = matrix[top][i];
            }
            if(++top>bottom){
                break;
            }
//            右边遍历
            for (int i = top; i <=bottom ; i++) {
                res[index++] = matrix[i][right];
            }
            if(--right<left){
                break;
            }
//            下边遍历
            for (int i = right; i >=left ; i--) {
                res[index++] = matrix[bottom][i];
            }
            if(--bottom<top){
                break;
            }
//             左边遍历
            for (int i = bottom; i >=top ; i--) {
                res[index++] = matrix[i][left];
            }
            if(++left>right){
                break;
            }
        }
        return res;
    }
【剑指Offer】39、数组中出现次数超过一半的数字
//1.利用HashSet去统计次数
//2.排序,中间的那个数便是
//3.分治算法
//4.摩尔投票法,摩尔投票法 简称同归于尽法,设众数+1,非众数-1,前后和为0之后,剩下的必定含有众数

class Solution {
    //数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
    public int majorityElement(int[] nums) {
        int more = 0, votes = 0;
        for(int num : nums){//遍历当前所有元素
            if(votes == 0) more = num;//如果前后的和为0,重新设定众数的值为当前值
            votes += num == more ? 1 : -1;
        }
//        如果题目并没有给定说一定存在众数,则需要多一步验证
        int count = 0;
        for(int num : nums){//遍历当前所有元素
            if(num == x) count+=1;//如果前后的和为0,重新设定众数的值为当前值
        }
        return count>nums.length/2?x:0;
    }
}

//    分治算法
//    1.不断的递归,基线是长度为1,返回这个数字
//    2.左右两个递归的数字是否相等,所以要加范围
//    * 如果相等,这个就是众数直接返回
//    * 如果不相等,那么就需要算个数,返回个数多的
    public int majorityElement2(int[] nums) {
        return majorityElementRange(nums,0,nums.length-1);

    }
    public int majorityElementRange(int[] nums,int begin,int end ) {
//        递归基线
        if(begin==end){
            return nums[begin];
        }
        int mid = begin + (end - begin)/2;
        int left = majorityElementRange(nums,begin,mid);
        int right = majorityElementRange(nums,mid+1,end);

        if(left==right){
            return left;
        }else{
            int leftCount = count(nums,left,begin,mid);
            int rightCount  = count(nums,right,mid+1,end);
            return leftCount>rightCount?left:right;
        }
    }

    public int count(int[] nums,int number,int begin,int end){
        int count=0;
        for (int i = begin; i <= end; i++) {
            if(nums[i]==number){
               count+=1;
            }
        }
        return count;
    }
【剑指Offer】51、数组中的逆序对

image-20210920171617606

// 结合归并排序的操作
// 合并两个排序数组 的过程,而每当遇到 左子数组当前元素 > 右子数组当前元素 时,意味着 「左子数组当前元素 至 末尾元素」 与 「右子数组当前元素」 构成了若干 「逆序对」,新增计数操作

class Solution {
    int count;
    public int reversePairs(int[] nums) {
        this.count = 0;
        merge(nums, 0, nums.length - 1);
        return count;
    }

    public void merge(int[] nums,int left,int right){
        if(left<right){
            int mid = left + ((right-left)>>1);
            merge(nums,left,mid);
            merge(nums,mid+1,right);
            mergeSort(nums,left,mid+1,right);
        }
    }

    public void mergeSort(int[] nums,int left,int mid,int right){
//        1.重构两个子数组
        int[] leftArr = new int[mid-left];
        for (int i = 0; i < leftArr.length; i++) {
            leftArr[i] = nums[left+i];
        }
        int[] rightArr = new int[right-mid+1];
        for (int i = 0; i < rightArr.length; i++) {
            rightArr[i] = nums[mid+i];
        }

//        2.双指针遍历两个数组,比较大小
        int i=0,j=0;
        while(i<leftArr.length && j<rightArr.length){
            if(leftArr[i]<=rightArr[j]){
                nums[left++] = leftArr[i++];
            }else{
//                加上前半部分数组的索引之后的元素个数
                count+=(leftArr.length-i);
                nums[left++] = rightArr[j++];
            }
        }

        while(i<leftArr.length){
            nums[left++] = leftArr[i++];
        }

        while(j<rightArr.length){
            nums[left++] = rightArr[j++];
        }
    }

}
【剑指 Offer】 56 - I. 数组中数字出现的次数

【剑指 Offer】 56 - I. 数组中数字出现的次数

//异或的操作:如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0,相同的数字异或为0
//两数相等异或结果为0,一个数与0异或结果就等于其本身。所以如果数组中只有一个出现一次的数
//那么就只需要对所有的数进行异或就可以得到这个只出现一次的数

//那么也就来了思路,如果我们能把两个不相等的数字,分别分到两个子数组中,再在子数组中进行异或操作,就能把两个不相等的数找出来
//1.分数组,首先确定两个数字之中不一样的位数是哪一位
//2.在根据这个固定出来的位数进行分数组异或操作
//3.bingo,得到结果

一位数字不一样的异或

image-20210920171630881

两位数字不一样的异或

image-20210920171643012

public class Solution{
    public int[] singleNumbers(int[] nums) {
        //1.先求出所有数字异或操作之后得到的结果
        int res=0;
        for (int num : nums) {
            res^=num;
        }

        //2.确定异或结果中是哪一个二进制为不一样
        int m = 1;
        //若 a & 0001 = 1 ,则 a 的第一位为 1 ;
        //若 a & 0010 = 1 ,则 a 的第二位为 1 ;
        //左移m的1,0001——0010——0100——1000
        while((res&m)==0) m<<=1;

        //3.根据确定的不同的位来进行划分子数组
        int x=0,y=0;
        for (int num : nums) {
            if((num&m)!=0) x^=num;//一个数组中异或得到的结果就是x
            else y^=num;//另一个数组中异或得到结果y
        }
        
        //4.返回结果
        return new int[]{x,y};
        }
}
【剑指 Offer 】56 - II. 数组中数字出现的次数 II

【剑指 Offer 】56 - II. 数组中数字出现的次数 II

 //找出在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
//[1,1,1,3]-->3
public int singleNumber(int[] nums) {
    //1.hashMap直接统计次数
    //2.计算对应二进制位中的1的个数,在对3取余,得到的结果就是只出现一次的结果,但是过程比较复杂,留作以后再看吧。
}

image-20210920171703038

【剑指 Offer】 57、和为s的两个数字

【剑指 Offer】 57、和为s的两个数字

//    1.HashMap将所有数存起来,固定一个,getValue是否有
//    2.递增数组,首尾双指针
    public int[] twoSum(int[] nums, int target) {
        int i = 0,j = nums.length-1;
        while(i<j){
            int num = nums[i],ans = target-num;
            while(i<j && nums[j]>ans){
                j--;
            }
            if(nums[j]==ans){
                return new int[]{i,j};
            }else{
                i++;
                j = nums.length-1;//todo,这里可以继续优化
            }
        }
        return null;
    }
    //优化
    public int[] twoSum2(int[] nums, int target) {
        int i = 0, j = nums.length - 1;
        while(i < j) {
            int s = nums[i] + nums[j];
            if(s < target) i++;
            else if(s > target) j--;
            else return new int[] { nums[i], nums[j] };
        }
        return new int[0];
    }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值