【筆記】JavaScript版數據結構與算法——基礎算法之“字符串類”(557.反轉字符串中的單詞 III、696.計數二進制子串)


一、環境搭建

環境搭建主要支持以下內容:

搭建過程可以參考文章:【筆記】再學JavaScriptES(6-10)全版本語法——課程介紹與環境搭建

兩個很重要的文件:

  • .babelrc:配置對ES6的支持
  • .eslintrc.js:代碼格式檢查

Vue中ESlint配置文件eslintrc.js文件詳解

mkdir leetcode
cd leetcode
npm init -y

1.安裝

cnpm i -D jest
cnpm i babel-jest @babel/core regenerator-runtime @babel/preset-env @babel/preset-react

編輯package.json

{
  "scripts": {
    "test": "jest"
  }
}

編輯.babelrc

{
  "presets": ["@babel/react","@babel/env"]
} 

2. 創建js文件

新建code\sum.js

function sum(a, b) {
  return a + b;
}
export default sum;

新建test\sum.test.js

import sum from './index'

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

3. 啓動測試

npm test

執行結果
在這裏插入圖片描述
若報錯:Plugin/Preset files are not allowed to export objects,only functions
處理方法
(1)將所有有關babel的包都升級爲7.0版本

  • “@babel/core”: “^7.2.2”,
  • “@babel/preset-env”: “^7.3.1”,
  • “@babel/preset-react”: “^7.0.0”,
  • “babel-loader”: “^8.0.5”,

並且修改.babel文件

  • {“presets”:["@babel/react","@babel/env",]}

(2)降級到babel6.0版本

  • “babel-core”: “^6.26.3”,
  • “babel-preset-env”: “^1.7.0”,
  • “babel-preset-react”: “^6.24.1”,
  • “babel-loader”: “^7.1.5”,

對應修改.babelrc文件

  • {“presets”: [“react”, “env”]}

babel捨棄了以前的babel--的命名方式,改成了@babel/-。修改依賴和.babelrc文件後就能正常啓動項目了。babel-core7.0之後,包名升級爲@babel/core。

二、反轉單詞原理

1.題目

557. 反轉字符串中的單詞 III - 力扣(LeetCode)

給定一個字符串,你需要反轉字符串中每個單詞的字符順序,同時仍保留空格和單詞的初始順序。

示例 1:

輸入: “Let’s take LeetCode contest”
輸出: “s’teL ekat edoCteeL tsetnoc”

注意:在字符串中,每個單詞由單個空格分隔,並且字符串中不會有任何額外的空格。

2.思路分析

先使用split將字符串拆解爲單詞放入數組中,然後使用map對數組進行遍歷執行操作(split單詞拆分爲單字符數組、reverse反轉、join拼爲單詞),最後使用join拼成字符串

依次調用split、map、split、reverse、join、join

3.所用到的方法

String.prototype.split:字符串(按字符)拆爲數組
Array.prototype.map:對數組進行遍歷並對每項執行操作
Array.prototype.reverse:數組順序反轉
Array.prototype.join:數組(按字符)拼爲字符串

4.題解及優化

let reverseWords = function (s) {
  let arr = s.split(' ')
  let result = arr.map(item => item.split('').reverse().join(''))
  return result.join(' ')
}

優化(去除中間變量,節約內存)

let reverseWords = function (s) {
  return s.split(' ').map(item => item.split('').reverse().join('')).join(' ')
}

在這裏插入圖片描述


其他解法(使用了rest參數,但相對耗時)

let reverseWords = function (s) {
  return s.split(' ').map(i => [...i].reverse().join('')).join(' ')
}

在這裏插入圖片描述


我的最初題解。。。

let reverseWords = function (s) {
  // 先將字符串拆解爲單詞放入數組中,數組中元素的順序就是單詞的原有順序
  let arr = s.split(' ')
  let str = ''
  let str1 = ''
  let arr1 = []
  console.log(arr)
  // 將數組中的每一項倒序
  for (let item of arr) {
    for (let i = 0; i < item.length; i++) {
      str = str.concat(item.substring(item.length - i - 1, item.length - i))
      console.log(str)
    }
    // 將翻轉後的字符串合併到一個數組中並用空格分開轉爲字符串
    arr1.push(str)
    str1 = arr1.join(' ')
    str = ''
    console.log(str1)
  }
  return str1
}
const s = "Let's take LeetCode contest"
console.log(reverseWords(s))

在這裏插入圖片描述

三、計數二進制子串

1.題目

696. 計數二進制子串 - 力扣(LeetCode)

給定一個字符串 s,計算具有相同數量0和1的非空(連續)子字符串的數量,並且這些子字符串中的所有0和所有1都是組合在一起的。

重複出現的子串要計算它們出現的次數。

示例 1 :

輸入: “00110011”
輸出: 6
解釋: 有6個子串具有相同數量的連續1和0:“0011”,“01”,“1100”,“10”,“0011” 和 “01”。

請注意,一些重複出現的子串要計算它們出現的次數。
另外,“00110011”不是有效的子串,因爲所有的0(和1)沒有組合在一起。

