【FCC】javascript_datastructure五道验收题思路及题解

题目1:

  • 题目概述:输入一串字符,判断其是否为回文(true or false)

    • 注意:这个回文是有要求的,从测试样例可以看出:字母的大小写不影响结果;在字母之间多一些下划线、空格、标点符号之类的不影响结果,这就需要对字符串进行处理。

      /[\W]/g是匹配除数字字符下划线外的其他字符,也就是不包括下划线,因此需要手动加上下划线,即:
      /[\W_]/g
      
  • 题解:

    function palindrome(str) {
      let sign=/[\W_]/g;
      let tmpstr=str.toLowerCase().replace(sign,"");
      let reverstr=tmpstr.split("").reverse().join("");
      return tmpstr===reverstr;
    }
    
    palindrome("eye");
    

题目2:

  • 题目概述:将阿拉伯数字转换为罗马数字

  • 思路:

    • 这道题转换成对字符串及字符数组的处理会比对数字进行求余更加方便简洁;

    • 同时,通过观察得知,罗马数字有特定的规则:虽然个十百千上表示具体数字的字母不同,但都是以5为分界。

    • 由此,可以在二维数组\对象 中构建一个便于查找的表,某个特定的位数对应于表的某一行,该位数上的具体数字对应于表的某一列。

      注:以上思路是我在看了一位大佬的做法之后才更加明确和坚定的,也是截至目前我认为的最好的解法。附上大佬做法的链接地址:

      https://forum.freecodecamp.org/t/roman-numeral-converter-the-easy-way/450428

  • 题解:

    function convertToRoman(num) {
      const romanobj={
        0: ["","I","II","III","IV","V","VI","VII","VIII","IX"],
        1:["","X","XX","XXX","XL","L","LX","LXX","LXXX","XC"],
        2:["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM"],
        3:["","M","MM","MMM","MV","V","VM","VMM","VMMM","IX"],
      }
    
      let numarr=num.toString().split("").map(item=>parseInt(item));
      //将数组中的元素从字符转为数字的原因是:后续需要传入数字作为下标而不是字符
      let romanarr=[];
      let N=numarr.length-1;
    for(let i=N;i>=0;i--){
      romanarr.push(romanobj[i][numarr[N-i]]);
      //从最高位开始入栈 
    }
     return romanarr.join("");
    }
    
    convertToRoman(36);
    

题目3:

  • 题目简介:破译密码,原码字母与译码字母相差13。如A-N,B-O。

  • 思路:灵活运用将字符串转换为字符数组、字符与ASCII码间相互转换的方法。

    求字符串中第i个字符的ASCII码:str.charCodeAt(i)

    从ASCII码转为对应字符:String.fromCharCode(i)

    • 注意:从测试样例看,仅仅是大写字母范围内进行字符转换(65-90),不注意这个细节可能连空格(32)都转换走了。
    • 另外,有加13还是减13的差别,这取决于两个字母在ASCII表中的先后顺序。例如从A到N是+13,从N到A是-13,让程序作出判断的较简便方法就是:先统一-13,如果得到的编码小于范围,再+26(相当于最终结果+13) (也可以用一些临时变量存起来然后比较编码大小之类的,但是个人觉得没必要这么麻烦)
  • 题解

    function rot13(str) {
      let newarr=str.split("");
      //以下代码直接对产生的字符数组的元素进行修改,不需要额外产生一个数组
      for(let i=0;i<newarr.length;i++){
        if(str.charCodeAt(i)>=65&&str.charCodeAt(i)<=90){
          let tmp=str.charCodeAt(i)-13;
          if(tmp>=65) newarr[i]=String.fromCharCode(tmp);
          else newarr[i]=String.fromCharCode(tmp+26);
        }
      }
      return newarr.join("");
    }
    
    rot13("SERR PBQR PNZC");
    

