【问题标题】:Java, count number of Sundays that fell on 1st of the month from 1901 to 2000Java,计算从 1901 年到 2000 年每月 1 日的星期日数
【发布时间】:2012-05-09 08:59:59
【问题描述】:

我是编程和 Java 的新手,我正在尝试通过 Project Euler 网站自学。我正在尝试完成这个问题:http://projecteuler.net/problem=19,即:

在第 20 个月的第一天有多少个星期天 世纪(1901 年 1 月 1 日至 2000 年 12 月 31 日)?

我想解决这个问题的方法是制作一个表示压延机的二维数组,并通过数到 7 来循环遍历数组,然后每次数到 7 时,在数组中的该点上加 1 .最后,我将对数组的第一行求和,这应该是每月第一天有多少个星期日。

但是我的循环有问题,到月底时我的计数重置为 7,我不知道如何阻止它这样做?

这是我的代码:

public class Problem019 {
    public static void main (String[] args){

        //System.out.println(LeapYearTest(1996));
        int ThirtyOne = 31;
        int Thirty = 30;
        int FebNorm = 28;
        int FebLeap = 29;
        int a, b, c, Day, e = 0, f = 0;
        int Calander[] []= new int [12] [] ;

        Calander[0] = new int [ThirtyOne];
        Calander[1] = new int [FebNorm];
        Calander[2] = new int [ThirtyOne];
        Calander[3] = new int [Thirty];
        Calander[4] = new int [ThirtyOne];
        Calander[5] = new int [Thirty];
        Calander[6] = new int [ThirtyOne];
        Calander[7] = new int [ThirtyOne];
        Calander[8] = new int [Thirty];
        Calander[9] = new int [ThirtyOne];
        Calander[10] = new int [Thirty];
        Calander[11] = new int [ThirtyOne];

        for (a=1901;a<2001;a++){
            //System.out.println(a);
            if (LeapYearTest(a))
            {
                Calander[1] = new int [FebLeap];
            }
            else
            {
                Calander[1] = new int [FebNorm];
            }

            for (e=0;e<Calander.length;e++)
            {   
                System.out.println("e: " + e);
                f=0;

                while (f<Calander[e].length)
                {   

                    //System.out.println(Calander[e].length);
                    Day=1;
                    while (Day<8 && f<Calander[e].length)
                    {   
                        System.out.println("f: " + f + "\tDay: " + Day + "\tCalander[e][f]: " + Calander[e][f]);
                        Day++;
                        f++;

                        if (f<Calander[e].length && f!=0 && Day==7)
                        {
                        Calander[e][f]+= 1;
                        }

                    }

                }
            }
            //System.out.println(a);
        }
        for (b=0;b<Calander.length;b++)
        {   
            System.out.print(Calander[0][b]);
        }
    }   


    public static boolean LeapYearTest(int x)
    {
        if (x%4==0 || x%400==0){
            return true;
        }
        if (x%100==0){
            return false;
        }
        else return false;
    }

}

这是它打印的内容,e 是月份,f 是月份中的天数,Day 数到 7:

f: 25   Day: 5  Calander[e][f]: 0
f: 26   Day: 6  Calander[e][f]: 0
f: 27   Day: 7  Calander[e][f]: 100
f: 28   Day: 1  Calander[e][f]: 0
f: 29   Day: 2  Calander[e][f]: 0
**f: 30 Day: 3  Calander[e][f]: 0**
e: 10
**f: 0  Day: 1  Calander[e][f]: 0**
f: 1    Day: 2  Calander[e][f]: 0
f: 2    Day: 3  Calander[e][f]: 0

如何设置循环以使 Day 不会在月底重置?或者有没有其他方法可以解决这个问题,不涉及这么多嵌套循环?

谢谢!

【问题讨论】:

  • 所有这些数组都是多余的。您只需要迭代并增加 Sun 1st 的总数。您与计数器 0-6 和另一个计数器并行迭代,该计数器转到当前月份的天数。
  • 另外,除了每天循环之外,还有更聪明的方法可以计算每月第一天的星期日。还有——如果没有人看到你的代码也没关系,但你拼错了 Calendar,Java 编码约定建议使用 camelCase 变量名(第一个字母小写)。
  • @Marko:对不起,我不明白,你能详细说明一下吗?
  • 感谢所有帮助过我的人,非常感谢!

标签: java arrays loops


