一、二维数组的创建和使用
1.1二维数组的基本概念
定义:二维数组本质其实还是一维数组,但是它是一个比较特殊的一维数组,为什么这样说呢,我们接下来会解释这个说法。
二维数组的创建:类型[][] 数组名 = new 类型[行][列]
演示示例:
int[][] arr1;//创建了一个数组 int[][] arr2 = new int[2][];//创建了一个两行的二维数组 int[][] arr3 = new int[2][3];//创建了一个两行三列的二维数组 System.out.println(arr1);//程序报错了 System.out.println(arr2); System.out.println(arr3);
- 通过观看上述代码我们会发现创建一个二维数组可以有多种方式,每种方式有什么不一样的呢,这个值得我们去探讨了解一下。
- arr1:创建了int类型的二维数组,但是没有初始化。(一般不这样创建)
- arr2:创建了int类型发二维数组,并且给二维数组初始化了行,列没有初始化。
- arr3:创建了int类型发二维数组,并且给二维数组初始化了行和列。
- 在打印三个数组时我们发现arr1出现了报错,是什么原因呢,发现没有给arr1赋初始值,这样是不可以的,在Java中变量必须要赋初始值,否则编译不通过。
- 打印arr2和arr3输出的都是它们对象的地址
- 需要注意的是定义二维数组时,列可以省略,但行不可以省略。
1.2二维数组的初始化
在二维数组中初始化分为动态初始化和静态初始化。接下来我们先用代码来演示一下:
//动态初始化 int[][] arr1 = new int[9][]; int[][] arr2 = new int[6][6]; //静态初始化 int[][] arr3 = {{1, 2 ,3}, {2 ,3, 5 }};//二行三列 int[][] arr4 = new int[][]{{11,1,1,}, {2,2,22}, {3,3,33}}; //三行三列
- 什么时候用静态?什么时候用动态?
- 静态初始化的场景适用于对二维数组的大小和内容都是已知的情况下。(并且数组元素不多的时候)
- 动态初始化使用场景则更为广阔,可以根据需求来设定。(多数使用)
1.3二维数组的遍历
用循环的嵌套就能很好的遍历一个二维数组啦,接下来我们用代码演示一下:
int[][] arr3 = {{1,2,3},{2 ,3, 5 }};//二行三列 //控制行 for (int i = 0; i < arr3.length ; i++) { //控制列 for (int j = 0; j < arr3[i].length; j++) { //打印每个下标 System.out.print(arr3[i][j] + " "); } System.out.println(); }
下面在来一个动态二维数组的赋值和遍历示例:
//二维数组啊arr1 int[][] arr1 = new int[9][5]; //初始化赋值 for (int i = 0; i < arr1.length; i++) { for (int j = 0; j < arr1[i].length; j++) { arr1[i][j] = i + j; } } //输出元素 for (int i = 0; i < arr1.length; i++) { for (int j = 0; j < arr1[i].length; j++) { System.out.print(arr1[i][j] + " "); } System.out.println(); }
- 遍历静态数组并不需要赋值,直接输出每个元素就可以了,而动态数组需要先赋值,否则输出的将是类型的默认值。
- 在中间的图片中我们可以轻松的看出二维数组中元素是怎么储存的,对应的下标是多少。
- 现在来解释我们说二维数组本质上是特殊的一维数组的概念
- 先看图片:
- 从图片中可以看出下标0,1,2三行输出的是一个地址,这个地址指向了一维数组列
- 无论是一维数组还是二维数组,在内存中都是连续存储的。二维数组的行在内存中是连续的,而行本身也是数组(一维数组)
- 二维数组可以看作是一个数组的数组。每个元素(行)本身也是一个数组(一维数组)。
- 在Java的内部实现中,二维数组的声明和初始化实际上是创建了一个一维数组,其中的每个元素是另一个一维数组。
二、数组的练习
练习-----数组转字符串
//方法1 使用Array.toString这个库方法 public static void ArrayToString1(int[] array) { //定义一个String类型接收字符串,使用Array.toString把array转为字符串 String ArrayToString = Arrays.toString(array); System.out.println(ArrayToString);//输出 } //方法2 模拟实现字符串打印过程 public static void ArrayToString2(int[] array) { //先打印[ System.out.print("["); //遍历 for (int i = 0; i < array.length; i++) { System.out.print(array[i]);//输出所有元素 //判断是否要继续打印空格和逗号 if(array[i] < array.length ) { System.out.print(" " + ","); } } System.out.print("]");//最后打印 ] }
练习-----求数组中元素的平均值
实现思路:
- 先遍历数组得到每个元素
- 把每个元素相加在一起
- 和除以数组的长度
//方法1 public static double AverageCalculator1(int[] array) { double sum = 0; //遍历 for (int i = 0; i < array.length; i++) { //求和 sum = sum + array[i]; } //返回平均值 return sum / array.length; } //方法2 public static double AverageCalculator2(int[] array) { //使用Stream API 求平均值 double average = Arrays.stream(array).average().orElse(0.0); return average;//返回 }
练习-----查找数组中指定元素(顺序查找)
实现思路:
- 遍历数组元素
- 判断是否跟指定元素相等
- 返回元素,没有返回-1
//查找数组中指定元素(顺序查找) public static int chaz1(int[] array ,int a) { //遍历 for (int i = 0; i < array.length; i++) { //判断是否相等 if(array[i] == a) { return array[i]; } } return -1;//不存在元素 }
练习----- 查找数组中指定元素(二分查找)--有序数组
实现思路:
- 先排序确定数组有序
- while循环查找指定元素,条件为L <= R,大于等于说明不存在指定元素
- 定义一个中间值M,判断M是否等于查找的元素
- 小于查找元素L = M+1,大于查找元素R = M+1,
- M继续判断,循环往复,直到找到指定元素或者循环结束。
//查找数组中指定元素(二分查找) public static int chaz2(int[] array,int a) { //先排序,确定数组有序 for (int i = 0; i < array.length; i++) {//排序的趟数 for (int j = 0; j < array.length-1-i; j++) {//排序一趟 if(array[j] > array[j + 1]) { //交换 int tmp = array[j]; array[j] = array[j + 1]; array[j + 1] = tmp; } } } //查找指定元素 int l = 0;//L int r = array.length -1;//R while(l <= r) { int m = (l + r) / 2;//M if(array[m] == a) { return array[m]; } else if (array[m] > a) { r = m - 1; } else if (array[m] < a) { l = m + 1; } } return -1; }
练习-----实现一个方法 transform, 以数组为参数, 循环将数组中的每个元素 乘以 2 , 并设置到对应的数组元素上.
实现思路:
- 遍历数组,给每个元素乘以2;
- 返回数组
//实现一个方法 transform, 以数组为参数, 循环将数组中的每个元素 乘以 2 , 并设置到对应的数组元素上. public static int[] transform(int[] array) { for (int i = 0; i < array.length; i++) { array[i] = array[i] * 2; } return array; }
练习-----调整数组顺序使得奇数位于偶数之前。调整之后,不关心大小顺序。
实现思路:
- 设计一个L = 下标0,一个R = 下标最后一位
- 遍历数组,判断L = 偶数,R = 奇数,满足条件交换位置不满足说明不需要交换位置
- 继续判断下标L是否等于奇数,是就继续判断下一位
- 继续判断下标R是否等于偶数,是就继续判断下一位
- 相遇则结束循环,返回数组
//调整数组顺序使得奇数位于偶数之前。调整之后,不关心大小顺序 public static void adjustOrder1(int[] array) { int l = 0;//L为0 int r = array.length - 1;//最后一个下标 //循环条件 while (l < r) { //奇数&1 = 1 偶数&1 = 0 if ((array[l] & 1) == 0 && (array[r] & 1) == 1) { int tmp = array[l];//交换 array[l] = array[r]; array[r] = tmp; //奇数就自增 } else if ((array[l] & 1) == 1) { l++; //偶数自减 } else if ((array[r] & 1) == 0) { r--; } } } //方法二创建两个数组分别存奇数和偶数 public static void adjustOrder2(int[] array) { int[] temp = new int[array.length]; // 创建一个临时数组 int oddIndex = 0; // 用于记录奇数在临时数组中的位置 int evenIndex = array.length - 1; // 用于记录偶数在临时数组中的位置 // 遍历原数组 for (int num : array) { if (num % 2 != 0) { // 如果是奇数,放在临时数组的前面 temp[oddIndex++] = num; } else { // 如果是偶数,放在临时数组的后面 temp[evenIndex--] = num; } } // 将临时数组的内容复制回原数组 System.arraycopy(temp, 0, array, 0, array.length); }
练习-----给定一个整型数组, 实现冒泡排序(升序排序)
实现思路:
- 两层嵌套循环遍历数组
- 外层控制数组排序的次数,内层控制数组排序一趟
- 比较大小,前大后小交换数值
//给定一个整型数组, 实现冒泡排序(升序排序) public void bubbleSort(int[] array) { //外层循环控制需要多少躺 for (int i = 0; i < array.length - 1; i++) { boolean boo = false;//测试数组是否有序 //内存循环控制一趟的排序 for (int j = 0; j < array.length - 1 - i; j++) { //前一个数比后一个数大交换数值 if(array[j] > array[j + 1]) { int tmp = array[j]; array[j] = array[j + 1]; array[j + 1] = array[j]; boo = true;//执行了说明数组发生了交换,数组还是无序状态 } } if(boo == false) {//没有交换就直接跳出循环 break; } } }
练习----- 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
实现思路:
首先对数组进行排序。
初始化两个指针,
left
指向数组的起始位置,right
指向数组的末尾位置。在循环中,计算两个指针指向元素的和:
如果和等于目标值,返回这两个整数的索引。
- 如果和小于目标值,将左指针向右移动,尝试增大和。
- 如果和大于目标值,将右指针向左移动,尝试减小和。
如果遍历完整个数组都没有找到,抛出异常。
public static int[] twoSum(int[] nums, int target) { // 首先对数组进行排序 java.util.Arrays.sort(nums); int left = 0; // 初始化左指针 int right = nums.length - 1; // 初始化右指针 while (left < right) { // 当左指针小于右指针时 int sum = nums[left] + nums[right]; // 计算当前两个指针指向元素的和 if (sum == target) { // 如果和等于目标值 return new int[] { left, right }; // 返回这两个整数的索引 } else if (sum < target) { // 如果和小于目标值 left++; // 将左指针向右移动 } else { // 如果和大于目标值 right--; // 将右指针向左移动 } } throw new IllegalArgumentException("No two sum solution"); // 如果没有找到,抛出异常 }
练习-----给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
实现思路:
使用两个整数
ones
和twos
,分别存储只出现一次的位和出现两次的位。遍历数组,对于每个元素:更新只出现一次的位
ones
。更新出现两次的位twos
。消除已经出现过两次的位。重置已经出现过三次的位(即两次的位)。最终,
ones
中存储的就是只出现一次的元素。public static int singleNumber(int[] nums) { int ones = 0; // 存储只出现一次的位 int twos = 0; // 存储出现两次的位 for (int num : nums) { // 遍历数组 ones = ones ^ (ones & num); // 更新只出现一次的位 twos = twos ^ (twos & num); // 更新出现两次的位 ones = ones ^ (ones & twos); // 消除已经出现过两次的位 twos = twos ^ (twos & ones); // 重置已经出现过三次的位(即两次的位) } return ones; // 返回只出现一次的元素 }