题目4:

  • 题目简介:验证所给的号码是否为美国电话号码

  • 思路:

    • 从题目描述及测试样例可见,区域码为三个数字,后面真正的电话号码加起来是7个数字

    • 可以没有国家码,但如果在第一位出现了,则其必须是1

    • 该题用正则表达式匹配字符串

      • ?{0,1} 是相等的,它们都代表着: 0个或1个前面的内容(即?前面匹配的内容可选可不选,符合这里对国家码及中间的横线、空格的要求)

      • 补充:***** 与 {0,} 是相等的,它们都代表着 0 个或多个前面的内容

      • 字符 +{1,} 是相等的,表示 1 个或多个前面的内容

      • /\d{3}/表示匹配三个数字

        以上几小点参见 菜鸟教程-正则表达式

  • 题解:

    function telephoneCheck(str) {
    if(str.match(/^1?\s?(\d{3}|\(\d{3}\))[-\s]?\d{3}[-\s]?\d{4}$/))
     return true;
     else return false;
    }
    
    telephoneCheck("555-555-5555");
    

    附上对这一串匹配式子的解答:这是对一个字符串从头到尾进行了很严格的规定,适用于匹配不是很长的字符串(否则这样制定规则很耗时耗力)

    • 开头的^与结尾的$分别表示匹配字符串的开始位置与结束位置
    • 1?表示1可有可无;同理 \s?表示空格可有可无
    • (\d{3}|(\d{3})) 表示匹配纯三个数字的形式或者带有括号的三个数字的形式。

    ​ 对于这里我也尝试过(?\d{3})?的写法,但是这样写正中某些测试样例的圈套。像"555)-555-5555" 和“(555-555-5555”之流的连括号都缺少匹配的字符串会被误判为对,因此不可取。

题目5:找零问题

找零问题会涉及到贪婪算法思想,个人认为这道题目很有参考价值。以下将题目详细列出并一一分析。

  • Design a cash register drawer function checkCashRegister() that accepts purchase price as the first argument (price), payment as the second argument (cash), and cash-in-drawer (cid) as the third argument.
    cid is a 2D array listing available currency.

    function checkCashRegister(price, cash, cid){}
    price 是应付,cash是实付,cash-price就是要找的零钱
    cid示例如下:
    [
      ["PENNY", 1.01],
      ["NICKEL", 2.05],
      ["DIME", 3.1],
      ["QUARTER", 4.25],
      ["ONE", 90],
      ["FIVE", 55],
      ["TEN", 20],
      ["TWENTY", 60],
      ["ONE HUNDRED", 100]
    ]
    其中右边的数字表示该面值的总额,如["ONE HUNDRED", 100]指的是现在100元只有1
  • The checkCashRegister() function should always return an object with a status key and a change key.

要新建一个返回对象,里面包含status和change两种属性
  • Return {status: "INSUFFICIENT_FUNDS", change: []} if cash-in-drawer is less than the change due, or if you cannot return the exact change.

    Return {status: "CLOSED", change: [...]} with cash-in-drawer as the value for the key change if it is equal to the change due.

    Otherwise, return {status: "OPEN", change: [...]}, with the change due in coins and bills, sorted in highest to lowest order, as the value of the change key.

    当需找零数大于柜台钱数时,返回{status: "INSUFFICIENT_FUNDS", change: []};
    当需找零数刚好等于柜台钱数时,返回{status: "CLOSED", change: [...]}(找完这一笔就没得找了,所以关柜子);
    其他情况都是显示开柜子, 从大到小 列出要找的钱数。如:
    checkCashRegister(19.5, 20, [["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.1], ["QUARTER", 4.25], ["ONE", 90], ["FIVE", 55], ["TEN", 20], ["TWENTY", 60], ["ONE HUNDRED", 100]]) should return {status: "OPEN", change: [["QUARTER", 0.5]]}
    
  • 题目还给了面值比对表

    Currency UnitAmount
    Penny$0.01 (PENNY)
    Nickel$0.05 (NICKEL)
    Dime$0.1 (DIME)
    Quarter$0.25 (QUARTER)
    Dollar$1 (ONE)
    Five Dollars$5 (FIVE)
    Ten Dollars$10 (TEN)
    Twenty Dollars$20 (TWENTY)
    One-hundred Dollars$100 (ONE HUNDRED)
在代码中创建这个表的时候最好从大到小排,既方便输出又容易用贪婪算法
  • 贪婪算法的原则就是:总是尝试寻找当前情况的最优解。 转化为找零问题就是:先从大面额的钱币入手,如需要找100元,1张100与100张1元都是找零的方法,但是前者是更优解。
  • 最终写成的代码参考了freecodecamp get a hint上的相关文章、博客园一位大佬的文章,后者链接如下:

https://www.cnblogs.com/arduka/p/13321781.html

  • 这道题最好能像我上面链接里的大佬一样先乘100,再将最后结果统一除以100

原因?我倒数第二稿代码如下:

function checkCashRegister(price, cash, cid) {
  //面值表
 let denomination=[
        ["ONE HUNDRED", 10000],
        ["TWENTY", 2000],
        ["TEN", 1000],
        ["FIVE", 500],
        ["ONE", 100],
        ["QUARTER", 25],
        ["DIME", 10],
        ["NICKEL", 5],
        ["PENNY", 1]
    ];

//应找钱数,num.toFixed(n)对num保留n位小数
var odd=(cash-price).toFixed(2);

//应返回的对象
let returnobj={
  status:null,//设默认值为空,之后根据情况添加设置
  change:[]
};

//对传入的零钱柜二维数组进行解析,能够算出总钱数并且方便引用单个数值
//初始值设为0:计算总数,后面引用单个数值也方便
var register=cid.reduce((acc,curr)=>{
  acc[curr[0]]=curr[1].toFixed(2);
  acc.total+=acc[curr[0]];
  return acc;
},{total:0.00});

//情况判断
//总钱数恰好相等
if(odd===register.total){
  returnobj.status="CLOSED";
  returnobj.change=cid;
  return returnobj;
}

//应找钱大于柜台实有的钱
if(odd>register.total){
   returnobj.status="INSUFFICIENT_FUNDS";
  returnobj.change=[];//这一行可要可不要
  return returnobj;
}

//一般情况,开始检索柜台钱数
for(let item of denomination){
  let value=0;//value可理解成检索过程中已经能够兑换出的钱
  while(register[item[0]]>0&&odd>=item[1]){
odd-=item[1];
value+=item[1];
register[item[0]]-=item[1];
  }
  if(value>0) returnobj.change.push(item[0],value);
}


if(odd>0){//到最后还有没能找的钱,说明找零不成功(总数够,但面额不允许)
returnobj.status="INSUFFICIENT_FUNDS";
returnobj.change=[];//打算找的钱找不成了
return returnobj;
}

returnobj.status="OPEN";
return returnobj;
}

checkCashRegister(19.5, 20, [["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.1], ["QUARTER", 4.25], ["ONE", 90], ["FIVE", 55], ["TEN", 20], ["TWENTY", 60], ["ONE HUNDRED", 100]]);

但我发现 CLOSED的测试样例过不了。

从表面上看,返回的是一个字符串和传进去的cid数组,好像没有问题,但是实际上通过打印total值,我发现这种取两位有效数字的方法会使计算结果产生一些偏差,也就是说问题出在这一步:

if(odd===register.total)

我尝试把=换成,但没有效果。而通过将代码改成先*100再除100的方法,这个问题得以解决。

基于上述启发作出修改的代码终稿如下:

用的函数也相应换成:Math.round(num)(四舍五入)

function checkCashRegister(price, cash, cid) {
  //面值表
 let denomination=[
        ["ONE HUNDRED", 10000],
        ["TWENTY", 2000],
        ["TEN", 1000],
        ["FIVE", 500],
        ["ONE", 100],
        ["QUARTER", 25],
        ["DIME", 10],
        ["NICKEL", 5],
        ["PENNY", 1]
    ];

//应找钱数,num.toFixed(n)对num保留n位小数
var odd=Math.round((cash-price)*100);
//应返回的对象
let returnobj={
  status:null,//设默认值为空,之后根据情况添加设置
  change:[]
};

//对传入的零钱柜二维数组进行解析,能够算出总钱数并且方便引用单个数值
//初始值设为0:计算总数,后面引用单个数值也方便
var register=cid.reduce((acc,curr)=>{
  acc[curr[0]]=Math.round(curr[1]*100);
  acc.total+=acc[curr[0]];
  return acc;
},{total:0});

//情况判断
//总钱数恰好相等
if(odd==register.total){
  returnobj.status="CLOSED";
  returnobj.change=cid;
  return returnobj;
}

//应找钱大于柜台实有的钱
if(odd>register.total){
   returnobj.status="INSUFFICIENT_FUNDS";
  returnobj.change=[];//这一行可要可不要
  return returnobj;
}

//一般情况,开始检索柜台钱数
//let arr=[];
for(let item of denomination){
  var value=0;//value可理解成检索过程中已经能够兑换出的钱
  while((register[item[0]]>0)&&(odd>=item[1])){
odd-=item[1];
value+=item[1];
register[item[0]]-=item[1];
  }
  if(value>0) returnobj.change.push([item[0],value/100]);
}


if(odd>0){//到最后还有没能找的钱,说明找零不成功(总数够,但面额不允许)
returnobj.status="INSUFFICIENT_FUNDS";
returnobj.change=[];
return returnobj;
}

//否则,正常进行
returnobj.status="OPEN";
return returnobj;
}

checkCashRegister(19.5, 20, [["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.1], ["QUARTER", 4.25], ["ONE", 90], ["FIVE", 55], ["TEN", 20], ["TWENTY", 60], ["ONE HUNDRED", 100]]);

至此全部关卡都通过了,也希望能帮到正在看文章的你。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值