【算法思维训练-剑指Offer联名 一】数组篇

从今天开始通过算法训练来锻炼自己的逻辑思维,在上手阶段通过刷《剑指Offer》的一些题来找回手感,由于数据结构也有些遗忘,所以按照题型分类的角度来进行训练,每篇为一个主题,每周完成至少一个主题,挑战值三个主题,同时也能完善下自己数据结构的一些知识。每篇blog先基础总结下数据结构和核心点,然后训练相应的题,最后总结下收获

数据结构

当然在训练之前回顾下基础知识,在Java版的数据结构中的【数据结构 一】数组一文中已经进行过详细的整理,这里只摘录一些基本要点如下:

  • 数组变量是引用类型,数组本身就是对象,数组中的每个元素相当于该对象的成员变量,数组对象本身是在中存储的
  • 数组的长度是固定的,也就是说可以存储固定个数的数据
  • 数组类型可以是任何数据类型,包括基本数据类型和引用类型,但是同一数组内的元素类型必须一致,也就是说数组中存储的数据的数据类型一致
  • 数组元素在内存中是一个接着一个线性存放的,是内存中连续分配的。通过第一个元素就能访问随后的元素,避免了数据覆盖的可能性。数组所存在的内存空间为数组专用,避免了数据被覆盖的问题。

同时还有些注意事项需要注意:

  • 数组声明的时候并没有实例化任何对象,只有在实例化数组对象时,JVM才分配空间,这时才与长度有关。
  • 声明一个数组的时候并没有数组真正被创建。
  • 构造一个数组,必须指定长度,二维数组指定第一维。

数组的初始化方式分为三种:静态初始化、动态初始化以及默认初始化。静态即直接赋值,不需要new,直接分配空间。动态即先new一个内存空间出来,然后再赋值。默认初始化则是在new出空间后按照元素类型给元素进行默认赋值。初始化时内存的变化如下,分别为基本数据类型的初始化和引用数据类型的初始化:

  1. 基本数据类型的初始化:给所有的元素默认初始化赋值为0,然后再进行初始化赋值

在这里插入图片描述

  1. 引用数据类型的初始化:给所有的元素默认初始化赋值为null,然后再进行初始化赋值
    在这里插入图片描述

以上就是数组数据结构的一些理论要点,接下来进行算法训练。

算法训练

《剑指offer》关于数组的算法训练共有3题:构建乘积数组【难度2】、数组中重复的数字【难度3】、二维数组中的查找【难度4】。

构建乘积数组

给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…*A[i-1]A[i+1]…*A[n-1]。不能使用除法。(注意:规定B[0] = A[1] * A[2] * … * A[n-1],B[n-1] = A[0] * A[1] * … * A[n-2];)

分析

这道题比较好理解,当然对于重新捡起来我还是有些难度,题的意思很明确,就是长度为n的A、B两个数组,B的每个元素都是除了A中这个元素的其它元素的连乘:

  • A、B的长度是相同的,都是n
  • B的每个元素都是除了A中这个元素的其它元素的连乘
  • 不能用除法
    • 边界值:数组不存在、数组长度小于2均返回false,不满足边界条件

这样就是这个题的四要素,然后得出如下的图。
在这里插入图片描述

解法

其实最直观的解法就是把A的元素连乘,然后循环,每个B的元素等于A元素连乘的结果除以当前B元素索引位置A的值。但是这样与不能用除法相悖。所以还有第二直观的方法,就是左边获取一个乘积,右边获取一个乘积,再相乘,但这样的算法复杂度却比较高

