1.数组求和
(1)问题描述
给定一个含有n个元素的整型数组a,求a中所有元素之和,要求只用一行代码
(2)代码
private static int getSum(int[] a,int endIndex)
{
return endIndex==0?a[0]:a[endIndex]+getSum(a,endIndex-1);
}
public static void main(String[] args)
{
Random random = new Random();
int[] a = new int[10];
for(int i=0; i<10; i++)
{
a[i] = random.nextInt(10);
System.out.print(a[i]+" ");
}
System.out.println();
System.out.println(getSum(a, a.length-1));
}
结果为:
6 9 5 1 0 5 4 4 0 0
34
2.数组最大值
(1)问题描述求出整型数组中的最大值,要求时间复杂度小于O(n)
(2)分析
一般情况下直接遍历一遍就找出最大值,但这里必须要使用分治的方法,将数组分为两部分,分别求出两部分的最大值再进行比较;对左右两部分再进行分割,直至每个部分中只有一个数值
(3)代码
private static int max(int[] a,int begin, int end)
{
if(begin>=end)
return a[begin];
else
{
int mid = (begin+end)>>1;
return Math.max(max(a,begin,mid-1), max(a,mid+1,end));
}
}
public static void main(String[] args)
{
Random random = new Random();
int[] a = new int[10];
for(int i=0; i<10; i++)
{
a[i] = random.nextInt(10);
System.out.print(a[i]+" ");
}
System.out.println();
System.out.println(max(a,0,a.length-1));
}
结果为:
4 7 4 2 1 8 9 0 7 2
9
3.数组中出现次数超过一半的数字
(1)思路1
使用HashMap记录所有数字的出现次数,找出其中出现次数最多的数字
代码:
Scanner cin = new Scanner(System.in);
int m = cin.nextInt();
int n = cin.nextInt();
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for(int i=0; i<m; i++)
{
for(int j=0; j<n; j++)
{
int key = cin.nextInt();
if(map.get(key)==null)
map.put(key, 1);
else
{
int count = map.get(key);
if(count>=half)
{
System.out.println(key);
break;
}
else
map.put(key, ++count);
}
}
}
(2)思路2
因为出现次数超过一半,所以对所有数字进行排序,中间的元素必定就为所求
代码:
public static void main(String[] args)
{
Scanner cin = new Scanner(System.in);
int m = cin.nextInt();
int n = cin.nextInt();
int[] a = new int[m*n];
int half = (m*n)>>1;
int index = 0;
for(int i=0; i<m; i++)
{
for(int j=0; j<n; j++)
{
a[index] = cin.nextInt();
index++;
}
}
Arrays.sort(a);
System.out.println(a[half]);
}
4.数组中距离最近的两个数字(即差的绝对值最小的两个数字)
(1)思路1
对数组中所有数字进行排序,再遍历一遍,记录相邻数字绝对值的最小值
(2)代码
public static void main(String[] args)
{
Random random = new Random();
int[] a = new int[10];
for(int i=0; i<10; i++)
{
a[i] = random.nextInt(100);
System.out.print(a[i]+" ");
}
System.out.println();
int minAbs = Integer.MAX_VALUE;
Arrays.sort(a);
for(int i=1; i<a.length; i++)
{
if(Math.abs(a[i]-a[i-1])<minAbs)
minAbs = Math.abs(a[i]-a[i-1]);
}
System.out.println(minAbs);
}
结果为:
87 25 6 24 87 98 30 92 77 98
0
(3)思路2
使用动态规划,dp[i]表示数组a中从0到i个元素中差绝对值的最小值,min表示a[i]与a[0]到a[i-1]差的绝对值的最小值,那么状态转移方程就为:dp[i] = dp[i-1] , dp[i-1]<min ;dp[i] = min , dp[i-1]<min
(4)代码2
int[] a = {1,-6,5,3,13,7,-8};
int[] dp = new int[a.length];
for(int i=0; i<dp.length; i++)
dp[i] = Integer.MAX_VALUE;
for(int i=1; i<a.length; i++)
{
int min = Integer.MAX_VALUE;
for(int j=0; j<i; j++)
{
if(Math.abs(a[i]-a[j]) < min)
min = Math.abs(a[i]-a[j]);
}
dp[i] = dp[i-1]<min?dp[i-1]:min;
}
System.out.println(dp[a.length-1]);
5.数组中小于0的数字替换为-100,-100,-100三个数字
(1)思路
因为替换过后数组长度会变长,所以先遍历一遍找出其中小于0的数字的个数,这样就求出了新数组的长度。然后使用两个指针,分别指向原数组的结尾和新数组的结尾
(2)代码
int count = 0;
for(int i=0; i<a.length; i++)
{
if(a[i]<0)
count++;
}
int[] b = new int[a.length+count*2];
int pb = b.length-1;
for(int i=a.length-1; i>=0; i--)
{
if(a[i]>=0)
{
b[pb] = a[i];
pb--;
}
else
{
b[pb--] = -100;
b[pb--] = -100;
b[pb--] = -100;
}
}
6.求递增数组中的2个数字,使得这2个数字的和为指定的值,时间复杂度为O(n)
(1)思路
同时使用两个指针,指向数组的开头和结尾,根据指向数据的和的情况来改变指针移动的方向
(2)代码
int[] a = {1,2,4,7,11,15};
int begin = 0;
int end = a.length-1;
while(begin<end)
{
if(a[begin]+a[end]>15)
end--;
else if(a[begin]+a[end]<15)
begin++;
else
break;
}
if(begin<end)
System.out.print(a[begin]+" "+a[end]);
总结:一般可以通过增加指针来降低时间复杂度
(3)扩展
1)问题描述
给定一正整数n,求所有和为n的连续正数序列(至少两个数)
2)思路
如上,使用两个指针,因为这次需要找出多种方案,所以就不再指向最大和最小的了,指向第一个和第二个,分别为begin、end,记tempSum为a[begin]+.....+a[end]
1>如果tempSum<n,那么end后移一位,即增大最大的元素
2>如果tempSum>n,那么begin后移一位,即去掉最小的元素
3>如果tempSum=n,那么end后移一位(因为如果begin后移那么只是子序列,里面数字完全没有变)
3)代码
int[] a = {1,2,3,4,5,6};
int s = 9;
int begin = 0;
int end = 1;
while(end<a.length && begin<end)
{
int sum = getSum(a, begin, end);
if(sum < s)
end++;
else if(sum > s)
begin++;
else
{
for(int i=begin; i<=end; i++)
{
if(i==begin)
System.out.print(a[i]);
else
System.out.print("+"+a[i]);
}
System.out.println();
end++;
}
}
private static int getSum(int[] a, int begin, int end)
{
int sum = 0;
for(; begin<=end; begin++)
sum += a[begin];
return sum;
}
结果为:
2+3+4
4+5
7.查找递增数组中指定数字出现的次数
(1)思路
因为数组是递增的,可以使用类似二分查找+分治方法
1>如果中间元素=find,那么次数=左边区间出现次数+右边区间出现次数+1;
2>如果中间元素<find,说明查找的元素在右边区间中,那么次数=右边区间出现次数
3>如果中间元素>find,说明查找的元素在左边区间中,那么次数=左边区间出现次数
(2)代码
private static int count(int[] a, int find,int begin, int end)
{
if(begin<=end)
{
int mid = (begin+end)/2;
if(a[mid] == find)
return count(a, find, begin, mid-1)+count(a,find,mid+1,end)+1;
else if(a[mid]<find)
return count(a,find,mid+1,end);
else
return count(a,find,begin,mid-1);
}
else
return 0;
}
8.数组中数字向右循环移动k位
(1)思路
引入:输入一个字符串,将该字符串中的所有单词逆序,但是单词内部不逆序。如“hello my java world”输出“world java my hello”
可以使用先将整个字符串逆序“dlrow avaj ym olleh”,再将各个单词逆序“world java my hello”
在这里 1,2,3,4右移一位可得4,1,2,3,就相当于先将整个数组逆序4,3,2,1,再将两个子数组逆序
(2)代码
<span style="white-space:pre"> </span>private static void reverse(int[] a, int begin, int end)
{
while(begin<end)
{
int temp = a[begin];
a[begin] = a[end];
a[end] = temp;
begin++;
end--;
}
}
<span style="white-space:pre"> </span>int[] a = {1,2,3,3,3,3,4,5};
int k = 2;
reverse(a, 0, a.length-1);
reverse(a, 0, k-1);
reverse(a, k, a.length-1);
for(int x:a)
System.out.print(x+" ");
结果为:
4 5 1 2 3 3 3 3
9.数组中只出现1次的数字(其他数字均出现2次)
(1)思路1
a^a=0 a^0!=a,所以对整个数组异或操作,最后的结果就是出现1次的数组。
(2)代码
int temp = 0;
for(int i=0; i<a.length; i++)
temp = temp ^ a[i];
System.out.println(temp);
扩展1:数组中有2个数字出现1次,其余均出现2次,找出2个数字
先异或一次,得出的就是这两个数字异或的结果,如果结果中某一位为1,则说明这两个数字这一位不同,一个为1,一个为0,那么就根据这一位的不同,将所有数组中数据分为两组,分别找出这两组中唯一出现一次的数字即可。
扩展2:寻找字符串中第一个只出现一次的字符。eg“abeaksks”,则输出“b”
直接使用哈希表
String a = "abaccdeff";
Map<Character,Integer> map = new HashMap<Character, Integer>();
for(int i=0; i<a.length(); i++)
{
char key = a.charAt(i);
if(map.containsKey(key))
{
int count = map.get(key);
map.put(key, ++count);
}
else
map.put(key, 1);
}
//因为HashMap不是按存入的顺序存放的,所以必须查找出现一次且位置最靠前的那个
int index = a.length();
for(Entry<Character,Integer> entry:map.entrySet())
{
if(entry.getValue()==1)
{
if(index > a.indexOf(entry.getKey()))
index = a.indexOf(entry.getKey());
}
}
if(index == -1)//如果不存在只出现一次的
System.out.println("none");
else
System.out.println(a.charAt(index));
10.数组中有0和非0元素,重排数组使得所有0在前面,非0在后面且非0顺序不变
(1)思路1
从后向前,依次把非0移动到最后,循环完成之后其余直接置0
(2)代码
<span style="white-space:pre"> </span>int[] a = {1,1,0,2,0,3,3,0,0,4,4,5,5,0};
//查找倒数第一个0的元素的下标
int isZero = a.length-1;
for(int i= a.length-1; i>=0; i--)
{
if(a[i] == 0)
{
isZero = i;
break;
}
}
//每一个非0元素向0元素赋值,同时指向0元素的指针向前
for(int i= isZero-1; i>=0; i--)
{
if(a[i] != 0)
{
a[isZero] = a[i];
isZero--;
}
}
for(int i=0; i<=isZero; i++)
a[i] = 0;
11.调整数组中元素,使得奇数在偶数前面
(1)思路
类似于快排的调整,设置两个指针,分别指向开始和结尾,前者指向的为奇数,后者指向的为偶数,交换两者,直至两个指针相遇
(2)代码
int[] a = { 1, 2, 3,4,5};
int odd = 0;
int even = a.length-1;
while(odd < even)
{
while(a[even]%2==0 && even>odd)
even--;
while(a[odd]%2!=0 && odd<even)
odd++;
if(odd < even)
{
int temp = a[odd];
a[odd] = a[even];
a[even] = temp;
odd++;
even--;
}
}
for(int x:a)
System.out.print(x+" ");
12.合并两个有序数组
(1)思路
先不管谁先结束,直接合并,然后再判断谁结束了
(2)代码
int[] a1 = {1,3,5,7};
int[] a2 = {2,4,6};
int[] a = new int[a1.length+a2.length];
int i = 0;
int j = 0;
int k = 0;
while(i<a1.length && j<a2.length)
{
if(a1[i]<a2[j])
{
a[k] = a1[i];
i++;
}
else
{
a[k] = a2[j];
j++;
}
k++;
}
if(i==a1.length)
{
for(;j<a2.length; j++,k++)
a[k] = a2[j];
}
else if(j==a2.length)
{
for(;i<a1.length; i++,k++)
a[k] = a1[i];
}
for(int x:a)
System.out.print(x+" ");
}
结果为:
1 2 3 4 5 6 7