【解决方案1】:

如果有一个将年份从 1901 年增加到 2001 年的外部循环,以及一个检查 Jan -> Dec 的内部循环,然后只查看该月的第一天是否是星期日,会不会快得多?

总共 100 * 12 次迭代,10 行代码,最高。

编辑:对此进行扩展。

您可以通过两种方式解决问题 - 查看所有星期日,看看它们是否在一个月的第一天,或者查看所有月份的第一天,看看是否是星期日。

未经测试的代码:

Calendar calendar = Calendar.getInstance();
int count = 0;
for(int i=1901;i<2000;i++){
    for(int j=1;i<12;j++){
        calendar.set(Calendar.YEAR, i);
        calendar.set(Calendar.MONTH,j);
        calendar.set(Calendar.DAY,1);
        if(calendar.get(Calendar.DAY_OF_WEEK).equals(Calendar.SUNDAY)){
            count++;
        }
    }
}

System.out.println(count);

【讨论】:

  • 对不起,我不明白你的意思。内循环究竟做了什么?我怎么能“只看那个月的第一天是不是星期天”?你能更详细地解释一下吗?干杯。
  • @Keith:您可以使用 Java 的内置日历 API 来计算 DayOfWeek...假设这不被视为在 Project Euler 上作弊。
  • @rob:我想把这个问题作为一个数学问题来解决,所以不使用 Calender API。但我可能会尝试使用 Calender API 再次解决它,以了解如何使用它。我会用谷歌搜索“camelCase”并将 Calander 更正为 Calender,谢谢。
  • @Keith:没问题;您可以轻松地将日历 API 的功能替换为您自己的世界末日算法实现。详情见我的回答。
  • 此代码将无法到达 2000 年 12 月 31 日,因为首先 for 循环在 1999 年停止,条件为 i &lt; 2000
【解决方案2】:

我认为您需要抛弃现有代码并重新开始。 既然你试图通过解决 Project Euler 问题来学习如何编码,我不会因为给你代码而破坏你的乐趣。看来你确实想要完整的工作代码,所以我已经修复了您的代码,包括由于问题陈述中的一些微妙细节而出现的一些错误,您可能误解或忽略了这些细节。

只是为了好玩,让我们看一下您想要修复的代码的直接问题...

当你最初声明Day时,将其初始化为1。然后替换这一行:

Day=1;

用这个:

if (Day > 7) {
    Day = 1;
}

并将其移动到一个月中的循环中。

但是还有一个严重的问题。你每年都会覆盖你的二月数组。您应该只初始化一次,并将其长度设置为 29。但这也有一个不幸的副作用,即破坏任何依赖于 calendar[month].length 的循环,因此您也必须考虑到这一点。

您真正需要跟踪的是每月第一天的星期日数,因此您只需要存储和递增一个变量。这解决了上述覆盖二月数组的问题,因为您将不再使用它(或任何其他月份的数组)。另一方面,如果你真的只是想练习使用数组,你可以使用一个 3 维数组(其中额外的维度是年份)。但我敢猜测,大多数 Java 程序员大部分时间都使用列表而不是数组,而当他们使用数组时,他们几乎不会使用一维以上的数组。

还有几点说明

您的外部while 循环是多余的。

您的 LeapYearTest 方法将错误地返回所有能被 100 整除的闰年(所有能被 100 整除的年份也能被 4 整除,因此您永远不会进入测试年份能被 100 整除的 if 块)。

最后,您将在 1 月的每一天循环(而不是在每个月的第一天循环)。

还要注意问题状态,

1 月 1 日 1900 是星期一。

但是您应该找到从 1901 年 1 月 1 日开始的星期日。

在修复了这些和其他错误(例如循环中的条件)之后,我在下面包含了您的代码的完整工作版本。请注意,您可以通过更多地使用模数 (%) 运算符并且不计算该月其他日期的星期日数(因为最终还是将它们扔掉),可以轻松地优化它以在一小部分时间内运行)。

