幾個(gè)有趣的算法,你知道嗎?
本文轉(zhuǎn)載自微信公眾號(hào)「微醫(yī)大前端技術(shù)」,作者孫領(lǐng)芝 。轉(zhuǎn)載本文請(qǐng)聯(lián)系微醫(yī)大前端技術(shù)公眾號(hào)。
根據(jù)身份證號(hào)碼計(jì)算出性別、年齡
一、身份證號(hào)碼國(guó)家標(biāo)準(zhǔn)
1、范圍
《公民身份號(hào)碼》(GB11643-1999)該標(biāo)準(zhǔn)規(guī)定了公民身份號(hào)碼的編碼對(duì)象、號(hào)碼的結(jié)構(gòu)和表現(xiàn)形式,使每個(gè)編碼對(duì)象獲得一個(gè)唯一的、不變的法定號(hào)碼。
2、號(hào)碼的結(jié)構(gòu)
公民身份號(hào)碼是特征組合碼,由十七位數(shù)字本體碼和一位校驗(yàn)碼組成。排列順序從左至右依次為:六位數(shù)字地址碼,八位數(shù)字出生日期碼,三位數(shù)字順序碼和一位數(shù)字校驗(yàn)碼。
2.1、地址碼
表示編碼對(duì)象常住戶口所在縣(市、旗、區(qū))的行政區(qū)劃代碼
2.2、出生日期碼
表示編碼對(duì)象出生的年、月、日
2.3、順序碼
表示在同一地址碼所標(biāo)識(shí)的區(qū)域范圍內(nèi),對(duì)同年、同月、同日出生的人編定的順序號(hào),順序碼的奇數(shù)分配給男性,偶數(shù)分配給女性。
2.4、校驗(yàn)碼
根據(jù)前面十七位數(shù)字碼,按照 ISO 7064:1983.MOD 11-2 中的校驗(yàn)碼計(jì)算方法計(jì)算確定
(1)十七位數(shù)字本體碼加權(quán)求和公式:S = Sum(Ai * Wi)
身份證號(hào) | 1 | 1 | 0 | 1 | 0 | 5 | 1 | 9 | 4 | 9 | 1 | 2 | 3 | 1 | 0 | 0 | 2 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
加權(quán)因子 | 7 | 9 | 10 | 5 | 8 | 4 | 2 | 1 | 6 | 3 | 7 | 9 | 10 | 5 | 8 | 4 | 2 |
Ai * Wi | 7 | 9 | 0 | 5 | 0 | 20 |
S = 7 + 9 + 0 + 5 + 0 + 20 + 2 + 9 + 24 + 27 + 7 + 18 + 30 + 5 + 0 + 0 + 4 = 167
(2)計(jì)算模:Y = mod(S, 11) Y = 167 % 11 => 2
(3)通過(guò)模得到對(duì)應(yīng)的校驗(yàn)碼
模 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
校驗(yàn)碼 | 1 | 0 | X | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 |
模為 2 時(shí),校驗(yàn)碼為 X。
二、代碼實(shí)現(xiàn)
1、身份證號(hào)正確性校驗(yàn)
- const WEIGHT = [7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2]
- const MO = [1,0,'X',9,8,7,6,5,4,3,2]
- function isRightId(id){
- const arr = id.split('')
- const checkNumber = arr.pop() // 去除校驗(yàn)碼,將 pop 的返回值賦值給 checkNumber
- let sum = 0
- arr.forEach((ele, index) => {
- sum += ele * WEIGHT[index]
- })
- const m = sum % 11
- const result = MO[m]
- return result+'' === checkNumber
- }
- console.log(isRightId('11010519491231002X')) // true
- console.log(isRightId('110105194912310029')) // false
2、由身份證號(hào)計(jì)算年齡
- function getAge(id){
- // 1、先判斷身份證號(hào)的正確性
- // 2、判斷是否在世
- const year = id.substr(6,4)
- const month = id.substr(10,2)
- const day = id.substr(12,2)
- const timeBrth = new Date(`${year}/${month}/${day}`).getTime()
- const timeNow = new Date().getTime()
- const longTime = timeNow - timeBrth
- const days = longTime / (1*24*60*60*1000)
- let result = ''
- if(days<31){
- result = parseInt(days) + '天'
- }else if(days<365){
- result = `${parseInt(days/30)}月${parseInt(days%30)}天`
- }else{
- result = `${parseInt(days/365)}歲${parseInt(days%365/30)}月${parseInt(days%365%30)}天`
- }
- return result
- }
- console.log(getAge('11010519491231002X')) // 71 歲 8 月 16 天
- console.log(getAge('11010520210820002X')) // 6 天
- console.log(getAge('11010520210720002X')) // 1 月 7 天
3、由身份證號(hào)判斷性別
- function getSex(id){
- // 1、先判斷身份證號(hào)的正確性
- const sex = id.substr(16,1)
- return sex%2? '男': '女'
- }
- console.log(getSex('11010519491231002X')) // 女
- console.log(getSex('11010520210820001X')) // 男
三、其他
1、變性手術(shù)后,身份證號(hào)碼是否更改?
跨性別人士身份證性別變更后,依戶口所在派出所公示為準(zhǔn),進(jìn)行身份證號(hào)碼變更。
2、計(jì)算年齡前應(yīng)先確認(rèn)是否在世。
四、參考資料
《公民身份號(hào)碼》(GB11643-1999)
動(dòng)態(tài)規(guī)劃
1、定義
動(dòng)態(tài)規(guī)劃(Dynamic Programming,DP)是運(yùn)籌學(xué)的一個(gè)分支,是求解決策過(guò)程最優(yōu)化的過(guò)程。
可以簡(jiǎn)單的理解為是對(duì)傳統(tǒng)遞歸的一種優(yōu)化。在 DP 的實(shí)踐中很重要的就是遞推關(guān)系和邊界條件。
2、簡(jiǎn)單:爬樓梯
題目
假設(shè)你正在爬樓梯。需要 n 階你才能到達(dá)樓頂。
每次你可以爬 1 或 2 個(gè)臺(tái)階。你有多少種不同的方法可以爬到樓頂呢?
注意 給定 n 是一個(gè)正整數(shù)。
- 輸入:2
- 輸出:2
- 解釋: 有兩種方法可以爬到樓頂。
- 1. 1 階 + 1 階
- 2. 2 階
示例 2:
- 輸入:3
- 輸出:3
- 解釋: 有三種方法可以爬到樓頂。
- 1. 1 階 + 1 階 + 1 階
- 2. 1 階 + 2 階
- 3. 2 階 + 1 階
代碼:
- // 把數(shù)據(jù)緩存在一個(gè)數(shù)組中
- var climbStairs = function(n) {
- let dp = []
- dp[0]=1;
- dp[1]=1;
- for(let i=2;i<=n;i++){
- dp[i]=dp[i-1]+dp[i-2];
- }
- return dp[n];
- };
- // 使用遞歸
- var climbStairs = function(n) {
- if(n===1) return 1
- if(n===2) return 2
- return climbStairs(n-1) + climbStairs(n-2)
- };
思路:
f(x)=f(_x_?1)+f(x_?2) 爬到第 x _級(jí)臺(tái)階的方案數(shù)是爬到第 x - 1 級(jí)臺(tái)階的方案數(shù)和爬到第 x - 2 級(jí)臺(tái)階的方案數(shù)的和。
LeetCode 運(yùn)行結(jié)果:
3、中等:最長(zhǎng)回文子串
題目
給你一個(gè)字符串 s,找到 s 中最長(zhǎng)的回文子串。
示例 1:
- 輸入:s = "babad"
- 輸出:"bab"
- 解釋:"aba" 同樣是符合題意的答案。
示例 2:
- 輸入:s = "cbbd"
- 輸出:"bb"
思路:
當(dāng) s[i+1 : j-1] 是回文串,并且 s 的第 i 和 j 個(gè)字母相同時(shí),s[i:j] 才會(huì)是回文串。
即:P(i,j)=P(i+1,j?1) 且 (Si == Sj)。
邊界條件:子串的長(zhǎng)度為 1 或 2。對(duì)于長(zhǎng)度為 1 的子串,它顯然是個(gè)回文串;對(duì)于長(zhǎng)度為 2 的子串,只要它的兩個(gè)字母相同,它就是一個(gè)回文串。
- P(i, i) = true
- P(i, i+1) = (Si == Si+1)
代碼:
- function longestPalindrome (s) {
- // 先判斷字符串長(zhǎng)度,如果為 1 則直接返回
- let len = s.length
- if (len < 2) return s
- // 初始化變量
- let maxLen = 1
- let begin = 0
- // dp[i][j] 表示 s[i..j] 是否是回文串
- let dp = []
- // 初始化:所有長(zhǎng)度為 1 的子串都是回文串
- for (let i = 0; i < len; i++) {
- dp[i] = []
- dp[i][i] = true
- }
- // 將字符串切割為數(shù)組
- let charArray = s.split('')
- // 遞推開(kāi)始
- for (let L = 2; L <= len; L++) { // 枚舉子串長(zhǎng)度
- // 枚舉左邊界,左邊界的上限設(shè)置可以寬松一些
- for (let i = 0; i < len; i++) {
- // 由 L 和 i 可以確定右邊界,即 j - i + 1 = L 得
- let j = L + i - 1;
- // 如果右邊界越界,退出當(dāng)前循環(huán)
- if (j >= len) {
- break;
- }
- // 判斷是否為回文
- if (charArray[i] !== charArray[j]) {
- dp[i][j] = false
- } else {
- // 對(duì)于一個(gè)子串而言,如果它是回文串,并且長(zhǎng)度大于 2,那么將它首尾的兩個(gè)字母去除之后,它仍然是個(gè)回文串。
- let flag = j - i < 3
- dp[i][j] = flag ? true : dp[i + 1][j - 1]
- }
- // 當(dāng) dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,記錄回文長(zhǎng)度和起始位置
- if (dp[i][j] && j - i + 1 > maxLen) {
- maxLen = j - i + 1;
- begin = i;
- }
- }
- }
- return s.substring(begin, begin + maxLen);
- }
- console.log(longestPalindrome('babad'), 'babad') // bab babad
- console.log(longestPalindrome('cbbd'), 'cbbd') // bb cbbd
LeetCode 運(yùn)行結(jié)果:
4、困難:接雨水
題目:
給定 n 個(gè)非負(fù)整數(shù)表示每個(gè)寬度為 1 的柱子的高度圖,計(jì)算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
- 輸入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
- 輸出:6
- 解釋:上面是由數(shù)組 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度圖,在這種情況下,可以接 6 個(gè)單位的雨水(藍(lán)色部分表示雨水)。
示例 2:
- 輸入:height = [4,2,0,3,2,5]
- 輸出:9
代碼:
- function trap(height) {
- // 不滿足條件,直接返回
- let len=height.length;
- if(len<=2) return 0;
- let maxLeft = []; // 第 i 根柱子左邊最高柱子的高度
- let maxRight = []; // 第 i 根柱子右邊最高柱子的高度
- maxLeft[0] = height[0];
- for(let i=1; i<len; i++){
- maxLeft[i] = Math.max(height[i], maxLeft[i-1]) // 動(dòng)態(tài)轉(zhuǎn)移
- }
- maxRight[len-1] = height[len-1];
- for(let j=len-2; j>=0; j--){
- maxRight[j] = Math.max(height[j], maxRight[j+1]) // 動(dòng)態(tài)轉(zhuǎn)移
- }
- let sum=0;
- for(let i=0;i<len;i++) sum+=Math.min(maxLeft[i],maxRight[i])-height[i];
- return sum;
- }
思路:
每一列柱子接的雨水是該柱子兩側(cè)最高柱子的最小值減去該柱子高度。
LeetCode 運(yùn)行結(jié)果:
附件:雙指針?lè)?/strong>
代碼:
- function trap(height) {
- let ans=0;
- for(let i=1; i<height.length-1; i++){
- let l_hight = height[i];
- let r_hight = height[i];
- // 找到 i 列右側(cè)最高柱子高度
- for(let r=i; r<height.length; r++){
- if(height[r]>r_hight) r_hight=height[r];
- }
- // 找到 i 列柱子左側(cè)最高柱子高度
- for(let l=i; l>=0; l--){
- if(height[l]>l_hight) l_hight=height[l];
- }
- ans+=Math.min(l_hight,r_hight)-height[i];
- }
- return ans;
- }
LeetCode 運(yùn)行結(jié)果:
5、參考資料
來(lái)源:力扣(LeetCode)
https://leetcode-cn.com/problems/climbing-stairs/
https://leetcode-cn.com/problems/longest-palindromic-substring/
https://leetcode-cn.com/problems/trapping-rain-water/
貪心算法
1、定義
在對(duì)問(wèn)題求解時(shí),總是做出在當(dāng)前看來(lái)是最好的選擇。
2、分餅干
假設(shè)你是一位很棒的家長(zhǎng),想要給你的孩子們一些小餅干。但是,每個(gè)孩子最多只能給一塊餅干。對(duì)每個(gè)孩子 i,都有一個(gè)胃口值 g[i],這是能讓孩子們滿足胃口的餅干的最小尺寸;并且每塊餅干 j,都有一個(gè)尺寸 s[j] 。如果 s[j] >= g[i],我們可以將這個(gè)餅干 j 分配給孩子 i ,這個(gè)孩子會(huì)得到滿足。你的目標(biāo)是盡可能滿足越多數(shù)量的孩子,并輸出這個(gè)最大數(shù)值。
示例 1: 輸入: g = [1,2,3], s = [1,1] 輸出: 1
示例 2: 輸入: g = [1,2], s = [1,2,3] 輸出: 2
- function findContentChildren1(children, cookies){
- children = children.sort((a, b) => a - b)
- cookies = cookies.sort((a, b) => a - b)
- let childrenLength = children.length
- let cookiesLength = cookies.length
- let count = 0
- for(let i = 0, j = 0; i < childrenLength && j < cookiesLength; i++, j++){
- while(j < cookiesLength && children[i] > cookies[j]){
- j++
- }
- if(j < cookiesLength){
- count++
- }
- }
- return count
- }
- console.log(findContentChildren1([1,2,3], [1,1])) // 1
- console.log(findContentChildren1([1,2], [1,2,3])) // 2
- console.log(findContentChildren1([1,2,3], [1,1,3,4])) // 3
核心思想:
- 將孩子的胃口、餅干的大小都按照從小到大排序。
- for 循環(huán)遍歷,比較孩子的胃口 children[i]和餅干的大小 cookies[j]之間的關(guān)系,當(dāng)當(dāng)前餅干不能滿足孩子的胃口時(shí),選擇下一個(gè)餅干進(jìn)行比較。
- 如果滿足孩子的胃口,且 j 在范圍內(nèi),則 count 加 1。
代碼解讀:
- 定義函數(shù) findContentChildren,接受 2 個(gè)參數(shù),分別為 children:孩子的胃口,cookies 餅干的大小。
- 將 children 和 cookies 按照從小到大排序。
- 定義能夠滿足孩子胃口的個(gè)數(shù) count,并最終將 count 返回。
- 循環(huán)遍歷,當(dāng) children[i] > cookies[j] 即當(dāng)前餅干不能滿足孩子時(shí),j++選擇下一塊餅干進(jìn)行比較。
- 如果 children[i] <= cookies[j] 即當(dāng)前餅干能滿足孩子,且 j 在范圍內(nèi),則 count 加 1。
- 進(jìn)入下一次循環(huán)(即嘗試滿足下一個(gè)孩子)。
3、買股票
給定一個(gè)整數(shù)數(shù)組 prices,其中第 i 個(gè)元素代表了第 i 天的股票價(jià)格 ;整數(shù) fee 代表了交易股票的手續(xù)費(fèi)用。你可以無(wú)限次地完成交易,但是你每筆交易都需要付手續(xù)費(fèi)。如果你已經(jīng)購(gòu)買了一個(gè)股票,在賣出它之前你就不能再繼續(xù)購(gòu)買股票了。返回獲得利潤(rùn)的最大值。注意:這里的一筆交易指買入持有并賣出股票的整個(gè)過(guò)程,每筆交易你只需要為支付一次手續(xù)費(fèi)。
示例 1:輸入:prices = [1, 3, 2, 8, 4, 9], fee = 2, 輸出:8
解釋:
能夠達(dá)到的最大利潤(rùn):
在此處買入 prices[0] = 1 在此處賣出 prices[3] = 8 在此處買入 prices[4] = 4 在此處賣出 prices[5] = 9 總利潤(rùn): ((8 - 1) - 2) + ((9 - 4) - 2) = 8
示例 2:輸入:prices = [1,3,7,5,10,3], fee = 3 輸出:6
- function maxProfit(list, fee){
- const length = list.length
- let buy = list[0] + fee // 假定:買入時(shí)機(jī)為第 1 天
- let profit = 0
- for(let i = 1; i < length; i++){
- if(list[i]+fee < buy){ // 如果股票價(jià)格降低,則調(diào)整買入時(shí)機(jī)
- buy = list[i]+fee
- }else if(list[i] > buy){ // 如果有利潤(rùn),則賣出
- profit += list[i] - buy // 計(jì)算利潤(rùn)
- buy = list[i] // 調(diào)整買入時(shí)機(jī)
- }
- }
- return profit
- }
- console.log(maxProfit([1, 3, 2, 8, 4, 9], 2)) // 8
- console.log(maxProfit([1,3,7,5,10,3], 3)) // 6
代碼解讀:
- 定義函數(shù) maxProfit,接受 2 個(gè)參數(shù),list 為股票價(jià)格趨勢(shì),fee 為手續(xù)費(fèi)
- 定義 profit,并在最后將 profit 返回
- 假定:買入時(shí)機(jī)為第 1 天(即:list[0])
- 如果股票價(jià)格降低,則調(diào)整買入時(shí)機(jī)為后一天
- 如果有利潤(rùn),則賣出,并計(jì)算利潤(rùn),再次調(diào)整買入時(shí)機(jī)(即循環(huán)步驟 3、4、5)
核心思想:
- 假定:買入時(shí)機(jī)為第 1 天
- 如果股票價(jià)格降低,則調(diào)整買入時(shí)機(jī)為后一天
- 如果有利潤(rùn),則賣出,并且計(jì)算利潤(rùn)
- 再次 - 假定:買入時(shí)機(jī)(循環(huán)步驟 1-3)
4、情侶牽手
N 對(duì)情侶坐在連續(xù)排列的 2N 個(gè)座位上,想要牽到對(duì)方的手。計(jì)算最少交換座位的次數(shù),以便每對(duì)情侶可以并肩坐在一起。一次交換可選擇任意兩人,讓他們站起來(lái)交換座位。人和座位用 0 到 2N-1 的整數(shù)表示,情侶們按順序編號(hào),第一對(duì)是 (0, 1),第二對(duì)是 (2, 3),以此類推,最后一對(duì)是 (2N-2, 2N-1)。
這些情侶的初始座位 row[i] 是由最初始坐在第 i 個(gè)座位上的人決定的。
示例 1:
輸入: row = [0, 2, 1, 3] 輸出: 1 解釋: 我們只需要交換 row[1]和 row[2]的位置即可。
示例 2:
輸入: row = [3, 2, 0, 1] 輸出: 0 解釋: 無(wú)需交換座位,所有的情侶都已經(jīng)可以手牽手了。
- /**
- * @param {number[]} row
- * @return {number}
- */
- var minSwapsCouples = function(row) {
- let hashMap = {}; // {人: 位置}
- for(let i=0; i<row.length; i++){
- hashMap[row[i]] = i
- }
- let ans = 0; // 交換次數(shù)
- for(let i=0; i<row.length; i+=2){ // 按照一對(duì)遍歷
- let lover=row[i]^1; // row[i]的情侶
- if(hashMap[lover] !== i+1){ // 如果不相鄰,就交換
- ans++;
- hashMap[row[i+1]] = hashMap[lover]; // row[i+1]使用了 lover 的下標(biāo)
- // 交換位置
- [row[i+1], row[hashMap[lover]]] = [row[hashMap[lover]], row[i+1]]
- hashMap[lover]=i+1; // lover 的下標(biāo)改為 i+1 使其相鄰
- }
- }
- return ans;
- };
核心思想:
- 遍歷數(shù)組,檢查情侶是否相鄰,如果不是,就調(diào)整一個(gè)的位置使其相鄰。
- 我們盡量只挪動(dòng)情侶中的一位,另一位不動(dòng),這樣才能保證挪動(dòng)次數(shù)最少。
- 這樣交換,不會(huì)影響其他的情侶。
代碼解讀:
- 定義函數(shù) minSwapsCouples,接收一個(gè)參數(shù) row 即情侶座位關(guān)系的數(shù)組。
- 得到{人: 位置}的對(duì)象。
- 定義交換次數(shù) ans,并在最后將其返回。
- 遍歷左側(cè)情侶,檢查右側(cè)是否為 TA 的情侶,如果不是,則找到 TA 的情侶并調(diào)換座位。
5、參考資料
本文題目來(lái)源:力扣(LeetCode)
鏈接:https://leetcode-cn.com
孫領(lǐng)芝: 一個(gè)愛(ài)好碳水的山東大妞。