剑指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     }

 

 

能够完美的运行。

 

 

 

posted @ 2018-12-06 16:11  轻抚丶两袖风尘  阅读(209)  评论(0编辑  收藏  举报