题目描述
和为S的两个数字:
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
输出描述:
对应每个测试案例,输出两个数,小的先输出。
因为给出的数组是排好序的,所以我们就可以在有序数组中放两个指针用于查找符合条件的两个数字。首先定义两个指针,第一个指针指向数组的第一个(最小的)数字,第二个指针指向数组的第二个(最大的)数字。如果这两个数字的和大于给定的数字S,就把第二个指针向前移动一个数字。如果这两个数字的和大于给定的数字S,就把第一个指针向后移动一个数字。依次类推,直到找到符合条件的两个数字。这只是找到的第一个,因为要寻找两个数的乘积最小的数字。如果两个数的和相等,那么两个数的差值越大,乘机就越小,因此通过指针移动找到的第一个数对的乘机是最小的。因为再移动指针的话,第一个指针向后移,第二个指针向前移,差值就不能那么大了,他们的乘积自然也就不是最大的了。
分析完问题,我们就可以动手开始写代码了。
查找递增数组中和为S的两个数字的Java代码如下:
import java.util.ArrayList;
public class Solution {
public static void main(String[] args) {
int[] num = {1,2,4,7,11,15};
ArrayList<Integer> array = FindNumbersWithSum(num,15);
for(int i=0;i<array.size();i++){
System.out.println(array.get(i));
}
}
public static ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
ArrayList<Integer> arrayList = new ArrayList<Integer>();
if(array.length == 0){
return arrayList;
}
int i;
int j;
for(i=0,j=array.length-1;i<array.length && j>=0 && i<j;){
if(array[i]+array[j]>sum){
j--;
}else if(array[i]+array[j]<sum){
i++;
}else{
arrayList.add(array[i]);
arrayList.add(array[j]);
break;
}
}
return arrayList;
}
}
程序执行结果:
因为代码中只有一个for循环从两端向中间扫描数组,对数组中的元素至多只遍历一次,因此该算法的时间复杂度为O(n)。
分析完该问题,还有一个和该问题类似的查找数组和的数对问题。
题目描述
和为S的连续正数序列:
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
输出描述:
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序。
剑指offer上的描述:
输入一个正数s,打印出所有和为s的连续正数序列(至少含两个数)。例如,输入15,由于1+2+3+4+5=4+5+6=7+8=15,所以打印出三个连续序列1~5,4~6,7~8。
有了解决前面问题的经验,我们也考虑用两个数small和big分别表示序列的最小值和最大值。首先把small初始化为1,big初始化为2。如果从small到big的序列的和大于s,则可以从序列中去掉较小的值,也就是增大small的值。如果从small到big的序列的和小于s,则可以增大big,让这个序列包含更多的数字。因为这个序列至少要有两个数字,我们一直增加small到(1+s)/2为止。
以求和为9的所有连续序列为例,我们先把small初始化为1,big初始化为2,此时介于small和big之间的序列是{1,2},序列的和为3,小于9,所以我们下一步要让序列包含更多的数字。我们把big增加1变成3,此时序列为{1,2,3}。由于序列的和是6,仍然小于9,我们接下来再增加big变成4,介于small和big之间的序列也随之变成{1,2,3,4}。由于序列的和10大于9,我们要删去序列中的一些数字,于是我们增加small变成2,此时得到的序列是{2,3,4},序列的和正好是9。我们找到了第一个和为9的连续序列,把它打印出来。接下来我们再增加big,重复前面的过程,可以找到第二个和为9的连续序列{4,5}。
形成了清晰的解题思路之后,我们就可以开始写代码了。
查找和为S的连续正数序列的所有序列的Java代码如下:
import java.util.ArrayList;
public class Solution {
public static void main(String[] args) {
int[] num = {1,2,4,7,11,15};
ArrayList<ArrayList<Integer>> arrayLists = FindContinuousSequence(15);
for(int i=0;i<arrayLists.size();i++){
for(int j=0;j<arrayLists.get(i).size();j++){
System.out.print(arrayLists.get(i).get(j)+" ");
}
System.out.println();
}
}
public static ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
int small = 0;
int big = 0;
ArrayList<ArrayList<Integer>> arrayLists =new ArrayList<ArrayList<Integer> >();
if(sum<3){
return arrayLists;
}
int temp = 0;
for(small = 1,big = 2;small<(sum+1)/2 && big<=(sum+1)/2;){
if(temp==0){
temp = small + big;
}
if(temp>sum){
temp = temp - small;
small++;
}else if(temp<sum){
big++;
temp = temp +big;
}else{
ArrayList<Integer> array = new ArrayList<Integer>();
for(int i = small;i<=big;i++){
array.add(i);
}
arrayLists.add(array);
big++;
temp = temp +big;
}
}
return arrayLists;
}
}
程序运行结果:
在上述代码中,求连续序列的和应用了一个小技巧。通常我们可以用循环求一个连续序列的和,但考虑到每次操作之后的序列和操作之前的序列相比大部分数字都是一样的,只是增加或者减少了一个数字,因此我们可以在前一个序列的和的基础上求操作之后的序列的和。这样可以减少很多不必要的运算,从而提高代码的效率。
该算法的时间复杂度也是O(n)。