示例 2 :

輸入: “10101”
輸出: 4
解釋: 有4個子串:“10”,“01”,“10”,“01”,它們具有相同數量的連續1和0。

注意:

  • s.length 在1到50,000之間。
  • s 只包含“0”或“1”字符。

2.思路分析

類似於數學幾何題畫輔助線,我們得到如下圖譜:
在這裏插入圖片描述

3.所用到的方法

見題解

4.題解及優化

課程提供的解法(有bug,通不過)

let countBinarySubstrings = (s) => {
  // 建立數據結構,堆棧,保存數據
  let r = []
  // 給定任意子輸入都返回第一個符合條件的子串
  let match = (s) => {
    let j = s.match(/^(0+|1+)/)[0]
    let o = (j[0] ^ 1).toString().repeat(j.length)
    let reg = new RegExp(`^(${j}${o})`)
    if (reg.test(s)) {
      return RegExp.$1
    } else {
      return ''
    }
  }
  // 通過for循環控制程序運行的流程
  for (let i = 0, len = s.length - 1; i < len; i++) {
    let sub = match(s.slice(i)) // 返回從第i位開始的字符串
    if (sub) {
      r.push(sub)
    }
  }
  return r.length
}

這種解法超長測試用例會導致RegExp too big的問題

下面推薦幾種LeetCode小夥伴的解法:


巧妙解法1

let countBinarySubstrings = (s) => {
  let n = 0, arr = s.match(/([1]+)|([0]+)/g) // 對於0和1分組捕獲
  for (let i = 0; i < arr.length - 1; i++) {
  	// 統計每相鄰兩組之間元素長度的最小值,即爲這兩組中符合條件的子串個數
    n += Math.min(arr[i].length, arr[i + 1].length)
  }
  return n
}

因爲已知

  • 000111必定有三個子串
  • 00011必定有兩個子串
  • 0111必定有1個子串

以此類推, 每兩組數據之間長度最短的值爲子串的數量

先統計連續的0和1分別有多少個,如:111100011000,得到4323;在4323中的任意相鄰兩個數字,取小的一個加起來,就是3+2+2 = 7

在這裏插入圖片描述


巧妙解法2

let countBinarySubstrings = function (s) {
  // pre 前一個數字連續出現的次數,curr 當前數字連續出現的次數,n 統計子串個數
  let n = 0, pre = 0, curr = 1
  for (let i = 0, len = s.length; i < len - 1; i++) {
    if (s[i] == s[i+1]) {
      // 相同,則當前數字出現的次數curr加1
      curr++
    } else {
      // 不同,則當前數字事實上變成了前一個數字,當前數字的次數重置爲1
      pre = curr
      curr = 1
    }
    // 前一個數字出現的次數 >= 後一個數字出現的次數,則一定包含滿足條件的子串
    if (pre >= curr) n++
  }
  return n
}

以00110011爲測試用例:

i pre curr n 說明
0 0 1 0 初始狀態
0 0 2 0 √,首次循環
1 2 1 1 ×
2 2 2 2
3 2 1 0 ×
4 2 2 0
5 2 1 0 ×
6 2 2 0

在這裏插入圖片描述

其他常規解法1

var countBinarySubstrings = function(s) {
    if (s.length < 2) return 0;
    // 模擬隊列
    let queue = [], i = 1, res = 0;
    queue.push(s[0]);
    while (i < s.length) {
        // 如果隊頭元素與當前元素相等
        if (queue[0] == s[i]) {
            let last = queue[queue.length - 1];
            queue.push(s[i]);
            // 如果隊列尾部元素與當前元素不等,則必須計算一次,且重置隊列
            if (last != s[i]) {
                res++;
                let index = queue.indexOf(last);
                queue = queue.slice(index + 1);
            }
        } else {
            // 如果與隊列頭不相等
            res++;
            queue.shift();
            queue.push(s[i]);
        }
        i++;
    }
    return res;
};
i queue last res 說明
1 0 0 0 初始狀態
1 00 0 0 首次循環
2 01 1 shift
3 11 2 shift
4 10 3 shift
5 00 4 shift
6 01 5 shift
7 11 6 shift

在這裏插入圖片描述


其他常規解法2

比較耗時

var countBinarySubstrings = function(s) {
  let result = 0;

  // 字符串匹配算法
  const match = (subString) => {
    // 先找到開頭的連續數字[0|1]
    let startStr = subString.match(/^((0+)|(1+))/)[0]
    subString = subString.slice(0, startStr.length * 2)
    // 推算出組合字符串
    let endStr = (startStr[0] ^ 1).toString().repeat(startStr.length)
    // 查看字符串中是否匹配組合字符串
    return subString.startsWith(`${startStr}${endStr}`)
  }
  // 循環計算每個子串中出現符合條件的字符情況,如果找到就+1,並break到下一個子串
  for (let index = 0; index < s.length-1; index++) {
    let subString = s.slice(index)
    if (match(subString)) {
      result += 1;
    }
  }
  return result
};

在這裏插入圖片描述


拓展:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章