剑指Offer第二十六题:窗口思想(双指针贪心)的应用。
题目:
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
题目如上,题目要求在一个递增的数组中国找到两个数相加为S的数,并且如果有多个值选择他们乘积最小的一个输出。如果用传统的两个循环语句那么导致时间复杂度大,并且没有意义。在这个题,采用双指针的方法解决,一个指针指向数组的第一个数,另一个指针指向数组的最后一个数,判断两个指针所指数的和Sum和S比较,如果S=Sum那么这两个指针就是结果,如果Sum>S,那么右指针向中间移动,如果Sum>S,那么左指针向中间移动,即可解决问题。
题目的要求是多个结果选择乘积最小的,最开始我一直都在纠结这个问题,后来才发现,如果两个指针隔得越远,那么他们的乘积就越小,所以采用双指针贪心法,这个都不需要考虑。
/** * 方法实现说明 输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S, * 如果有多对数字的和等于S,输出两个数的乘积最小的 * @author yangxin * @param * @return * @exception * @date 2018/12/6 14:50 */ public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) { ArrayList<Integer> list=new ArrayList<Integer>(); if(array.length<1||sum==0||array==null) return list; int start=0; int end=array.length-1; while(start<end){ int newSum=array[start]+array[end]; if(newSum==sum){ list.add(array[start]); list.add(array[end]); return list; } else if(newSum>sum){ start++; }else end--; } return list; }
题目:
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序,函数模板如下。
public static ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
}
解法一:这个思路是我个人想出的,有点low。我观察题目,给出的和为100的序列为:18,19,20,21,22 共5个;
和为100的序列为:9,10,11,12,13,14,15,16 共8个。可以得出:100/5=20,那么这个20是5个连续数字的中间数,那么依据他分别向左向右移动5/2位,这个范围就是结果的范围。另一组数据也能得出:100/8=12.5,那么12.5是这8个连续数字的中间数,因为只能取整数,所以向左和向右获取8/2位数就是结果。所以我们想要i位数的连续和,那么就使用Sum/i,得到中间值,来判断有没有满足条件的。怎么判断满不满足条件呢?
我们只需要计算Sum%i;如果i为奇数:Sum%i==0,那么就存在这个序列;如果i为偶数:Sum%i==n/2。
那么我们可先写一个函数如下:实现的功能就是根据传入的和Sum,和位数i,判断是否有这个值。
/** * FindContinuousSequence的调用函数,实现找出一组存在的数据 * @author yangxin * @param * @return * @exception * @date 2018/12/6 14:20 */ public static ArrayList<Integer> FindNumber(int sum, int i) { ArrayList<Integer> list = new ArrayList<Integer>(); if (i % 2 == 0) { if (sum % i == i / 2 && (sum / i - i / 2 + 1) >= 0) { int j = (sum / i - i / 2 + 1); if(j==0) j++; for (; j <= (sum / i + i / 2); j++) list.add(j); } } else { if (sum % i == 0 && (sum / i - i / 2) >= 0) { int j = (sum / i - i / 2 ); if(j==0) j++; for (; j <= (sum / i + i / 2); j++) { list.add(j); } } } return list; }
按照题目的要求那么答案肯定不止一个,至少要两个连续的数,最多没说,那么最多这个是多少呢,那就是(Sum+1)/2。所以得:
**方法一: * 输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序 * * @param * @return * @throws * @author yangxin * @date 2018/12/6 13:14 */ public static ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) { ArrayList<ArrayList<Integer>> lists = new ArrayList<ArrayList<Integer>>(); if (sum < 3) return lists; int mid = (1 + sum) / 2; for (int i = 2; i <= mid; i++) { if(FindNumber(sum, i).size()!=0) if(!lists.contains(FindNumber(sum, i))) lists.add(FindNumber(sum, i)); } return lists; }
感觉完成了,但是提交还是不成功,题目要求排好序提交,但是数据结构是ArrayList<ArrayList<Integer>>保存的ArrayList<Integer>串,在ArrayList<Integer>中又保存了一串数据,这个怎么排序??
每个串的第一个数字大于其他串的第一个数据的话,那么其他位置的也是一样,我们通过重写Collections.Sort的Comparator比较器来完成排序。
Collections.sort(lists, new Comparator<ArrayList<Integer>>() { @Override public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) { if(o1.get(0)>o2.get(0)) return 1; if(o1.get(0)<o2.get(0)) return -1; return 0; } });
总的代码
1 public static void main(String[] args){ 2 ArrayList<ArrayList<Integer>> lists=FindContinuousSequence(100); 3 for(ArrayList<Integer> list:lists) 4 System.out.println(list.toString()); 5 System.out.println(); 6 } 7 8 9 10 /**方法一: 11 * 输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序 12 * 13 * @param 14 * @return 15 * @throws 16 * @author yangxin 17 * @date 2018/12/6 13:14 18 */ 19 public static ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) { 20 ArrayList<ArrayList<Integer>> lists = new ArrayList<ArrayList<Integer>>(); 21 if (sum < 3) 22 return lists; 23 int mid = (1 + sum) / 2; 24 for (int i = 2; i <= mid; i++) { 25 if(FindNumber(sum, i).size()!=0) 26 if(!lists.contains(FindNumber(sum, i))) 27 lists.add(FindNumber(sum, i)); 28 } 29 /* 30 排序 31 */ 32 Collections.sort(lists, new Comparator<ArrayList<Integer>>() { 33 @Override 34 public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) { 35 if(o1.get(0)>o2.get(0)) 36 return 1; 37 if(o1.get(0)<o2.get(0)) 38 return -1; 39 return 0; 40 } 41 }); 42 43 return lists; 44 } 45 /** 46 * FindContinuousSequence的调用函数,实现找出一组存在的数据 47 * @author yangxin 48 * @param 49 * @return 50 * @exception 51 * @date 2018/12/6 14:20 52 */ 53 public static ArrayList<Integer> FindNumber(int sum, int i) { 54 ArrayList<Integer> list = new ArrayList<Integer>(); 55 if (i % 2 == 0) { 56 if (sum % i == i / 2 && (sum / i - i / 2 + 1) >= 0) { 57 int j = (sum / i - i / 2 + 1); 58 if(j==0) 59 j++; 60 for (; j <= (sum / i + i / 2); j++) 61 list.add(j); 62 } 63 } else { 64 if (sum % i == 0 && (sum / i - i / 2) >= 0) { 65 int j = (sum / i - i / 2 ); 66 if(j==0) 67 j++; 68 for (; j <= (sum / i + i / 2); j++) { 69 list.add(j); 70 } 71 } 72 } 73 return list; 74 }
运行结果
Ok搞定
解法二:这种解法就是最正宗的,使用双指针的应用,还是初始化两个指针,start和end,但是start=1,end=2;
我们每一次都将start-end之间的和求出来与Sum比较,如果start-end的和<Sum,那么end++;反之start++。
1 /** 2 * 方法二:输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序 3 * @author yangxin 4 * @param 5 * @return 6 * @exception 7 * @date 2018/12/6 14:21 8 */ 9 public static ArrayList<ArrayList<Integer>> FindContinuousSequence1(int sum) { 10 ArrayList<ArrayList<Integer>> lists=new ArrayList<ArrayList<Integer>>(); 11 if(sum<3) 12 return lists; 13 int start=1; 14 int end=2; 15 int middle=(1+sum)/2; 16 int newSum=start+end; 17 while(start<middle){ 18 if(newSum==sum){ 19 if(addToList(start,end).size()>1) 20 lists.add(addToList(start,end)); 21 } 22 23 while (newSum>sum&&start<end){ 24 newSum-=start; 25 start++; 26 if(newSum==sum){ 27 if(addToList(start,end).size()>1) 28 lists.add(addToList(start,end)); 29 } 30 } 31 end++; 32 newSum+=end; 33 34 } 35 return lists; 36 } 37 38 public static ArrayList<Integer> addToList(int start,int end){ 39 ArrayList<Integer> list=new ArrayList<Integer>(); 40 for(int i=start;i<=end;i++) 41 list.add(i); 42 return list; 43 }
能够完美的运行。