import java.util.ArrayList;
public class Solution {
    public int[] multiply(int[] A) {
       //设定边界条件,B[n-1] = A[0] * A[1] * ... * A[n-2];所以A长度必须大于等于2,否则会报索引越界
       if(A==null||A.length<=1)
          return null;
       //动态初始化数组B
       int length = A.length;
       int[] B=new int[length];
       //循环给B赋值,得到下三角
       for(int i=0;i<length;i++){
          int temp1=1; //用temp1接当前元素左边的连乘积
          int temp2=1; //用temp2接当前元素右边的连乘积
          //用temp1接当前元素左边的连乘积
          for(int j=0;j<i;++j){
           temp1*=A[j];
          }
          //用temp2接当前元素右边的连乘积
          for(int m=i+1;m<length;++m){
           temp2*=A[m];
          }
           B[i]=temp1*temp2;
       }
        return B;
       
    }
}

其实可以从图中看出,B数组可以看做两个倒三角的乘积。

import java.util.ArrayList;
public class Solution {
    public int[] multiply(int[] A) {
       //设定边界条件,B[n-1] = A[0] * A[1] * ... * A[n-2];所以A长度必须大于等于2,否则会报索引越界
       if(A==null||A.length<=1)
          return null;
       //动态初始化数组B
       int length = A.length;
       int[] B=new int[length];
       //循环给B赋值,得到上三角
       B[0]=1;
       for(int i=1;i<length;i++){
           B[i]=B[i-1]*A[i-1];
       }
        
       //循环给B赋值,乘以下三角
       int temp=1;
       for(int i=length-2;i>=0;i--){
           temp*=A[i+1];
           B[i]*=temp;
       }
        
        return B;
       
    }
}

这道题的收获是:1,要将要求转换为分解条件,最好有图解。2,注意边界条件

数组中重复的数字

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

分析

按照之前的训练思维来处理,先分解题目信息:

  • 数组长度为n,整数数组
  • 数组中有重复数字,重复数字的个数为0到n/2之间
  • 找到第一个重复的数字
  • 边界值:数组不存在、数组长度为0均返回false,不满足边界条件

这样就是这个题的四要素,接下来按照要求来做一下题。

解法

自己AC了一种复杂度比较高但比较直观的解法,就是构造一个数组B用来存储历史值, //如果当前值在历史值中存在则命中,否则加入历史值。

public class Solution {
    public boolean duplicate(int numbers[],int length,int [] duplication) {
       //如果数组不存在或者只有一个数字,则直接返回false;
       if(numbers==null||length<=1)
             return false;
       int[] B=new int[length];
       B[0]=numbers[0]; //用B存储左边的值
       for(int i=1;i<length;i++){
          for(int j=0;j<i;j++){
              //如果当前值在历史值中存在则命中,否则加入历史值
              if(numbers[i]==B[j]){ 
                  duplication[0]=numbers[i];
                  return true;
              }else{
                  B[i]=numbers[i];
              }
          }
       }
        return false;
       
    }
    
}

其实还有一种更好的解法,就是构造一个标布尔数组,因为numbers里存的都是0到n-1整数,所以其实可以当做另一个数组的索引下标。

public class Solution {
    //boolean只占一位,所以还是比较省的
    public boolean duplicate(int numbers[], int length, int[] duplication) {
        //构造一个二进制数组
        boolean[] k = new boolean[length];
        for (int i = 0; i < k.length; i++) {
            //把数组值当做这个二进制数组的索引,只要发现该索引位被标记过,就认为重复
            if (k[numbers[i]] == true) {
                duplication[0] = numbers[i];
                return true;
            }
            //设置二进制数组的索引位数组值为true
            k[numbers[i]] = true;
        }
        return false;
    }
    
}

这道题的收获是:思路要灵活,注意题中的0–n-1,思考为什么这么设置

二维数组中的查找

在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

分析

按照之前的训练思维来处理,先分解题目信息:

  • 数组为一个n*n的二维数组,说白了就是一个正方形数组
  • 数组的每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序
  • 判断数组中是否存在一个整数。
  • 边界值:数组不存在、数组长度为0、数组行长度为0,均返回false,不满足边界条件

这样就是这个题的三要素,接下来按照要求来做一下题。画个模型来辅助理解。例如我们要命中下面矩阵中的6.
在这里插入图片描述

