有趣的算法题之幸运的 3 ,购买数字

幸运的3

题目

你有 n 个数,可以将它们两两匹配(即将两数首尾相连),每个数只能使用一次,问匹配后最多有多少个 3 的倍数(没有进行匹配的数不算)?

原题链接:码题集OJ-幸运的3 (matiji.net)

格式

输入格式:第一行一个 n ,接下来 n 个正整数。

输出格式:输出匹配后最多有多少个 3 的倍数。

取值范围:1 ≤ n ≤ 10000, 1 ≤ x ≤ 100000000

样例

输入:3

           123 123 99

输出:1

 问题分析

        要解决这个问题,我们首先需要理解如何判断一个数是否是 3 的倍数。一个数是 3 的倍数当且仅当其各位数字之和是 3 的倍数。因此,我们可以通过计算每个数的各位数字之和来分类这些数,然后尝试最大化匹配后得到的 3 的倍数的数量。

        将所有的数分为三类:各位数字之和为 3 的倍数的数(即对 3 取余余数为 0 );各位数字之和对 3 取余余数为 1 的数;各位数字之和对 3 取余余数为 2 的数。我们可以初始化一个一维数组来存储三类数的个数。

        很容易想到 3 的倍数只有再加上 3 的倍数才能又成为一个 3 的倍数,又因为没有进行匹配的数不算,因此当这类数的个数为偶数时,它匹配完恰好为原来的一半;当这类数的个数为奇数时,匹配完能凑成原来的个数减一的一半。我们利用向下整除就能恰好囊括这两种情况。

        而余数为 1 的就只能与余数为 2 的匹配才能凑成一个 3 的倍数的数,其余则不能凑成功,因此我们只要得到两者间的最小值,就能找到这种情况能凑成的个数。

        最后,相加起来这两种情况得到的个数,就是总个数了。

代码

import java.util.Scanner;
import java.util.*;

class Main {
    // 存储余数为 0, 1, 2各有几个数字
   static int[] arr = new int[3];

   public static void main(String[] args) {
      Scanner input = new Scanner(System.in);
      // 总个数
      int n = input.nextInt();
      // 加载数字进行统计
      for (int i = 0;i < n;i++) {
        int num = input.nextInt();
        cha(num);
      }
      // 余数0与余数0配对,余数1与余数2配对
      int ans = arr[0]/2 + Math.min(arr[1], arr[2]);
      System.out.println(ans);
      input.close();
   }
    
   public static void cha(int num) {
        // 统计各个位数的和
        int cout = 0; 
        while (num != 0) {
            cout+= num%10;
            num/= 10;
        }
        // 检查数字的余数
        arr[cout%3]++;
   }
}

购买数字

题目

        小码哥是数学王国的一位居民,最近他迷上了回文数,决定去数字商店购买一个回文数。小码哥是个比较贪心的孩子,他想要购买自己能买到的最大的回文数,已知每个数的价格是这个数本身(即 1 是 1 块钱,2 是 2 块钱…)。

        小码哥家由于是个数学世家,小码哥的零花钱也比别人多一点点,目前小码哥手上有 n 块钱,小码哥想问你他能买到的最大的回文数是多少?

        原题链接:码题集OJ-购买数字 (matiji.net)

格式

输入格式:一个正整数𝑛n,表示小码哥的零花钱,其中可能有前导零。

输出格式:输出一个正整数,表示小码哥能买到的最大的回文数。

取值范围:1 ≤ n ≤ 10200 ,数字的长度也小于等于 200 。

 样例

输入:648

输出:646

 问题分析

        为了解决这个问题,我们需要分析如何构造最大的回文数,同时确保这个回文数的价格不超过小码哥手中的钱数。回文数是一个正读和反读都一样的数,例如 121、1331 等。

        首先,因为输入可能有前导 0 ,因此我们应该先处理一下,创建双指针来指定数字的有效边界。通过循环比较数字是否为 0 ,将前导 0 排除。

        接下来,我们可以开始构造回文数。由于我们想要得到最大的回文数,我们应该尽可能地保留高位的数字。因此,为了保证回文特性,我们应该尽可能得去改低位的数字。

        当比较低位和高位数字是否为相等时,会出现两种情况。当低位数字大于高位数字时,例如 123456 ,则说明我们的钱是充裕的,我们只要将低位数字设置成跟高位数字一样的数字就行;当低位数字小于高位数字时,因为高位数字能不减小就不减小,所以我们低位要尝试去向前一位借钱。

        当前一位也没钱时,就要继续向更前一位去借。由于借钱这个过程存在一种嵌套性,因此我们可以将它抽取成一个独立的方法,让其耦合度不要过高,条例也更清晰一点。借完钱后就将第二种情况变为第一种情况,这样就修改该位数就行,例如 423002,经过第一轮调整后就变成 422994 ,后三位都成功借到钱了。

        仔细去想刚才的借钱流程,会发现有个极端情况可能出现问题,比如说 1000 ,经过第一轮借钱后会变成 0990 ,因此为了搞定这种特殊情况,我们可以在遍历完后,舍弃掉第一位的 0 ,再将末尾的 0 改回 9 ,这样就 OK 啦。

代码

import java.util.Scanner;
import java.util.*;

class Main {

    static char[] cs;

   public static void main(String[] args) {

      Scanner input = new Scanner(System.in);
      String s = input.nextLine(); // 读入字符串
      cs = s.toCharArray(); // 转换为字符串数组
      int i = 0; // 左边界
      int j = cs.length-1; // 右边界
      while (cs[i] == '0') i++; // 不要前导 0
      int cur = i; // 记录数字开始的索引,方便后面还原为字符串

      while (i < j) {
        // 如果构建不了汇文就要借数
        if (cs[j] < cs[i]) {
            borrow(j);
        }
        // 借完后更新为回文
        cs[j] = cs[i];
        // 比较下一位
        i++;
        j--;
      }

      // 如果是类似 1000 这样的数字,它会因为借数导致第一位变成 0
      if (cs[cur] == '0') {
        cur++; // 那就舍弃这一位 0
        cs[cs.length-1] = '9'; // 将最后一位改回 9
      }
      // 截取转换为字符串
      String ans = new String(cs, cur, cs.length-cur);
      System.out.println(ans);
      input.close();
   }

   // 如果不够就向前一位解数
   public static void borrow(int j) {
        // 如果前一位数为 0 ,那就继续往前走
        if (cs[j-1] > '0') {
            cs[j-1]--;     
        }else {
            borrow(j-1);
        }
        // 返回的时候,之前为 0 的数也会更新为 9
        cs[j] = '9';
   }
}

最后


     

   如果有所收获请点赞支持一下作者哦,您的点赞是我持续创作的动力!! 

  • 17
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值