【Java复健指南01】简介与数组
写在最前
学习Java已经是很久之前的事情了,因为技术栈的转变,很久没有使用Java正经地开发过项目。
对于该语言的理解也是停留在表面,因此萌生了重新学习的念头。一方面是为刷算法题打基础,另一方面也是想拓展一下自己的技术栈(要不然失业了都)
既然我的目标是“复健”,那么显然不可能完全重新学习记录一遍。当初学习Java的目的是为了开发Android和制作Minecraft的mod,一些Java的高级部分涉及不多。
因此,本次学习以Java每个部分(从数组往后的)的关键知识点作为展开,会着重记录:
- 相应部分的一些易错点和对应的练习
- 编程思想与处理问题的trick
- 典型的算法题的解法、优化和总结(Java、Python版本)
开始吧~
【数组】
数组的分配方式
一维:
int[] arr = new int[5]; //动态分配
int arr[];
arr = new arr[5];//先定义,后使用
int[] arr = {1,2,3};//直接分配
二维:
int[][] arr = new int[3][2]; //基本用法
int[][] arr = new int[3][]; //后分配数组列数,可用于循环
// 动态初始化
int arr[];//声明二维数组
arr new int[2][3];//再开空间
int[][] arr = {{1,2},{6,7}};//直接分配
数组拷贝
对于基本的数据类型,其赋值过程为值拷贝,赋值完成后修改被赋值变量不会影响原变量的值
而数组不一样
数组被定义之后会开辟一块相应的内存空间,然后该空间的地址传回给数组变量arr1
当数组arr1被拷贝,arr1会复制当前的地址给被赋值的数组变量arr2(该地址指向的空间仍为之前为arr1开辟的那个)
因此,当我们对arr2的值进行修改,会影响arr1的值,因为两者指向的实际地址空间一致
数组细节
-
数组是多个相同类型数据的组合,实现对这些数据的统一管理
-
数组中的元素可以是任何数据类型,包括基本类型和引用类型,但是不能混用。
-
数组创建后,如果没有赋值,有默认值如下:
int 0, short 0, byte 0, long 0, float 0.0, double 0.0 ,boolean false, String null
-
数组下标必须在指定范围内使用,否则报:下标越界异常,比如
int arr=new int[5];//则有效下标为0-4
-
使用数组的步骤
(1)声明数组并开辟空间
(2)给数组各个元素赋值
(3)使用数组
一维数组练习
数组翻转
思路1:
从数组的头部和尾部两个方向向数组中间位置推进,过程中不断交换数组元素
public class ArrayReverse{
public static void main(String[] agrs){
/*
要求:把数组的元素内容反转。
例如:arr {11,22,33,44,55,66}→{66,55,44,33,22,11}
思路1:
规律
1.把arr[0]和arr[5]进行交换{66,22,33,44,55,11}
2.把arr[1]和arr[4]进行交换{66,55,33,44,22,11}
3.把arr[2]和arr[3]进行交换{66,55,44,33,22,11}
4.一共要交换3次 = arr.length/ 2
5.每次交换时,对应的下标是arr[i](0,1,2)和arr[arr.length - 1 -i](5,4,3)
*/
//定义一个数组
int[] arr = {11,22,33,44,55,66,77};
int temp = 0;//临时变量
for(int i = 0; i < arr.length / 2; i++){
arr[arr.length - 1 - i] = temp;
arr[arr.length - 1 - i] = arr[i];
arr[i] = temp;
}
System.out.println("====翻转后的数组====");
for(int i = 0; i<arr.length; i++){
System.out.print(arr[i] + "\t");
}
}
}
两个注意点:
- 循环范围是数组长度的一半。这里不论数组的元素个数是单/双数都没关系(反正取整都一样),单数的话中间的那个元素是不用动的(因为不是排序)
- 循环过程中的元素下标。
- i表示从数组头部开始向数组尾部的方向;
- 数组长度-i表示从尾部到头部的方向;
思路2:逆序遍历。即逆序遍历待翻转的数组,然后再顺序存入一个新数组中,最后使原数组指向新数组
public class ArrayReverse02{
public static void main(String[] agrs){
/*
思路2:
逆序遍历
1、逆序遍历待翻转的数组
2、顺序存入一个新数组内
3、将原数组的地址指向新数组(旧数组垃圾回收)
4、在循环中增加一个顺序增加的变量j
*/
int[] arr = {11,22,33,44,55,66,77};
int[] arr2 = new int[arr.length];
for(int i = arr.length - 1, j = 0; i > = 0; i--, j++){
arr2[j] = arr[i];
}
arr = arr2;//舍弃旧数组
System.out.println("====翻转后的数组====");
for(int i = 0; i < arr.length; i++){
System.out.print(arr[i] + "\t");
}
}
}
一个注意点:
- 在循环中,循环变量可以有两个;
冒泡排序
思路:
就是冒泡法的思路。遍历数组,当前的数与下一个数进行比较,若当前数大则与下一个数交换,继续比。若后一个数较大,则指针后移,重复之前的操作。最终的目的是将当前最大的数往数组的末尾移动,完成第一次移动后,我们需要找到第二大的数移动至数组的次末尾,重复上述过程直到排序完成。
因此,排序是分多轮进行的,每轮只能将一个数送至数组的末尾(相对意义上的末尾)
public class BubbleSort{
public static void main(String[] agrs){
/*
将五个无序数24,68,80,57,13使用冒泡排序法排成一个
从小到大的有序数列
数组{24,69,80,57,13}
第1轮排序:目标把最大数放在最后
第1次比较[24,69,80,57,13]
第2次比较[24,69,80,57,13]
第3次比较[24,69,57,80,13]
第4次比较[24,69,57,13,80]
*/
int[] arr = {24,69,80,57,13};
int temp = 0;
for(int i = 0; i < arr.length - 1; i++){
for(int j = 0; j < arr.length - 1 - i; j++){
if(arr[j]>arr[j+1]){
temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
/*
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
*/
}
}
System.out.println("\n====第"+(i+1)+"轮====");
for(int j = 0;j < arr.length; j++) {
System.out.print(arr[j]+"\t");
}
}
}
}
几个注意点:
- 从最基本(核心)的功能开始写,即实现数组前后两个数的比较和交换,然后再考虑重复上述过程
- 使用临时变量交换的逻辑不要搞混
- 从第二轮排序开始,其实需要比较的次数是不断变少的,这点体现再内层循环的条件中,即减掉外层循环当前的循环变量,这是一种常规思想
二维数组练习
打印杨辉三角
使用二维数组打印一个10行杨辉三角
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
【提示】
1.第一行有1个元素,第n行有n个元素
2.每一行的第一个元素和最后一个元素都是1
3.从第三行开始,对于非第一个元素和最后一个元素的元素的值
arr[i][j] = arr[i-1][j] + arr[i-1][j-1];
思路:先打印一个从上至下元素(数组长度)增加的三角,然后依据规律往里填数
public class YangHui{
public static void main(String[] agrs){
int[][] arr = new int[10][];
for(int i = 0; i < arr.length; i++){
arr[i] = new int[i + 1];//获得一堆三角形数组
//判断当前的数是否为第三行后数组的第一个数或最后一个数
for(int j = 0; j < arr[i].length){
if(j == 0 || j == arr[i].length){
arr[i][j] = 1;
}else{
arr[i][j] = arr[i - 1][j] + arr[i - 1][j - 1];
}
}
}
System.out.println("杨辉三角");
for(int i = 0; i < arr.length; i++){
for(int j = 0; j < arr[i].length; j++){
System.out.print(arr[i][j] + "\t");
}
System.out.println();
}
}
}
一个注意点:
-
二维数组先定义外层,之后再定义内层数组元素是常规用法,这题中有体现
-
例如
-
//创建二维数组,一共有3个一维数组, //但是每个一维数组还没有开辟数值空间 int[][] arr = new int[3][];//列数不确定 for(int i = 0; i < arr.length; i++){ //给每个一维数组开空间new //如果没有给一维数组new ,那么arr[i]就是null arr[i] = new int[i + 1]; //遍历一维数组,并给一维数组的每个元秦赋值 for(int j = 0; j < arr[i].length; j++){ arr[i][j] = i + 1;//赋值 } }
-
数组习题
习题一
已知有个升序的数组,要求插入一个元素,该数组顺序依然是升序,比如:[10,12,45,90],添加23后,数组为[10,12,23,45,90]
思路1:
1、定义一个变量用于接收插入的数
2、遍历数组,与待插入的数比较大小若插入的数小于数组当前遍历到的值,则插入
public class Homework04{
public static void main(String[] agrs){
//我的解法
int[] arr = {10,12,45,90};
int[] arrNew = new int[arr.length + 1];//用于存放扩充后元素的数组
int ins = 89;//插入值
int flag = 0;//用于确定是否插入值的标志位
System.out.println("====插入元素前的arr====");
for(int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
for(int i = 0; i < arr.length; i++){
if(arr[i] > ins && flag == 0){
arrNew[i] = ins;//满足条件,插入元素
flag = 1;
arrNew[i + 1] = arr[i];//将原本应在当前位置的元素顺延至后一个位置
}else if (flag == 1) {
arrNew[i + 1] = arr[i];
}else{
arrNew[i] = arr[i];
}
}
arr = arrNew;
System.out.println("====插入元素后的arr====");
for(int i =0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
思路2(标准解法):
本质数组扩容+定位
1.我们先确定添加数应该插入到哪个索引
2.然后扩容
public class Homework04{
public static void main(String[] agrs){
//先定义原数组
int[]arr = {10,12,45, 90};
int insertNum = 23;
int index = -1; //index就是要插入的位置
//遍历arr数组,如果发现insertNum<=arr[i],说明i就是要插入的位置
//使用index保留index = i;
//如果遍历 完后,没有发现insertNum<=arr[i],说明index = arr.length
//即:添加到arr的最后
for(int i = 0; i < arr. length; i++) {
if(insertNum<= arr[i]){
index = i;
break;//找到位置后,就退出
}
}
//判断index的值
if(index == -1) {//说明没有还没有找到位置
index = arr. length;
}
// System.out.println("index=" + index);
//扩容
//先创建一个新的数组,大小arr.length + 1
int[]arrNew = new int[arr.length + 1];
//下面准备将arr的元素拷贝到arrNew ,并且要跳过index位置
for(int i = 0, j = 0; i < arrNew.length; i++) {
if( i != index ) {//说明可以把arr的元素拷贝到arrNew
arrNew[i]= arr[j];
j++;
}else {//i这个位置就是要插入的数
arrNew[i] = insertNum;
}
}
}
}
习题二
随机生成10个整数(1_100的范围)保存到数组,并倒序打印以及求平均值、求最大值和最大值的下标并查找里面是否有8
public class Homework05{
public static void main(String[] agrs){
/*
我的思路:
0、定义一个长度为10的数组
1、(导入)随机数模块random
2、通过循环调用random产生10个随机数并保存至数组
3、定义存放平均值的变量average
定义存放最大/小值下标的变量Maxindex/Minindex
*/
int[]arr = new int[10];
//(int)(Math.random() 100)+1 生产随机数1-100
for(int i = e; i < arr.length; i++){
arr[i]=(int)(Math.random()*100)+1;
}
System.out.println("====arr的元素情况=====");
for(int i = 0; i < arr.length; i++){
System.out.print(arr[i]+"\t");
}
System.out.println("in====arr的元素情况(倒序)=====");
for(int i =arr.length -1; i >= 0; i--){
System.out.print(arr[i]+ "\t");
}
//平均值、求最大值和最大值的下标
//
double sum = 0;
int max = arr[0];
int maxIndex = 0;
for(int i = 1; i < arr. length; i++ ){
sum += arr[i];
if(max < arr[i]){//说明max不是最大值,就变化
max = arr[i];
maxIndex = i;
}
}
System.out.println("\nmax=" + max + " maxIndex=" +maxIndex);
System.out.print1n("\n平均值="+(sum / arr.length);
//查找数组中是否有8->使用顺序查找
int findNum = 8;
int index = -1;//如果找到,就把下标记录到index
for(int i = 0; i < arr.length; i+){
if(findNum == arr[i){
System.out.println(“找到数"+findNum + ”下标="+i);
index = i;
break;
}
}
if(index == -1){
System.out.println("没有找到数"+findNum );
}
}
}
注意:
本题中有两个常用的手法
1、找出数组中的最大/小值
设置一个变量保存最值,先指定数组的第一个值为最大值,设定一个变量用于记录最值下标。
遍历数组,然后将当前遍历值与设定最大值比较
若发现更大值则更新最值变量和下标变量即可
2、寻找数组中的某个值
设置一个变量用于存放要找的目标值
设置一个变量用于存放下标,初始值为-1
关键点
遍历数组,若找到目标数,返回提示并将下标保存
即刻结束循环遍历
若没有找到,遍历会结束,此时只需判断index是否仍为初始值即刻得知找没找到目标数