首页
学习
活动
专区
圈层
工具
发布

有点无语,十几人的小公司,老板突然在群里问,谁能牵头开发一款编程语言。。。

这贴子我看到的时候,脑子第一反应就是:“这老板是不是前一天晚上喝多了?”十几个人的小公司,就敢在群里问“谁能开发一门编程语言”?说实话,别说会不会,就这语气,就已经让人满头问号。

我觉得,老板可能想要的是“结合业务的DSL”,而不是啥真正意义上的编程语言。这事儿吧,说难也难,说简单也简单。

要真是搞个类SQL的东西,配合公司业务场景生成代码,倒也不是不可能。但如果他真以为自己公司能搞出下一个Python,那就只能说,梦做得挺好。

而且要做这事,不是你随便叫一个人牵头就能搞定的。这玩意不仅要懂语法设计、解释器实现,还得配合业务逻辑、团队协作,最重要的,还得有人用,能推广。这难度,绝对不是“有程序员就能上”的活(备注:文末可领最新资料)。

算法题:至少有 1 位重复的数字

这个题目其实是个典型的“补集问题”:我们要找的是那些“至少有一个数字重复”的数,其实等价于:用总数减去“所有数字都不重复”的数。

我一开始没这么想,脑袋里先过了一遍暴力法,想着是不是每个数都拆开判断一下有没有重复数字。但你要是认真点一想,n可能是10^9,这玩意儿暴力跑根本跑不动,老老实实用数学建模来搞。

正经地来一套数学推导

先来看看总共有多少个数?n个。那我们要干的是:计算出[1, n]之间有多少个数字没有重复的数字,再用n减去这个数。

重点来了,这个“没有重复数字”的数怎么算?我们得分两种情况讨论。

第一种是:位数小于n的数,比如n是4321,那我们就先看1位、2位、3位数里有多少个不重复的。

这个其实可以组合数学搞定:

1位数里,1-9,有9个(0不能算)

2位数,第一位可以选1-9中的任何一个,第二位要从0-9去掉第一位那个数里选,共9*9=81

3位数,第一位还是9种,第二位是9种(除去第一位),第三位是8种(除去前两个),所以998=648

4位数,第一位是9,第二位9,第三位8,第四位7:998*7=4536

这一部分叫“低位全排列”,用的是P(9, d-1)

但有些n是非整十,比如4321,那我们不能只靠排列。得处理一下“最高位卡着”的情况,比如4xxx里面,必须小于4321,这就得做DFS。

接下来是正片:DFS逐位判断

我写的时候是从最高位往下递归,判断每一位的取值范围。比如说n=4321,我们要先看千位:

千位可以从1到4(不能从0开始)

假设我们取了3,那后面三位我们可以自由地取数字,但不能重复,也得小于321这个范围

这其实就是一个典型的“前缀树剪枝”的操作,得维护一个used数组,记录用过的数字,下一位不能再用。

每一位我们都有两种选择:

小于当前位:直接暴力枚举所有没用过的数字

等于当前位:那就进入下一位继续递归

一旦某位比目标位大了,就不能继续了;如果某位选的数已经用过了,也不能继续。

Java代码怎么写?我用了递归函数dfs(pos, usedMask, isLimit, isNum),分别代表:

当前是第几位

当前哪些数字已经用过(用一个int的bit位表示)

当前位是否受上层限制(比如前面位都是紧贴n)

当前是否已经选了数字(为了处理前导0)

这个思路虽然不太像普通回溯,但它的效率是真高,因为是状态压缩的,最多也就处理10个bit的used。

Java实现也贴一下,核心部分是DFS

public class Solution {

  public int numDupDigitsAtMostN(int n) {

      String s = String.valueOf(n);

      return n - dfs(0, 0, true, false, s);

  }

  private int dfs(int pos, int used, boolean isLimit, boolean isNum, String s) {

      if (pos == s.length()) return isNum ? 1 : 0;

      int res = 0;

      if (!isNum) {

          res += dfs(pos + 1, used, false, false, s);

      }

      int low = isNum ? 0 : 1;

      int high = isLimit ? s.charAt(pos) - '0' : 9;

      for (int d = low; d <= high; d++) {

          if ((used >> d & 1) == 0) {

              res += dfs(pos + 1, used | (1 << d), isLimit && d == high, true, s);

          }

      }

      return res;

  }

}

这段代码逻辑不长,但藏着不少门道。像used >> d & 1就是个经典的位运算技巧,用来检测数字d有没有用过。

补充个实战经验:

我在项目中还真用过类似思路。某次做手机号筛选,客户要求“不能有重复数字”,我第一反应就是这个题。不同的是,那次我还得加规则“不能有两个奇数”,规则多了,状态就更复杂,但套路一样:位数DFS+状态压缩。

再讲点“坑”:

有个坑是前导0的问题。像“00123”这种不应该算数,但我们的DFS逻辑会在没选任何数的情况下跳过当前位,这就可以规避这个坑。

还有一个坑就是n本身可能有重复,比如n=1000,我们得算清楚从1到999中不重复的,再处理1000这个数。

我觉得这个题有点启发意义:

很多题不是直接求目标,而是“先求补集”,这个思维在排列组合问题里特别常见。就像有时候我们求概率,直接求1-不满足条件的概率反而简单。

而这题也是数字DP的典型入门之一,下一步你可以试试“所有数字都不含4”“数字不连续递增”这种扩展题。

讲真,做完这题再回头看什么“多少个不含某个子串的数”都会轻松点。就看你愿不愿意多动脑子,把暴力优化成结构化搜索。你会发现,不止是代码,思维也干净了很多。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/O_7RnNZMBfokfo59kpQkW34w0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。
领券