目录
1. 顺序查找数组中的某个元素
public class Note {
public static int findArr(int[] arr, int n) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == n) {
return i;
}
}
return -1;
}
public static void main(String[] args) {
int[] arr = new int[]{1,3,5,7,9};
int ret = findArr(arr,3);
if (ret >= 0) {
System.out.println("找到了,元素下标为:" + ret);
} else {
System.out.println("没找到");
}
}
}
结果:找到了,元素下标为:1
这个程序比较简单,只要依次去比较数组元素的值和我们需要的值是否相等,就能得出答案
2. 二分查找
前情提要:数组必须是有序的
public static int binarySearch(int[] arr,int num) {
int low = 0;
int high = arr.length - 1;
while (low <= high) {
int mid = (low + high) / 2;
if(arr[mid] == num) {
return mid;
} else if (arr[mid] > num) {
high = mid -1;
} else {
low = mid + 1;
}
}
return -1;
}
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,4,5,6,7,8};
int ret = binarySearch(arr,3);
if(ret >= 0) {
System.out.println("找到了,下标为:" + ret);
} else {
System.out.println("没找到");
}
}
二分查找还可以使用递归地方法
public static int binarySearchRecursion(int[] arr,int num,int low,int high){
//先找终止条件,不需要任何辅助
if (low > high){
//区间中没有元素,没找到
return -1;
}
//区间中还有元素
int mid = (low + high) / 2;
if (arr[mid] == num) {
return mid;
} else if(arr[mid] > num) {
//要找的元素小于中间值,说明在左区间
return binarySearchRecursion(arr,num,low,mid - 1);
}else{
//要找的元素大于中间值,说明在右区间
return binarySearchRecursion(arr,num,mid + 1,high);
}
}
3. 冒泡排序
升序的冒泡排序就是将”大数“不断往上冒的过程
public class Note {
/**
* 冒泡排序
*/
public static int[] bubbleSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
boolean isOrder = false;
for (int j = 0; j < arr.length - 1 - i; j++) {
if(arr[j] > arr[j+1]) {
isOrder = true;
int tmp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = tmp;
}
}
if(isOrder = false) {
break;
}
}
return arr;
}
public static void main(String[] args) {
int[] arr = new int[]{7,5,9,12,10,21,15,13};
int[] ret = bubbleSort(arr);
System.out.println(Arrays.toString(ret));
}
}
结果:[5, 7, 9, 10, 12, 13, 15, 21]
上面图片是一次冒泡排序的过程,n个元素的冒泡排序需要n-1趟冒泡。
上述i的取值小于数组元素长度-1的原因:当数组内只剩一个未排序的元素,代表元素已经有序,不用再进行一趟冒泡。
上述j的取值小于数组元素长度-1 -i 的原因:每当完成一趟冒泡后,最大数已经放到后面有序了,不必要在进行冒泡了。
我们可以设置一个标志位来判断数组内元素是否有序,若已经有序,就可以退出循环了。
数组的toString方法是将数据元素转换成字符串输出。
优化代码是不断进步的过程。
4.给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
输入: [2,2,1]
输出: 1
public static int findOnce(int[] arr) {
int ret = arr[0];
for (int i = 1; i < arr.length; i++) {
ret = ret ^ arr[i];
}
return ret;
}
public static void main(String[] args) {
int[] arr = new int[]{2,2,1};
int ret = findOnce(arr);
System.out.println(ret);
}
}
这个代码的核心思想是把数组内所有元素的值整体异或。因为异或是相同为0,不同为1。如果两个相同的数字异或,其结果必定为0。0与任何数字异或就等于数字本身。所以所有元素异或之后,得出的结果就一定是只出现了一次的元素。
这种题目还有其他解法,欢迎大家讨论。
5.多数元素
题目:给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
第一个方法:双引用+计数
public static int findSev(int[] arr) {
int times = arr.length / 2;
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 > times) {
return arr[i];
}
}
return -1;
}
public static void main(String[] args) {
int[] arr = new int[]{2,2,1,1,1,2,2};
int ret = findSev(arr);
System.out.println(ret);
}
}
结果:2
我们只需要假设一个元素出现次数大于一半,然后从前往后遍历数组,计数和这个元素相等的元素,判断次数是否大于一半,即可返回值。
第二种方法:排序加输出
因为一个元素的出现次数大于一半,可推出这个元素一定在排序后的中间位置。
public class Note {
public static int findSev(int[] arr) {
Arrays.sort(arr);
return arr[arr.length >> 1];
}
public static void main(String[] args) {
int[] arr = new int[]{2, 2, 1, 1, 1, 2, 2};
int ret = findSev(arr);
System.out.println(ret);
}
}
第三种方法(重点)–摩尔投票法
核心思想:
我们可以把这想象成投票,我先设置第一个元素为候选者,后面的元素对他投票,自己给自己投一票,所以起始票数为1.
设计一个计数器去接收票数,如果后面元素和他相等,就是给他投了一票,计数器+1;反之,后面元素和他不相等,就是没给他投票,计数器减一。
当计数器为0的时候,当前给他投票的元素称为新的候选者。因为当所有人投完票,即正票数和负票数的抵消问题,出现次数大于一半的元素的得票数一定是>=1的,所以计数器等于0的时候,就可以换人了。
但也有可能这组元素没有多数,也会造成计数器=1的情况。例如arr = [1,2,3]。
所以我们要对这个元素进行验证,只要遍历一遍数组,和他这个元素相等的元素大于一半,即这个元素就是多数。
public class Note {
public static int findSev1(int[] arr) {
int count = 1;
int candidate = arr[0]; //设置第一个元素为候选者
for (int i = 1; i < arr.length; i++) {
if(arr[i] == candidate) {
count++;
} else if (count == 0){
candidate = arr[i];
count = 1;
} else{
count--;
}
}
//验证
count = 0;
for (int i = 0; i < arr.length; i++) {
if (arr[i] == candidate) {
count++;
}
}
if (count > (arr.length / 2)) {
return candidate;
}
return -1;
}
public static void main(String[] args) {
int[] arr = new int[]{2, 2, 1, 1, 1, 2, 2};
int ret = findSev1(arr);
System.out.println(ret);
}
}
这种类型的题目可以推广成:在一堆元素中,如果至多选择m个最多的元素,则他的出现次数 > n / (m + 1)
也可以写成:寻找一个数组中出现次数超过1/k的元素,满足要求的元素最多有k-1个
下面写一个m = 2的代码
public static int[] findSev2(int[] arr) {
int count1 = 1;
int count2 = 0;
int candidate1 = arr[0];
int candidate2 = arr[1];
for (int i = 1; i < arr.length; i++) {
if (arr[i] == candidate1){
count1++;
} else if (arr[i] == candidate2){
count2++;
} else if(count1 == 0){
candidate1 = arr[i];
count1 = 1;
} else if(count2 == 0){
candidate2 = arr[i];
count2 = 1;
} else {
count1--;
count2--;
}
}
count1 = 0;
count2 = 0;
int[] ret = new int[2];
for (int i = 0; i < arr.length; i++) {
if(arr[i] == candidate1) {
count1++;
} else if (arr[i] == candidate2) {
count2++;
}
}
if (count1 > arr.length / 3) {
ret[0] = candidate1;
}
if (count2 > arr.length / 3) {
ret[1] = candidate2;
}
return ret;
}
public static void main(String[] args) {
int[] arr = new int[]{1,1,1,2,2,3,3,3};
int[] ret = findSev2(arr);
System.out.println(Arrays.toString(ret));
}
}
6.移除元素(leetcode27)
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
public int removeElement(int[] nums, int val) {
int fir = 0;
int sec = 0;
while(sec < nums.length){
if(nums[sec] != val){
nums[fir] = nums[sec];
fir ++;
}
sec ++;
}
return fir;
}
这道题我们可以采用双引用的解法(快慢指针),fir(慢指针),sec(快指针)。我们用快指针找到与删除元素不相等的元素,进行元素覆盖,慢指针用来接收不相等的元素,相当于相等的元素被覆盖了,就实现了元素的删除。
7.删除有序数组中的重复项(leetcode26)
给你一个 升序排列的数组 nums ,请你原地删除重复出现的元素,使每个元素只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持一致 。
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
public static int removeDuplicates(int[] nums) {
int n = nums.length;
if (n == 0){
return 0;
}
int fir = 1;
int sec = 1;
while (sec < nums.length){
if (nums[sec] != nums[sec - 1]){
nums[fir] = nums[sec];
fir++;
}
sec++;
}
return fir;
}
这道题也可以用双引用的方法,我们可以从第二元素开始,因为第一个元素一定是需要留下来的元素,所以我们用fir和sec指向第二个元素。用快慢指针,nums[sec]与前一个元素比较,相等的话向后移,不相等的话,将他覆盖到fir指向的元素。我们只要保证nums[0…fir-1]保留一个重复元素就可以了。
8.移动零(leetcode283)
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
public void moveZeroes(int[] nums) {
int fir = 0;
int sec = 0;
while(sec < nums.length){
if(nums[sec] != 0){
nums[fir] = nums[sec];
fir ++;
}
sec ++;
}
for(int i = fir;i < sec; i++){
nums[i] = 0;
}
}
这道题和上面的解法大致相同,也是双引用,当sec不等于零时,去覆盖掉fir指向的元素。循环结束后,nums[0…fir-1]的元素肯定不等于零。然后我们就可以把后面元素赋值为零就可以了。
9.总结
摩尔投票算法和双引用是常用的基本算法之一,需要深入理解。双引用做多了会习惯的。数组变幻莫测,需要继续努力。