字节面试新题|构造比limit小的最大数字

661 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

引言

今天来分享一道新鲜出炉的面试题,全网目前搜索引擎搜不到这道题,欢迎关注我,我会不定期给大家分享面试哒~

题目描述

给定一个只包含0~9的无重复数字的数组arr;

给定一个数字limit;

要求用数组arr中的数字构造一个比limit小的最大整数,数组arr中的数字可以重复使用。

示例:

arr = [ 2, 6, 7 ];

limit = 261;

返回结果:227

思路

目标:构造一个数字最接近limit,但是比limit小,我们不妨假设结果能不能达到limit-1;

  1. 定义一个递归函数,我们从limit-1中从左往右每次取一个数字cur,再从数组arr中选一个最接近cur的数字near

  2. 如果curnear相等,那么递归地去构造后面的数字;

  3. 如果curnear不相等,且数组中存在一个最接近cur的数字near < cur,那么选择near作为当前位的数字,后面的数字使用数组arr中最大的数字进行构造;

  4. 如果curnear不相等,且数组中不存在比cur的小的数字,那么需要返回上一层的递归的条件分支2,如果一直返回到最上一层都找不到,此时需要将构造的数字位数减一,然后取数组中最大的数字构造一个比limit小一位的数字。

代码

package main

import (
   "fmt"
   "sort"
)

func maxLimit(arr []int, limit int) int {
   //先排序,方便通过索引找离某个数最近的数字
   sort.Ints(arr)
   limit--

   //小技巧,用offset来从limit中取单个数字
   offset := 1
   for offset <= limit / 10 {
      offset *= 10
   }//到这一步,offset和limit同位数,例如limit=221,则offset=100

   //定义一个递归函数,从`limit-1`中从左往右每次取一个数字`cur`,去构造最接近他的数
   //如果返回的数做不到和limit位数一样,返回-1
   //如果能做到,返回的结果就是答案
   ans := recursive(arr,limit,offset)

   if ans != -1 { //已经构造出一个符合要求的、和limit位数一样的数字
      return ans
   } else { //ans = -1,构造一个比limit小一位的最大数字
      return rest(arr,offset/10)
   }
}

func recursive(arr []int, limit, offset int) int {
   //构造出的数字完全等于limit,即一直是走分支 arr[near] == cur
   if offset == 0 {
      return limit
   }

   cur := (limit / offset) % 10

   //从数组中找一个最接近cur的数字near,返回数组索引
   near := findNear(arr,cur)

   if near == -1 {//如果数组中找不到,向上层返回-1
      return -1
   } else if arr[near] == cur { //数组中找到数字和当前数字一样,递归向下
      ans := recursive(arr,limit,offset/10)
      if ans != -1 {
         return ans
      }else if near > 0 { //后面的位数无法构造出小于limit的,但是我可以让这一位的数字更小 < cur,后面的数字全选最大的
         near--
         return (limit / (offset*10))*offset*10 + (arr[near]*offset) + rest(arr,offset/10)
      }else { //后面的位数无法构造出小于limit的,而且这一位找不到更小的了,返回-1,去上一层找
         return -1
      }
   }else { //arr[near] < cur
      return (limit / (offset*10))*offset*10 + (arr[near]*offset) + rest(arr,offset/10)
   }
}

//比如offset = 10000,5位数字,把arr中最大的数字x,拼成xxxxx,返回
func rest(arr []int, offset int) int {
   res := 0
   for offset > 0 {
      res += arr[len(arr)-1]*offset
      offset /= 10
   }
   return res
}

// 在有序数组arr中,找到<=num,且最大的数字,返回该数字的索引
// 如果所有数字都大于num,返回-1
func findNear(arr []int, num int) int {
   l, r := 0, len(arr)-1
   m, near := 0, -1
   for l<=r {
      m = l + (r-l)>>1
      if arr[m]<=num {
         near = m
         l = m + 1
      }else {
         r = m - 1
      }
   }
   return near
}

//测试
func main()  {
   arr := []int{2, 6, 7}
   limit := 261
   fmt.Println(maxLimit(arr,limit))

   limit2 := 222
   fmt.Println(maxLimit(arr,limit2))
}

总结

在构造过程中,选择数字能追平就追平,不能追平就在适当的位置选择小于cur的数字,然后这个位置后面的数字用数组中最大的数字去拼接,即为答案;

本题最大的思维困难就在于递归过程中,多种条件的判断,属于难题了。