public class Problem019 {
    public static void main (String[] args){

        final int ThirtyOne = 31;
        final int Thirty = 30;
        final int FebNorm = 28;
        final int FebLeap = 29;
        int numOfSundays = 0;

        int calendar[][]= new int [12][];

        calendar[0] = new int [ThirtyOne];
        calendar[1] = new int [FebLeap];
        calendar[2] = new int [ThirtyOne];
        calendar[3] = new int [Thirty];
        calendar[4] = new int [ThirtyOne];
        calendar[5] = new int [Thirty];
        calendar[6] = new int [ThirtyOne];
        calendar[7] = new int [ThirtyOne];
        calendar[8] = new int [Thirty];
        calendar[9] = new int [ThirtyOne];
        calendar[10] = new int [Thirty];
        calendar[11] = new int [ThirtyOne];

        int dayOfWeek = 1;
        for (int year = 1900; year < 2001; year++) {
            for (int month = 0; month < calendar.length; month++) {   
                int dayOfMonth=0;

                int daysInMonth;
                if (month == 1) {
                    daysInMonth = isLeapYear(year) ? FebLeap : FebNorm;
                }
                else {
                    daysInMonth = calendar[month].length;
                }

                while (dayOfWeek < 8 && dayOfMonth < daysInMonth) {   
                    System.out.println("year: " + year + "\tday: " + dayOfWeek
                            + "\tcalendar["+month+"]["+dayOfMonth+"]: " + calendar[month][dayOfMonth]);

                    if (dayOfWeek == 7 && year > 1900) {
                        calendar[month][dayOfMonth]++;

                        if (dayOfMonth == 0) {
                            numOfSundays++;
                        }
                    }

                    dayOfMonth++;

                    dayOfWeek++;
                    if (dayOfWeek > 7) {
                        dayOfWeek=1;
                    }
                }
            }
        }

        for (int month = 0; month < calendar.length; month++) {   
            System.out.println(calendar[month][0]);
        }

        System.out.println(numOfSundays);
    }   

    public static boolean isLeapYear(int year){
        if (year % 400 == 0) {
            return true;
        }
        else if (year % 100 == 0) {
            return false;
        }
        else if (year % 4 == 0){
            return true;
        }
        else {
            return false;
        }
    }
}

同样,这可以改进很多。例如,您可以简单地循环年份和月份,并使用 Java 内置的 Calendar API 或第三方 API 来检查该月的第一天是否是星期日,但这可能是解决问题的最酷方法是自己实现Doomsday Algorithm。这将允许您轻松计算任何给定日期的星期几,而无需使用 java.util.Calendar。

一旦您实施了世界末日算法,您就不必每次都循环遍历所有月份,因此您可以进行更多优化。例如,isSunday(MAR,1,year) == (! isLeapYear(year)) &amp;&amp; isSunday(FEB,1,year)

【讨论】:

  • 谢谢,但我仍然想知道如何在不重置每列的计数的情况下,通过数到 7 来循环遍历交错的二维数组?
  • 你的代码实际上有几个问题。我已经更新了我的答案以指出一些问题。
  • 谢谢,我 - 有点 - 明白了。但是当我使用您的代码时,如果 dayoftheMonth = 0,它不会将 1 添加到数组中。我将 if 语句更改为: if (dayOfMonth1900) { calendar [月][dayOfMonth]+= 1; }
  • 我已经用工作版本更新了我的答案。我添加了一个 numOfSundays 变量来表明数组不是必需的。
【解决方案3】:

试试这个:

import java.util.Calendar;
public class Problem019 {   

    public static void main (String[] args){

        Calendar calendar = Calendar.getInstance();
        int countFirstSunday = 0;
        for(int year = 1901; year <= 2000 ; year++) {
            for(int month = 0; month <= 11; month++) {
                calendar.set(year, month, 1);
                if(calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
                    countFirstSunday++;
                }
            }
        }
        System.out.println("Sundays as the first of month: " + countFirstSunday);
    }

}

