Python 网上银行最少登录数的算法

Python 网上银行最少登录数的算法,python,algorithm,Python,Algorithm,下面是定期每月账单的列表。第一个数字是账单到达当月的第一个付款机会,第二个数字是当月最后一个付款机会的到期日 16, 1 2, 16 10, 25 31, 26 15, 31 到达日期和到期日期之间的差异总是小于一个月。我正在寻找一种算法,对于任何数量的具有任何接收日期和任何到期日期的账单,该算法将: 制作一份清单,列出支付账单的网上银行可能的最少登录日期。 保证不会错过任何到期日。 到目前为止,我的想法是寻找一个单一的日期或日期范围,其中尽可能多的账单在到达日期和到期日期之间,然后重复这个过程

下面是定期每月账单的列表。第一个数字是账单到达当月的第一个付款机会,第二个数字是当月最后一个付款机会的到期日

16, 1 2, 16 10, 25 31, 26 15, 31 到达日期和到期日期之间的差异总是小于一个月。我正在寻找一种算法,对于任何数量的具有任何接收日期和任何到期日期的账单,该算法将:

制作一份清单,列出支付账单的网上银行可能的最少登录日期。 保证不会错过任何到期日。 到目前为止,我的想法是寻找一个单一的日期或日期范围,其中尽可能多的账单在到达日期和到期日期之间,然后重复这个过程,直到列表为空

这是最好的方法吗?这个问题有没有一个现有的算法?它叫什么?如果有代码示例,最好使用Python、PHP或伪代码。

Java解决方案:-

public class Ress {

    Set<Interval> intervals = new HashSet<>();

    public static void main(String[] args) {

        String[] input = {"16 1","2 16","10 25","31 26","15 31"};
        System.out.println(minimumLogins(input));

    }

    public static int minimumLogins(String[] input) {
        Ress r = new Ress();
        for (String s : input) {
            Scanner sc = new Scanner(s);
            Bill b = new Bill(sc.nextInt(), sc.nextInt());
            r.addToIntervals(b);
        }
        //If we have to print the dates on which to pay the bills, then it will be any date inside the different intervals.
        return r.intervals.size();
    }

    public void addToIntervals (Bill b) {
        Interval i = null;
        for(Interval in:intervals){
            if(in.canAddBill(b)) {
                i = in;
                break;
            }
        }
        if(i==null) {
            i = new Interval(b);
            intervals.add(i);
        }
        i.addBill(b);
    }

}

class Bill{
    int receiveDate;
    int dueDate;

    public Bill(int a, int b) {
        receiveDate = a;
        if(b<a) {
            // adding to represent the date in next month
            b += 31;
        }
        dueDate = b;
    }
}

class Interval{
    int startDate;;
    int endDate;
    List<Bill> bills;

    public Interval(Bill b) {
        startDate = b.receiveDate;
        endDate = b.dueDate;
        bills = new ArrayList<>();
    }

    public void addBill(Bill b) {
        bills.add(b);
        //Reset interval boundaries based on the newly added bill
        if(b.dueDate<this.endDate) {
            endDate = b.dueDate;
        }
        if(b.receiveDate>this.startDate) {
            startDate = b.receiveDate;
        }
    }

    public boolean canAddBill(Bill b) {
        /*
         * Bill can be added if it lies in the interval.
         */
        if(b.dueDate<=this.endDate || b.receiveDate<=this.endDate) {
            return true;
        }
        return false;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + endDate;
        result = prime * result + startDate;
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Interval other = (Interval) obj;
        if (endDate != other.endDate)
            return false;
        if (startDate != other.startDate)
            return false;
        return true;
    }
}

实际上,您的解决方案不正确,原因如下:

假设有许多天,相同数量的范围相交,并且这个数量是所有其他数量中最大的。例如:

1 -> 3 3 -> 6 6 -> 9 9 -> 10 从我看到的情况来看,接下来的第3、6、9天,他们都有两张账单需要支付,没有其他一天有更多的账单需要支付。现在,由于您不可能确定从哪一天开始,您可以选择第6天,然后支付账单2、3。接下来,您别无选择,只能选择第3天和第9天相应地支付账单1和4。你用了3天,答案是2选择第一天3同时支付账单1和账单2,然后选择第9天支付账单3和账单4

不管怎样,我很确定我有一个近似线性的时间解

首先,让我们把你的输入弄清楚一点,如果第二个数字实际上比第一个数字小,那么在第二个数字加上30或31(如果是31天的话)。您的示例如下所示:

16->31

2->16

10->25

31->56

15->31

我的想法基于以下两个事实:

无论何时登录,最好是支付所有可用但尚未支付的账单

当遍历从开始第1天到结束第60天的当月时间线时,如果可能,最好尝试延迟日志记录过程;这意味着,如果延迟不会导致任何到期日错过

为此,我们首先为每个条目分配一个唯一的ID:

16->31

2->16

10->25

31->56

15->31

让我们使用扫描线算法,它通常解决与区间相关的问题。创建一个名为Sweep的向量,其中该向量的每个元素都包含以下信息:

ID:对应条目的ID

计时器:指示第一天或最后一天付款

类型:只是一个标志。0表示计时器包含支付账单编号ID第一个编号的第一天,而1表示计时器包含支付账单编号ID第二个编号的最后一天

对于每个条目,插入2个元素以扫描向量:

ID=条目的ID,计时器=第一个数字,类型=0

