文章目录
- 数组
- 什么是数组?(数组其实是一种数据结构)
- 什么是数据结构?
- 数组的创建与初始化
- 数据的静态初始化
- 数组的使用
- 数组和方法之间的关系
- 关于应用数据类型的理解问题
- 栈
- 堆
- 在数组中交换元素
- 二维数组(了解即可)
- 复制数组
- 数组相关例题
- 实现一个方法transform,以数组为参数,循环将数组中的每个元素乘以2
- 创建一个 int 类型的数组, 元素个数为 100, 并把每个元素依次设置为 1 - 100
- 给出一个整数的数组,找出这个数组的最大值
- 求一个数组的平均值
- 查找一个数组中是否包含指定元素,若存在,返回该元素的索引,不存在返回-1
- 方法1;遍历数组
- 方法二:二分查找
- 判断一个数组是否是有序数组,默认升序
- 冒泡排序
- 使一个数组逆序,逆序就是将前面的元素与后面的元素交换
- 给定一个整形数组,将所有的偶数都放在前半部分,所有奇数都放在后部分
- 给定一个整数数组nums和一个整数目标值target,请你在该数组中找出和为目标值target的那两个整数,并返回他们的数组下标
- 给定了一个非空整数数组,除了某个元素只出现了一次以外,其他元素都出现过两次,找出那个只出现了一次的元素
- 给定一个大小为n的数组,找出其中的多数元素,多数元素是指在数组中出现次数大于 n/2的元素,假设数组是非空,且给定的数组总是存在多数元素
- 给定一个整数数组arr,请你判断数组是否存在连续三个奇数,若存在返回true,不存在返回false
数组
什么是数组?(数组其实是一种数据结构)
一个定义N个相同数据类型的变量,我们就把这种结构称之为数组,数组作为第一个引用数据类型
什么是数据结构?
把一堆数字保存起来的结构就是数据结构,数据结构关心的是如何高效的读写数据
数组的创建与初始化
int[] arr = new int[]{1,2,3,4};//初始化数组时,每个元素同时赋值
int[] arr1 = new int[5];//创建数组时,若没有给元素赋值,那么每个元素都是该数据类型的默认值
数据的静态初始化
数据类型[ ]数组名称 = {初始化数据};
Int[] arr = {1,3,4};
Int[] arr = new int[]{1,3,4};
数据的静态初始化就是一个java的语法糖,javac编译以后,就是动态初始化
数组的使用
1.获取一个数组的长度(最多保存的元素个数),使用数组名称.length
int[] arr = new int[5];
System.out.println(arr.length);//打印数组长度
2.如何访问数组元素
使用数组名称[要访问的元素相较于第一个元素的偏移量]
使用数组名称[元素的索引]
int[] arr = new int[]{1,2,3};
System.out.println(arr[0]);//取得arr中的第一个元素
数组的索引从0开始,最后一个索引arr.length-1
如果打印一个不存在的元素,就会出现
索引之所以从0开始是因为索引其实就是”偏移量”,相较于第一个元素的单位长度,数组在内存中存储时,每个元素都是顺序存储的,保存的就是数组的首要地址,想要找到其他元素,只需要知道其他元素相较于第一个元素的距离就能找到
遍历每一个元素
for 循环
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,4};
for (int i = 0; i < arr.length; i++) {//此处的i表示元素的下标
System.out.print(arr[i]+" ");
}
}
for each 循环,增强型for循环
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,4};
for (int i:arr) {
System.out.print(i+" ");//此处i指的是从数组中第一个元素的拷贝,第二次循环时将第二个元素复制给i,以此类推直到遍历结束
}
}
for each循环只能读取数组的元素,无法修改
i是原数组中每个值的拷贝,并不是实实在在的数组元素
数组和方法之间的关系
1.数组作为方法的参数
关于应用数据类型的理解问题
JVM把内存划分为6个区域,有栈区,堆区…
方法的调用就是在栈区进行的每个方法的调用过程,就是一个栈帧的入栈以及出栈的过程
栈
“栈”-先进后出的结构,LIFO,方法中的局部变量和形参都在栈中存储,当方法调用结束出栈时,临时变量会被销毁
栈:程序每次调用的过程就对应栈中一个栈帧的入栈和出栈。
当方法开始调用时,入栈,方法中的局部变量都在栈中保存
当方法结束调用时,出栈,所有的局部变量就会销毁
public static void swap(int x,int y) {
int tmp = x;
x = y;
y = tmp;
System.out.println("x ="+x+" "+"y ="+y);
}
public static void main(String[] args) {
int x =1;
int y =2;
swap(x,y);
System.out.println("x ="+x+" "+"y ="+y);
}
运行结果如下
可以看出虽然在swap中交换了x和y的值,但并不影响main中的x和y的值
在栈中是这样的
先把main中x,y 的值赋给swap中的x,y的值
交换swap中x,y的值
方法swap调用结束销毁类型变量swap中的x,y
堆
JVM的另一块内存区域称为”堆区”,所有的对象都在堆中存储
数组对象,类的实例化对象,接口的对象
引用就是起个新的名称,保存的数值就是该对象的地址。
对于数组对象来说,数组引用实际上就是保存了数组首元素的地址
在数组中交换元素
public static void swap(int[] arr) {
int tmp = arr[0];
arr[0] = arr[1];
arr[1] = tmp;
System.out.println("arr[0] ="+arr[0]+" "+"arr[1] ="+arr[1]);
}
public static void main(String[] args) {
int[] arr = new int[]{1,2};
swap(arr);
System.out.println("arr[0] ="+arr[0]+" "+"arr[1] ="+arr[1]);
}
运行结果如下
在数组中交换元素是真正意义上的交换
在栈和堆中是这样的:
先将main中的arr赋给swap中的arr
再将swap中的arr进行元素的交换
方法结束调用后销毁临时变量swap中的arr
拿着swapArr方法中的数组引用swap-arr修改了堆中的数组对象的值,这个修改对于主方法中的arr是可见的
本质: 这两个引用指向了堆中的相同的内存区域
new后面的都是对象,数组的对象是堆中实实在在存在的实体
在swap中重新定义arr后交换的对象就不是main中的arr的地址所指向的对象
public static void swap(int[] arr) {
arr = new int[]{1,2};
int tmp = arr[0];
arr[0] = arr[1];
arr[1] = tmp;
System.out.println("arr[0] ="+arr[0]+" "+"arr[1] ="+arr[1]);
}
public static void main(String[] args) {
int[] arr = new int[]{1,2};
swap(arr);
System.out.println("arr[0] ="+arr[0]+" "+"arr[1] ="+arr[1]);
}
在栈和堆中如下图所示:
一开始仍然是把main中的arr传递给swap中的arr
看见new关键字,就一定在堆中开辟了新的空间
所有对象的内存释放,由JVM来进行
啥时候释放,当一个对象没任何强引用指向,且当前的JVM内存不够用时,才会释放这个对象的内存
Java中数组定义长度时可以是变量
int n=100;
int[] arr = new int[n];//此时我们使用变量n作为数组长度时,使用的这一时刻确定它的值就是100与new int[100]完全相同
数组对象转为字符串
int[] arr={1,2,3,4};
String str = Arrays.toString(arr);
System.out.println(str);
Arrays.toString(数组名称)-> 将数组转为字符串
Arrays.sort(数组名称) ->将数组排序
看到JDK中的某些类后加了s,这些类都是工具类,提供了大量的有用方法,直接调用Arrays-数组的工具类,包含各种各样的方法
以字符串形式式打印数组
public static String fun(int[] arr) {
String str ="[";
for (int i = 0; i < arr.length; i++) {
str = str+arr[i];
if(i!= arr.length-1) {
str=str+","+" ";
}
}
str=str+"]";
return str;
}
二维数组(了解即可)
所谓二维数组就是多了个列的概念
数组类型[ ][ ]数组名称=new数据类型[行数][列数]{可选的初始化数据}
复制数组
1.定义一个方法复制数组
public static int[] copyOf(int[] arr) {
int[] arr1=new int[arr.length];//定义一个新的数组复制原数组,长度和原数组相同
for (int i = 0; i < arr.length; i++) {//从第一个下标开始复制数组的元素
arr1[i]=arr[i];
}
return arr1;
}
2.使用Arrays.copyOf复制数组
Arrays.copyOf(数组名称,数组长度)
int[] arr = new int[]{1,2,3};
int[] arr2=Arrays.copyOf(arr,arr.length);//复制数组arr
3.使用Arrays.copyOfRange(数组名称,初始下标,结束下标)
取的范围是[初始下标,结束下标),所以是取不到结束下标的元素的
int[] arr = new int[]{1,2,3};
int[] arr2=Arrays.copyOfRange(arr,0,2);//复制数组arr前两个元素
System.out.println(Arrays.toString(arr2));
运行结果:
拷贝后的新数组和原数组长度的关系
1.若新数组的长度小于原数组的长度->部分拷贝
2.若新数组的长度等于原数组的长度->全拷贝
3.若新数组的长度大于原数组的长度->全拷贝,剩余的元素由该数据类型的默认值补
数组相关例题
实现一个方法transform,以数组为参数,循环将数组中的每个元素乘以2
public static void transform(int[] arr) {
for (int i = 0; i < arr.length; i++) {
arr[i]=arr[i]*2;
}
}
public static void main(String[] args) {
int[] arr = new int[]{1,2};
transform(arr);
System.out.println(Arrays.toString(arr));
}
创建一个 int 类型的数组, 元素个数为 100, 并把每个元素依次设置为 1 - 100
public static int[] buildArr() {
int[] arr = new int[100];//数组的动态初始化,在堆中开辟了100个int
for (int i = 0; i < arr.length; i++) {
arr[i]=i+1;
}
return arr;
}
public static void main(String[] args) {
System.out.println(Arrays.toString(buildArr()));
}
给出一个整数的数组,找出这个数组的最大值
public static int max(int[] arr) {
int max = arr[0];//假定数组的首元素为最大值
for (int i = 1; i <arr.length ; i++) {//从第二个元素开始
if(arr[i]>max) {//从第二个元素开始判断是否比max大
max = arr[i];//当第二个元素比max大时,就将第二个元素的值赋给max,以此类推
}
}
return max;
}
public static void main(String[] args) {
int[] arr = new int[]{1,2,3};
System.out.println(max(arr));
求一个数组的平均值
public static double avg(int[] arr) {
double sum=0;
for (int i = 0; i < arr.length; i++) {
sum+=arr[i];//用sum去取得所有元素之和
}
return sum/arr.length;
}
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,1};
System.out.println(avg(arr));
}
查找一个数组中是否包含指定元素,若存在,返回该元素的索引,不存在返回-1
方法1;遍历数组
public static int find(int[] arr,int toFind) {
for (int i = 0; i < arr.length; i++) {
if(toFind==arr[i]) {
return i;//当要找的元素找到时,返回下标i
}
}
return -1;
}
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,1};
System.out.println(find(arr,3));
}
方法二:二分查找
二分查找:在有序的集合上(升序或者降序)
在有序的区间中查找元素toFind,不断比较待查找元素和中间位置元素的大小关系,若我们要查找4这个元素对应的索引,除了简单的遍历外,我们可以从区间的中间位置比较
寻找一个元素toFind,left=0,right=arr.length-1,mid=(left+right)/2
若toFind<arr[mid],说明这个元素位于左区间,那么一定小于右区间所有的元素,所以right从mid-1开始判断
若toFind>arr[mid],说明这个元素位右区间,那么一定大于左区间所有的元素,所以left从mid+1开始判断
public static int binarySearch(int[] arr,int toFind) {
int left = 0;
int right = arr.length-1;
while(left<=right){
int mid = (left+right)/2;
if (toFind<arr[mid]) {
right=mid-1;
} else if(toFind>arr[mid]) {
left=mid+1;
} else {
return mid;
}
}
return -1;
}
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,1,7};
System.out.println(binarySearch(arr,7));
判断一个数组是否是有序数组,默认升序
所谓升序数组:前一个元素大于等于后一个元素,若在遍历数组时发现有一个元素大于后面的元素,则一定不是升序数组
方法一:
public static boolean isSortedArray(int[] arr) {
for (int i = 0; i < arr.length-1; i++) {
if (arr[i]>arr[i+1]) {//若前面的元素大于后面的元素则一定不是有序数组,返回false
return false;
}
}
return true;//遍历完数组还未发现有前面的元素大于后面的元素,返回true
}
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,1,7};
System.out.println(isSortedArray(arr));
}
注意边界问题,当循环中出现arr[i+1],就需要判断i+1<arr.length
所以循环继续的条件应该是i<arr.length-1
方法二:
冒泡排序
核心思想:假设现在数组有n个元素,每进行一次遍历过程,就将当前数组的最大值放在数组的末尾,每进行一次遍历,就有一个元素走到了最终位置
将整个数组看做两个子数组
待排序的数组[0…i-1]
排好序的数组[ ]
每当一次遍历之后,待排序的数组元素-1,排好序的数组元素+1
public static void bubbleSort(int[] arr) {
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]) {//内层循环表示每当前面的元素大于后面的元素,交换两个元素的位置
int tmp =arr[j];
arr[j]=arr[j+1];
arr[j+1]=tmp;
}
}
}
}
任何一个算法都有优化空间
当待排序数组只剩下一个元素就不需要排序了,当这个数组已经是一个有序数组也就不需要排序了
public static void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length-1; i++) {//外层循环表示一共要要遍历的次数,每经过一次遍历就有一个元素到达了最终位置
boolean isSwaped = false;//用isSwaped判断内层循环是否有元素发生了交换
for (int j = 0; j < arr.length-1-i; j++) {
if (arr[j]>arr[j+1]) {//内层循环表示每当前面的元素大于后面的元素,交换两个元素的位置
int tmp =arr[j];
arr[j]=arr[j+1];
arr[j+1]=tmp;
isSwaped = true;
}
}
if (!isSwaped){//当一个数组的内层循环没有一个元素发生交换时,说明此时已经是一个有序数组了
System.out.println("此时已经是一个有序数组了");
break;
}
}
}
使一个数组逆序,逆序就是将前面的元素与后面的元素交换
public static void reverse(int[] arr) {
int i = 0;
int j = arr.length-1;
while(i<j) {
int tmp = arr[i];
arr[i]=arr[j];
arr[j]=tmp;
i++;
j--;
}
}
while循环更多用于只知道循环终止条件,循环走多少次都不清楚
For循环明确知道循环执行的次数
给定一个整形数组,将所有的偶数都放在前半部分,所有奇数都放在后部分
只需要从前向后找奇数,从后向前找偶数,每找到一次两者进行交换
public static void transform(int[] arr) {
int i = 0;
int j = arr.length-1;
while(i<j) {
if (arr[i]%2==0&&i<j) {//从前往后找奇数,如果不是奇数接着找
i++;
}
if (arr[j]%2!=0&&i<j) {//从后往前找偶数,如果不是偶数接着找
j--;
}
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
public static void main(String args[]){
int[] arr= new int[]{1,2,3,4,5,6};
transform(arr);
System.out.println(Arrays.toString(arr));
给定一个整数数组nums和一个整数目标值target,请你在该数组中找出和为目标值target的那两个整数,并返回他们的数组下标
public static int[] twoSum(int []arr,int target) {
int[] arr1 = new int[2];//定义一个新的数组用于输出
for (int i = 0; i <arr.length ; i++) {//外层循环表示从头开始找元素
for (int j = arr.length-1; i<j ; j--) {//内层循环表示从最后开始找元素
if (arr[i]+arr[j]==target) {
arr1[0]=i;//当找到目标元素时,将下标赋值给新的数组的元素
arr1[1]=j;
return arr1;
}
}
}
return arr1;
}
public static void main(String args[]){
int[] arr= new int[]{1,2,3,4,5,6};
System.out.println(Arrays.toString(twoSum(arr, 3)));
}
给定了一个非空整数数组,除了某个元素只出现了一次以外,其他元素都出现过两次,找出那个只出现了一次的元素
方法一:
public static int singleNum(int[] arr) {
for (int i = 0; i < arr.length; i++) {//外层循环表示遍历的次数
int count=0;
for (int j = 0; j < arr.length; j++) {//内存循环找相同的元素
if (arr[i]==arr[j]) {
count++;
}
}
if (count==1) {//当找了一遍后只出现了一次的元素
return arr[i];
}
}
return -1;
}
public static void main(String args[]){
int[] arr= new int[]{1,2,3,4,5,6,2,3,4,5,6,1,0};
System.out.println(singleNum(arr));
}
方法二:异或运算
异或运算符的本质就是相同返回0,不同返回1,所以只需要将所有元素都异或一遍就能找到只出现一次的元素
public static int singleNum(int[] arr) {
int sum = 0;//定义一个sum为0,因为0异或任意数都为任意数
for (int i = 0; i < arr.length; i++) {
sum=sum^arr[i];
}
return sum;
}
public static void main(String args[]){
int[] arr= new int[]{1,2,3,4,5,6,2,3,4,5,6,1,0};
System.out.println(singleNum(arr));
}
给定一个大小为n的数组,找出其中的多数元素,多数元素是指在数组中出现次数大于 n/2的元素,假设数组是非空,且给定的数组总是存在多数元素
方法一:
public static int majorityElement(int[] arr) {
for (int i = 0; i <arr.length; i++) {
int count = 0;
for (int j = 0; j <arr.length ; j++) {
if (arr[i]==arr[j]) {//当两个元素相同,记录该元素的出现次数count++
count++;
}
}
if (count>arr.length/2) {//当出现的次数count大于数组长度的一半说明找到多数元素
return arr[i];
}
}
return -1;
}
public static void main(String args[]){
int[] arr= new int[]{1,2,3,3,4,3,3};
System.out.println(majorityElement(arr));
方法二:
一个排序的数组,某个元素至少出现了n/2次,那么该元素一定位于排序后的数组的中间位置
public static int majorityElement(int[] arr) {
Arrays.sort(arr);
return arr[arr.length/2];
}
public static void main(String args[]){
int[] arr= new int[]{1,2,3,3,4,3,3};
System.out.println(majorityElement(arr));
}
方法三:摩尔投票法
假设第一个元素为候选人,后面的数字给它投票,相同的数字票数+1,不同的票数-1,投的过程中如果票数为0了,更换当前数字为候选人继续投票,当所有人都投过票后,最后剩下的一定就是候选人
public static int majorityElement(int[] arr) {
int candidate = arr[0];//假设第一个元素为候选人
int count =1;//候选人先给自己投一票
for (int i = 1; i < arr.length ; i++) {//其他人从第二个开始投票
if (candidate==arr[i]) {//相同票数+1
count++;
} else {//不同票数-1
count--;
if (count==0) {
candidate=arr[i];
count++;//更换候选人后当前候选人给自己投一票
}
}
}
return candidate;//最后剩下的就是候选人也就是多数元素
}
public static void main(String args[]){
int[] arr= new int[]{1,2,3,3,4,3,3};
System.out.println(majorityElement(arr));
摩尔投票法的应用-众数问题
1.在一堆元素中,如果至多选择一个最多的元素,则它的票数>n/2
2.在一堆元素中,如果至多选择两个最多的元素,则它的票数>n/3
3.在一堆元素中,如果至多选择m个最多的元素,则它的票数>n/m+1
给定一个整数数组arr,请你判断数组是否存在连续三个奇数,若存在返回true,不存在返回false
public static boolean isConsectiveOdd(int[] arr){
int count =0;//用count计算连续出现奇数的次数
for (int i = 0; i < arr.length; i++) {
if (arr[i]%2!=0) {//出现奇数,count++
count++;
} else {
count=0;//出现偶数,count直接归0
}
if (count==3) {//连续出现三次奇数返回true
return true;
}
}
return false;
}
public static void main(String args[]){
int[] arr= new int[]{1,2,3,3,4,3,3,3};
System.out.println(isConsectiveOdd(arr));
}