文章目录
1.二维数组中的查找
题目
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
答案
这道题的考察点也就是在一个二维数组中查找指定的数是否存在。
可以从数组的左下角来看,向右递增,向上递减,所以就可以从左下角开始查找,当要查的数比左下角小时上移,大时右移。
package jzOffer;
public class Solution {
public static void main(String[] args) {
int[][] array = {
{1, 3, 5, 7, 9 },
{2, 4, 6, 8, 10}
};
boolean result = Find(11, array);
System.out.println(result);
}
public static boolean Find(int target, int [][] array) {
int lenX = array.length;
int lenY = array[0].length;
for(int i = lenX - 1, j = 0; i >= 0 && j < lenY; ) {
if(array[i][j] > target) {
i--;
}else if (array[i][j] < target) {
j++;
}else {
return true;
}
}
return false;
}
}
2.数组中重复的数字
题目
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2
答案
(1)用额外空间的方法
/**
* 利用hashmap,很简单
* @param numbers
* @param length
* @param duplication
* @return
*/
public boolean duplicate1(int numbers[],int length,int [] duplication) {
HashMap<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < length; i++) {
if (map.containsKey(numbers[i])) {
duplication[0] = numbers[i];
return true;
}
map.put(numbers[i], 0);
}
return false;
}
(2)重排此数组(与平常的排序不一样)
思路:比较好的思路的分析为,数组中的数字为0到n-1的范围内。如果这个数组中没有重复的数字,则对应的i位置的数据也为i。可以重排此数组
/**
* 重排法
* @param numbers
* @param length
* @param duplication
* @return
*/
public boolean duplicate2(int numbers[],int length,int [] duplication) {
int i = 0;
int temp = 0;
while(i < length) {
if(numbers[i] == i) {
i++;
}else {
temp = numbers[i];
if (numbers[i] == numbers[temp]) {
duplication[0] = numbers[i];
return true;
}
numbers[i] = numbers[temp];
numbers[temp] = temp;
}
}
return false;
}
3.构建乘积数组
题目
给定一个数组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]。不能使用除法。
答案
思路:构建出一个矩阵的样子,但是不用计算举证的方法,还是用原来的方法,只不过分两个for()
来计算。
/**
* 构建乘积数组
* @param A
* @return 构建后的数组
*/
public int[] multiply(int[] A) {
int n = A.length;
int[] B = new int[n];
B[0] = 1;
for(int i = 1; i < n; i++) {
B[i] = B[i - 1] * A[i - 1];
}
int temp = 1;
for(int i = n - 1; i >= 0; i-- ) {
B[i] = temp * B[i];
temp = temp * A[i];
}
return B;
}
4.数组中只出现一次的数字
题目
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
答案
思路:利用异或操作。同时还要注意:移位、取反等位运算。
/**
* 数组中只出现一次的数组
* @param array
* @param num1,num2 分别为长度为1的数组。传出参数
* 将num1[0],num2[0]设置为返回结果
*/
public static void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
int n1 = 0;
int temp = 0;
for (int j : array)
temp ^= j;
//这知识点我原来不知道,还用取余,取商,循环判断,多蠢!!!
int sign = temp & (~temp + 1);//求得二进制中第一位1,比如101和011得到010
for (int i : array) {
if((sign & i) != 0)
n1 ^= i;
}
num1[0] = n1;
num2[0] = n1 ^ temp;
}
5.和为S的两个数
题目
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
答案
思路:在数组中找到两个数,与S
比较,如果相等我们就找到了;如果比S
小,我们就想办法让这两个数的和变大一点;如果比S
大,我们就希望让两个数的和变小一点。
由于数组中的数是递增排好序的,我们可以定义两个指针,left
指向最左边,right
指向最右边,用left
和right
所指的数的和与S
比较:如果“和”小于S
,就让left
右移;如果“和”大于S
,就让right
左移;直到找到与S
相等的值或者left == right
题目中说了:输出的两个输的乘积最小。
由于数组时有序数组,所以说:
/**
* 和为S的两个数
* @param array
* @param sum
* @return
*/
public static ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
int left = 0;
int right = array.length - 1;
ArrayList<Integer> list = new ArrayList<>();
while(left < right) {
int small = array[left];
int big = array[right];
if(small + big == sum) {
list.add(small);
list.add(big);
return list;
}else if(small + big < sum) {
left++;
}else {
right--;
}
}
return list;
}
6.和为S的正数序列
题目
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
答案
- 思路:可以模仿上一题的方式,定义两个
small
和big
指针用来滑动。
首先让small
指向1
,big指向2
然后:
求出small -> big
的序列和curSum
,包括small
和big
。
如果curSum == S
,保存这个序列,让big
右移,寻找下一个序列。
如果curSum > S
,让small
左移,减小curSum
的值。
如果curSum < S
,让big
右移,增大粗人Sum
的值。
循环上面步骤,直到small的值大于S值的一半。
/**
* 和为S的正数序列
* @param sum
* @return
*/
public static ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
ArrayList<Integer> alist = new ArrayList<Integer>();
int small = 1;
int big = 2;
while(small <= (sum + 1)/2) {
int curSum = 0;
for(int i = small; i <= big; i++)
curSum += i;
if(sum == curSum) {
for(int i = small; i <= big; i++)
alist.add(i);
result.add(new ArrayList<Integer>(alist));
alist.clear();
big++;
}else if(curSum > sum) {
small++;
}else {
big++;
}
}
return result;
}
7.数组中出现次数超过一半的数字
题目
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
答案
思路:
一次在数组中删除两个不同的数,最后剩下的数 有可能
就是是超过一半的。所以要检验一下。 一个数出现次数大于一半,他肯定会被剩下来,但是剩下来的缺不一定满足。
算法步骤:
如果times为0,就把候选设为当前值。
如果下个数和候选一样,times就++。
如果下个数和候选不一样,times就–。相当于对子,同归于尽。因为超过一半的数肯定超过剩下的所有数。所以和这个数对,这个数肯定会剩下来。
但是剩下的数不一定是,比如 1 2 3 剩下3 比如 1 2 1 3 3 3 2 2 也是剩下3.所以要余外的判断,看是否这个数真的超过。
public int MoreThanHalfNum_Solution(int [] array) {
int num = 0;
int times = 0;
for (int i : array) {
if(times == 0) {
num = i;
times = 1;
}else if(num == i) {
times++;
}else {
times--;
}
}
times = 0;
for (int i : array) {
if(num == i)
times++;
if(times > array.length/2)
return num;
}
return 0;
}
8.连续子数组的最大和
题目
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
答案
思路:
不是看当前的值,而是看当前的累计值,
如果当前累计为负数,那么加上现在这个数,肯定和就小了,
所以重新从当前值开始累计
如果为正,那就继续加,
但是要时刻保存下最大值来,因为后面的累计有可能小。
public int FindGreatestSumOfSubArray1(int[] array) {
if(array.length == 0)
return 0;
int total = array[0]; //当前累计和
int maxsum = array[0]; //当前最大累计和
for (int i = 1; i < array.length; i++) {
if(total >= 0) //只要total的值大于零,对于后面的数来说,它就有相加的意义。
total = total + array[i];
else
total = array[i];
if(total>maxsum)
maxsum = total;
}
return maxsum;
}
9.调整数组的顺序使奇数位于偶数前面
题目
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
答案
(1)使用额外空间,很简单,没什么好说的
public static void reOrderArray(int [] array) {
ArrayList<Integer> jList = new ArrayList<>();
ArrayList<Integer> oList = new ArrayList<>();
for (int i : array) {
if(i % 2 == 1)
jList.add(i);
else
oList.add(i);
}
jList.addAll(oList);
int i = 0;
for (Integer j : jList) {
array[i++] = j.intValue();
}
}
(2)使用滑动变量
思路:
设置两个滑动变量start和end
让strat从数组的最左边开始向右滑动,遇到第一个偶数停止,并指向它
让end从strat+1开始向右滑动,遇到第一个奇数位停止,并指向它
然后,将数组的start -> end - 1这段值整体向右移动一位,再把end所指的值放到空出来的那个位置。
注意移位的时候,要把将要覆盖的那个位置的值提前取出来放到一个中间变量里面:
int temp = array[end];
for(int i = end; i > strat; i--)
array[i] = array[i - 1];
array[strat] = temp;
代码如下:
public void reOrderArray(int [] array) {
int strat = 0;
int end = 0;
int len = array.length;
while(strat < len) {
while(strat < len && (array[strat] & 1) == 1)
strat++;
end = strat + 1;
while(end < len && (array[end] & 1) == 0)
end++;
if(end < len ) {
int temp = array[end];
for(int i = end; i > strat; i--)
array[i] = array[i - 1];
array[strat] = temp;
}
else {
break;
}
}
}
10. 把数组排成最小的数
题目
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
答案
对于给定的2个数,a和b。如何确定两者之间的排序策略呢?我们可以发现这两者的排列为:ab,ba。我们最终目的是找到字典序最小的那个排列,所以我们肯定应该保持这种关系,从而决定是否交换顺序:
- 当ab < ba, a排在b的左边
- 当ab > ba, b排在a的左边
- 当ab = ba, 位置关系随意
/**
* 把数组排成最小的数
* @param numbers
* @return
*/
public static String PrintMinNumber(int [] numbers) {
int len = numbers.length;
String[] str = new String[len];
StringBuffer buf = new StringBuffer();
for(int i = 0; i < len; i++) {
str[i] = String.valueOf(numbers[i]);
}
Arrays.sort(str, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return (o1 + o2).compareTo(o2 + o1);
}
});
for (String st : str)
buf.append(st);
return buf.toString();
}
11.数组中的逆序对
题目
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
输入描述:
题目保证输入的数组中没有的相同的数字
数据范围:
对于%50的数据,size<=10^4
对于%75的数据,size<=10^5
对于%100的数据,size<=2*10^5
示例1
输入 :
1,2,3,4,5,6,7,0
输出 :
7
答案
思路:用了归并的思想,也就是变相的写一个归并排序。
不知道归并看这里:https://blog.csdn.net/qq_42570601/article/details/97147158
下面和一般的归并实现不同的地方在于,一般咋们使用归并的时候,往新数组中存放时是从头开始,也就是++
,这里是从尾开始,用的--
。
然后要注意的是:这段代码的意义!
if (leftA[i] > rightA[j]) {
count = count + j + 1;
count %= 1000000007;
newA[k--] = leftA[i--];
} else {
newA[k--] = rightA[j--];
}
如果说左边的数组是1,4,8,右边的数组是2,5,6
当8 > 6 时,就有三个逆序对。
当4 > 2 时,就有一个逆序对。
代码如下:
/**
*
* @param array
* @return
*/
int count = 0;
public int InversePairs(int[] array) {
if (array == null || array.length == 0)
return 0;
mergeSort(array, 0, array.length - 1);
return count;
}
public int[] mergeSort(int[] array, int start, int end) {
if (start == end)
return new int[] { array[start] };
int mid = (start + end) / 2;
int[] leftA = mergeSort(array, start, mid);
int[] rightA = mergeSort(array, mid + 1, end);
int i = leftA.length - 1;
int j = rightA.length - 1;
int[] newA = new int[i + j + 2];
int k = newA.length - 1;
while (i >= 0 && j >= 0)
if (leftA[i] > rightA[j]) {
count = count + j + 1;
count %= 1000000007;
newA[k--] = leftA[i--];
} else {
newA[k--] = rightA[j--];
}
while (i >= 0)
newA[k--] = leftA[i--];
while (j >= 0)
newA[k--] = rightA[j--];
return newA;
}
12.数字在排序数组中出现的次数
题目
统计一个数字在排序数组中出现的次数。
答案
思路:题目中说了,已经排好序,二分法一半是跑不了的。
可以让一个方法采用二分查找,先找到一个与k值想等的位置,再判断k的前一个位置对应的值是否为k,如果不是,则返回这个位置的下标(就是第一个出现的位置),如果是,则抛弃后半段,再从前半段中继续查找。注意巧妙的避免数组下标越界!!!
同样的方式找到最后一次出现的位置,就很容易得到想要的结果。
/**
* 数字在排序数组中出现的次数
* @param array
* @param k
* @return
*/
public static int GetNumberOfK(int[] array, int k) {
return getLast(array, k) - getFrist(array, k) + 1;
}
//找到 K 第一次出现的位置
public static int getFrist(int num[], int k) {
int left = 0;
int right = num.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (k < num[mid]) {
right = mid - 1;
} else if (k > num[mid]) {
left = mid + 1;
} else {
if (mid == 0 || num[mid - 1] != k) {
return mid;
} else {
right = mid - 1;
}
}
}
return 0;
}
//找到 K 最后一次出现的位置
public static int getLast(int num[], int k) {
int left = 0;
int right = num.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (k < num[mid]) {
right = mid - 1;
} else if (k > num[mid]) {
left = mid + 1;
} else {
if (mid == num.length - 1 || num[mid + 1] != k) {
return mid;
} else {
left = mid + 1;
}
}
}
return -1;
}