ID=条目ID,计时器=第二个数字,类型=1

将所有这些元素插入扫描向量后,其大小将等于2 x条目数。根据Timer的值对该向量进行递增排序,如果是平局,则根据Type的值递增排序,以便在条目结束之前首先检查条目的开始

遍历扫描向量,同时保留一个包含到目前为止所有未付账单ID的集合,让我们称此集合为就绪。在每个步骤中,您可能会根据我们添加的类型处理以下元素之一:

类型=0。在这种情况下,这意味着您已经到了第一次能够支付账单编号ID的日期。请不要支付此账单。相反,将其ID插入到我们的现成想法2中

类型=1。在这种情况下,检查相应的ID是否在就绪集中。如果不是,则继续下一个元素。如果它实际上在Ready set内,这意味着您已经到了支付以前未付账单的最后一天。你别无选择,只能支付这张账单,连同当天准备好的所有其他账单。通过支付账单,我的意思是将包含您答案的变量增加1,如果对您很重要,请遍历Ready set并存储在某处,所有这些id必须在当天支付。完成此操作后,您已经支付了所有就绪账单,只需清除就绪集并删除其中的所有元素

每个条目都会将2个元素插入到扫描向量中,并且每个条目都会精确地插入到就绪集中一次,并删除一次。在就绪集中检查ID的成本是OLog N,并且每个条目只检查一次。分类歌剧 假设在日志N上。因此,您的总复杂性将是:在日志N上,其中N是您拥有的条目的总数

Python不是真正的我最强的编程语言,所以我将把上面提到的算法编码到C++中的任务,例如,实现起来并不难。希望有帮助

编辑感谢@Jeff的评论

您可以使用以下方法使您的解决方案达到平衡:

您可以迭代1到60天,而不是迭代事件,并保持与我提到的相同的处理方法。这样我们就消除了排序操作

要从插入和检查操作中删除OLog N因子,我们可以使用@Jeff的注释中提到的哈希表,或者可以使用访问的布尔数组代替哈希表,并准备向量。您将向向量中插入准备就绪的票据。当您需要支付账单时,您只需在Ready向量上迭代,并将其中的账单标记为已访问,并在已访问数组中相应的索引中进行访问。通过访问访问的数组中的相应索引,可以简单地检查我的账单是否已支付


有趣的是,在写下我的答案后,我提出了几乎与@Jeff评论中提到的完全相同的优化。然而,考虑到这段时间很短,我决定不把我的答案弄得更复杂,让它更容易理解。既然@Jeff提到了优化,我决定也将其添加到我的答案中。但是,请注意,通过这种优化,总体复杂度现在等于ON+D,其中N是账单总数,D是总天数。因此,如果D相当大,您实际上需要坚持使用第一种解决方案。

上述代码将仅针对以下提到的输入返回答案为2:

1. 1->3 (Bill A)
2. 3->6 (Bill B)
3. 6->9 (Bill C)
4. 9->10 (Bill D)
解释如下: 1.我们得到的第一个输入票据为1->3。 2.此账单将添加到它的第一组:期间:1->3。 3.下一张账单是3->6,它与1->3处于同一时期 票据的收票日期与票据的期末相同

set1:(1->3) { BillA, BillB}
下一张账单是6->9,这将创建一个新的账单集:

set1:1->3{BillA,BillB} Set2:6->9{BillC}

下一张进入的票据是9->10,由于与上面相同的原因,它只会再次进入集合2中

set1:1->3{BillA,BillB} set2:6->9{BillC,BillD}


我相信这是正确的解决方案,因为这个问题实际上要求我们只确定不相交集的数量。

登录到您可以付款的系统时,尽可能少的登录?。还有一个问题:它们是以这种形式出现的,还是也知道月份?2月16日-3月1日?1是。2如果第二个数字小于第一个数字,第二个数字是下个月。这是一项静态分析任务,其中到达日期都提前知道,还是可以更改?我很难想象你怎么能在下一个到期日登录,并计划支付到目前为止所有的账单。是的。这些是固定日期的定期账单。你将不得不登录网上银行,以检查日期未知的未预见账单,这与尽可能少登录的目的背道而驰。除非所有账单都通过单独的渠道(如电子邮件)通知,但这不是练习的一部分。你的建议听起来简单有趣,但为什么它是正确的?要从您的建议中获得实际登录日期,必须编写一个简单的模拟,如果没有更好的替代方案出现,我将尝试使用该模拟。尝试为输入中的所有账单找到不同的不相交的间隔集。仅供参考:仅代码的答案通常不受欢迎,即使有注释。考虑提供注释文本来解释代码实际上做了什么。一个非常彻底的解释。我必须留出时间通读并完全理解它。这和我自己的建议有什么相似之处吗?见初始帖子?事实上没有。我刚刚更新了我的答案,并在开始时解释了为什么你的解决方案在某些情况下会给出错误的答案。当你阅读并理解我的答案时,我希望你能投票表决,并将其标记为正确答案:我认为你可以在技术上做到这一点,但通过避免向量,从而避免排序,而只需扫描每天从1到60的整个未排序范围列表。就绪集可以通过具有O1操作的哈希表来实现。如果N>3600,它可能会超过logn,但这将是一大笔账单@杰夫:我确信你的答案是100%正确的。我用一种与你非常相似的方法更新了我的答案。该解决方案应该提供登录网上银行的天数列表。抱歉,如果这不清楚。