【讨论】:

    【解决方案4】:

    这是我的提议。它使用公历来识别日期,然后是星期天。

    import java.util.Date;
    import java.util.GregorianCalendar;
    
    public class SundayOfXX {
    
        public static void main(String [] argv) {
            int counter = 0;
            for (int year = 1901, last_year = 2000; year <= last_year ; year++) {
                for (int month = 1, last_month = 12; month <= last_month ; month++) {
                    Date d = new GregorianCalendar(year,month-1,1).getTime(); // GregorianCalendar use 0 for January
                    if (d.getDay() == 0) { // sunday is day number 0
                        counter++;
                        System.out.println(String.valueOf(counter) + " " + d);
                    }
                }
            }
            System.out.println("Total sunday in XX century: "+counter);
        }
    }
    

    此解决方案已经过全面测试。它查找 20 世纪每月 1 号的 171 个星期日。

    【讨论】:

    • 171*7 = 1197 和 1197 还不到 4 年,那么 20 世纪是否只有 4 年?
    • 可见你没有阅读问题定义。从 1901 年到 2000 年,有 171 个星期日是一个月的第一天。但我同意你的观点,我编辑了我的帖子以更明确......
    【解决方案5】:

    这是您清理和简化的代码:

    public static void main(String[] args) {
      final int thirtyOne = 31, thirty = 30;
      final int calendar[][] = new int[12][];
      final int[] febLeap = new int[29];
      final int[] febNorm = new int[28];
      calendar[0] = new int[thirtyOne];
      calendar[2] = new int[thirtyOne];
      calendar[3] = new int[thirty];
      calendar[4] = new int[thirtyOne];
      calendar[5] = new int[thirty];
      calendar[6] = new int[thirtyOne];
      calendar[7] = new int[thirtyOne];
      calendar[8] = new int[thirty];
      calendar[9] = new int[thirtyOne];
      calendar[10] = new int[thirty];
      calendar[11] = new int[thirtyOne];
      int dow = 0; // set to day of week for Jan 1 1901
      for (int y = 1901; y < 2001; y++) {
        calendar[1] = leapYearTest(y)? febLeap : febNorm;
        for (int m = 0; m < calendar.length; m++)
          for (int d = 0; d < calendar[m].length; d++)
            if (dow++ % 7 == 0) calendar[m][d]++;
      }
      int sumSundays = calendar[0][0] + febLeap[0] + febNorm[0];
      for (int i = 2; i < calendar.length; i++) sumSundays += calendar[i][0];
      System.out.println("Number of Sundays is " + sumSundays);
    }
    
    public static boolean leapYearTest(int x) {
      if (x % 4 == 0 || x % 400 == 0)
        return true;
      return x % 100 != 0;
    }
    

    这就是我说你不需要数组时的意思:

    public static void main(String[] args) {
      final int[] mLens = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
      int dow = 0; // initialize to day of week on Jan 1, 1901
      int suns = 0;
      for (int y = 1901; y < 2001; y++)
        for (int m = 0; m < mLens.length; m++) {
          if (dow++ % 7 == 0) suns++;
          final int mLen = mLens[m] + leapAdd(y, m);
          for (int d = 1; d < mLen; d++) dow++;
        }
      System.out.println(suns);
    }
    
    static int leapAdd(int y, int m) {
      if (m != 1) return 0;
      if (y % 4 == 0 || y % 400 == 0) return 1;
      return y % 100 == 0 ? 0 : 1;
    }
    

    但是你马上就会意识到,在一个月的几天里运行的内部循环没有任何意义,因为它只是模 7。所以内部循环应该说

        for (int m = 0; m < mLens.length; m++) {
          if (dow == 0) suns++;
          final int mLen = mLens[m] + leapAdd(y, m);
          dow = (dow + mLen) % 7;
        }
    

    【讨论】:

      【解决方案6】:

      我会这样做(伪代码):

      class MyDate { ... } // support adding a number of days and comparing with another MyDate
      MyDate end = new MyDate(31. Dec 2000)
      MyDate start = new MyDate(first sunday in 20th century)
      int count = start.mday == 1 ? 1 : 0;
      start.add(7);
      while (start < end) (
          if (start.mday == 1) count++;
          start.add(7);
      }
      

      请注意,不需要任何数组,更不用说二维数组了。 (不过,要获取月份长度,在 MyDate 的 add 方法中,使用简单的常量数组即可。)

      【讨论】:

        【解决方案7】:

        这里有两种解决方案:

        1) 使用Calendar - 更简单,但效率不高 - 135 ms

        import java.util.Calendar;
        
        public class P19 {
        
            public static void main(String[] args) {
                int result = 0;
                for ( int year = 1901 ; year <= 2000 ; year++ ) {
                    for ( int month = Calendar.JANUARY ; month <= Calendar.DECEMBER ; month++ ) {
                        Calendar c = getCalendar(year, month, 1);
                        if ( c.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY ) {
                            result++;
                        }
                    }
                }
                System.out.println(result);
            }
        
            private static Calendar getCalendar(int year, int month, int day) {
                Calendar c = Calendar.getInstance();
                c.set(Calendar.YEAR, year);
                c.set(Calendar.MONTH, month);
                c.set(Calendar.DAY_OF_MONTH, day);      // or Calendar.DATE
                return c;
            }
        
        }
        

        注意:

        • DAY_OF_MONTHDATE 是等价的。
        • 我使用了Calendar.JANUARY,因为第一个月是0,而不是1,即使第一天/日期是1

        2) 使用我的自己的 Date class - 只需要 1.65 毫秒

        public class P19 {
        
            public static void main(String[] args) {
                // 1 Jan 1900 - Monday
                // 1900 is not leap => it has 365 days
                // 365 % 7 = 1 => 1 Jan 1901 - Tuesday => 6 Jan 1901 - Sunday
        
                int yearStart = 1901, yearEnd = 2000;
                int monthStart = 1, monthEnd = 12;
                int dayStart = 6, dayEnd = 31;
                Date dateStart = new Date(yearStart, monthStart, dayStart);
                Date dateStop = new Date(yearEnd, monthEnd, dayEnd);
        
                int result = 0;
                while (Date.compareDates(dateStart, dateStop) < 0) {
                    if (dateStart.day == 1) {
                        result++;
                    }
                    dateStart.addDays(7);
                }
                System.out.println(result);
            }
        
        }
        
        class Date {
            int year;
            int month;
            int day;
        
            Date(int year, int month, int day) {
                this.year = year;
                this.month = month;
                this.day = day;
            }
        
            public void addDays(int days) {
                int numberOfDaysForMonth = getTotalMonthDays(month, year);
                day += days;
                if (day >= numberOfDaysForMonth) {
                    day -= numberOfDaysForMonth;
                    month++;
                    if (month > 12) {
                        month = 1;
                        year++;
                    }
                }
        
            }
        
            public static int compareDates(Date d1, Date d2) {
                if (d1.year == d2.year && d1.month == d2.month && d1.day == d2.day) {
                    return 0;
                }
                if (d1.year < d2.year) {
                    return -1;
                }
                if (d1.year == d2.year && d1.month < d2.month) {
                    return -1;
                }
                if (d1.year == d2.year && d1.month == d2.month && d1.day < d2.day) {
                    return -1;
                }
                return 1;
            }
        
            private int getTotalMonthDays(int m, int y) {
                if (m == 2 && isLeapYear(y)) {
                    return 29;
                }
                if (m == 2) {
                    return 28;
                }
                if (m == 4 || m == 6 || m == 9 || m == 11) {
                    return 30;
                }
                return 31;
            }
        
            private boolean isLeapYear(int y) {
                if (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) {
                    return true;
                }
                return false;
            }
        
        }
        

        此实现仅在星期日迭代 (addDays(7))。

        一些可能的改进:

        • 增加步长(例如:对于1,我们可以添加28而不是7而不跳过任何一天)
        • 更改比较方法以返回 boolean 并简化其主体

        【讨论】:

          【解决方案8】:
          public class CountingSundays {
          
              public static void main(String[] args) {
          
                  int lastDayOfPreviousMonth = 6; //31 Dec 1899 is Sunday as 1 Jan 1900 is Monday
          
                  int countOfSundayOnFirstOfMonth = 0;
          
                  for (int year = 1900; year <= 2000; year++) {
                      for (int month = 1; month <= 12; month++) {
                          int dayOnFirstOfThisMonth = (lastDayOfPreviousMonth + 1) % 7;
                          if (year > 1900 && dayOnFirstOfThisMonth == 6)
                              countOfSundayOnFirstOfMonth++;
                          switch (month) {
                          case 1: // Jan
                          case 3: // Mar
                          case 5: // May
                          case 7: // Jul
                          case 8: // Aug
                          case 10: // Oct
                          case 12: // Dec
                              lastDayOfPreviousMonth = (lastDayOfPreviousMonth + 3) % 7;
                              break;
                          case 4: // Apr
                          case 6: // Jun
                          case 9: // Sep
                          case 11: // Nov
                              lastDayOfPreviousMonth = (lastDayOfPreviousMonth + 2) % 7;
                              break;
                          case 2: // Feb
                              if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))
                                  lastDayOfPreviousMonth = (lastDayOfPreviousMonth + 1) % 7;
                          }
                      }
                  }
          
          
                  System.out.println(countOfSundayOnFirstOfMonth);
              }
          
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2021-07-13
            • 2018-04-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多