解法

自己AC了一种复杂度比较高但比较直观的解法,就是遍历二维数组寻找整数值,这种做法当然不可取,复杂度高也浪费了顺序递增的条件。

public class Solution {
    public boolean Find(int target, int [][] array) {
       //如果数组不存在或者长度为0,直接返回false
       if(array==null||array.length<1||array[0].length<1)
             return false;
       for(int i=0;i<array.length;i++){
          for(int j=0;j<array.length;j++){
             if(target==array[i][j])
                 return true;
           }
       }
       return false;
 
    }
}

还有一种用上这两种条件的解法,经过简单思考就能想到,利用其顺序性,逐步缩小范围

public class Solution {
    public boolean Find(int target, int [][] array) {
       //如果数组不存在或者长度为0,直接返回false
       if(array==null||array.length<1||array[0].length<1)
             return false;
       //声明行、列以及检索下标
       int rows=array.length;
       int columns=array.length;
       int column=columns-1;
       int row=0;
       //移动光标的过程中分别做三次判断
       while(row<rows&&column>=0){
           if(target==array[row][column])
               return true;
           if(target>array[row][column])
               row++;
           else
               column--;
       }
       return false;
        
    }
}

这道题的收获是:思路要灵活,注意题中每行每列的顺序性,还有就是在矩阵二维数组中,多考虑下指针移动的过程。

思考

其实数组算是一种比较基础的数据结构,而且一般不会遇到太复杂的数组结构,一般也就到二维,更高维的维度计算也比较少。本次训练收获如下:

  • 拆解:遇到问题一定要把题拆解开来,拆解成一系列的条件
  • 画图:拆解完成之后,最好依据题目描述构造一个简单的Demo来辅助理解题目的意思
  • 边界值:写代码的时候一定要注意边界值,防止溢出
  • 特殊说明:要注意题目中的特殊说明,这些特殊说明可以帮助你跳出常规的复杂计算方式

思维也会因算法训练更加严谨。

展开阅读全文

Python数据分析与挖掘

01-08
92讲视频课+16大项目实战+源码+¥800元课程礼包+讲师社群1V1答疑+社群闭门分享会=99元   为什么学习数据分析?       人工智能、大数据时代有什么技能是可以运用在各种行业的?数据分析就是。       从海量数据中获得别人看不见的信息,创业者可以通过数据分析来优化产品,营销人员可以通过数据分析改进营销策略,产品经理可以通过数据分析洞察用户习惯,金融从业者可以通过数据分析规避投资风险,程序员可以通过数据分析进一步挖掘出数据价值,它和编程一样,本质上也是一个工具,通过数据来对现实事物进行分析和识别的能力。不管你从事什么行业,掌握了数据分析能力,往往在其岗位上更有竞争力。    本课程共包含五大模块: 一、先导篇: 通过分析数据分析师的一天,让学员了解全面了解成为一个数据分析师的所有必修功法,对数据分析师不在迷惑。   二、基础篇: 围绕Python基础语法介绍、数据预处理、数据可视化以及数据分析与挖掘......这些核心技能模块展开,帮助你快速而全面的掌握和了解成为一个数据分析师的所有必修功法。   三、数据采集篇: 通过网络爬虫实战解决数据分析的必经之路:数据从何来的问题,讲解常见的爬虫套路并利用三大实战帮助学员扎实数据采集能力,避免没有数据可分析的尴尬。   四、分析工具篇: 讲解数据分析避不开的科学计算库Numpy、数据分析工具Pandas及常见可视化工具Matplotlib。   五、算法篇: 算法是数据分析的精华,课程精选10大算法,包括分类、聚类、预测3大类型,每个算法都从原理和案例两个角度学习,让你不仅能用起来,了解原理,还能知道为什么这么做。
©️2020 CSDN 皮肤主题: 点我我会动 设计师: 上身试试 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值