DAILY DOCDAILY DOC
Rust
Node
Notes
Ubuntu
Leetcode
  • it-tools
  • excalidraw
  • linux-command
Rust
Node
Notes
Ubuntu
Leetcode
  • it-tools
  • excalidraw
  • linux-command
  • Array

    • 数组
    • 二分查找
    • moveZeros
  • Dynamic-programming

    • 动态规划
  • 刷题指南
  • String

    • 字符串
  • bitwise-operator

    • 位运算符
  • heap
  • history

    • [1014] 最佳观光组合
    • [1022] 从根到叶的二进制数之和
    • [104] 二叉树的最大深度
    • [11] 盛最多水的容器
    • [110] 平衡二叉树
    • [1227] 飞机座位分配概率
    • [129] 求根节点到叶节点数字之和
    • [1306] 跳跃游戏 III
    • [148] 排序链表
    • 155.最小栈
    • [165] 比较版本号
    • 1763. 最长的美好子字符串
    • [1870] 准时到达的列车最小时速
    • [199] 二叉树的右视图
    • [21] 合并两个有序链表
    • 215.数组中的第 k 个最大元素
    • [2306] 公司命名
    • [234] 回文链表
    • [2516] 每种字符至少取 K 个
    • [316] 去除重复字母
    • [3171] 找到按位或最接近 K 的子数组
    • [322] 零钱兑换
    • [41] 缺失的第一个正数
    • [44] 通配符匹配
    • [494] 目标和
    • [509] 斐波那契数
    • [518] 零钱兑换 II
    • [62] 不同路径
    • [676] 实现一个魔法字典
    • 70 爬楼梯
    • [718] 最长重复子数组
    • [78] 子集
    • [82] 删除排序链表中的重复元素 II
    • [871] 最低加油次数
    • [88] 合并两个有序数组
    • [887] 鸡蛋掉落
    • 958.二叉树的完全性检验
    • [98] 验证二叉搜索树
    • [983] 最低票价
    • leetcode practice
    • 约瑟夫问题
    • 移除重复节点
  • linked-list

    • 706. 设计哈希映射
    • 链表
  • stack

    • stack
  • tree

    • Tree Traversal
    • 二叉树的最近公共祖先
    • 二叉树
    • 题目
  • leetCode 刷题
  • 回溯
  • 排序算法

字符串

回文

判断一个字符串是不是回文

对撞指针法 双指针从 2 边开始遍历,不一样则不是回文

function isPalindrome(str) {
    let left = 0,
        right = str.length - 1;
    while (left < right) {
        if (str[left] !== str[right]) {
            return false;
        }
        left++;
        right--;
    }
    return true;
}

反转字符串法 反转字符串法是一种简单直接的方法,它先将字符串反转,再与原字符串进行比较。

function isPalindrome(str) {
    const reversed = str.split('').reverse().join('');
    return str === reversed;
}

递归法 递归法是一种比较巧妙的方法,它利用递归函数在字符串中依次比较首尾字符是否相等。

function isPalindrome(str) {
    if (str.length <= 1) {
        return true;
    }
    if (str[0] !== str[str.length - 1]) {
        return false;
    }
    return isPalindrome(str.slice(1, -1));
}
// 借助辅助函数,无需拷贝字符串开销
function isPalindrome(str) {
    return helper(0, str.length - 1);

    function helper(start, end) {
        if (end - start <= 1) {
            return true;
        }
        if (str[start] !== str[end]) {
            return false;
        }
        return helper(start + 1, end - 1);
    }
}

中心扩展法

function isPalindrome(s) {
    const len = s.length;
    for (let i = 0; i < len / 2; i++) {
        if (s[i] !== s[len - 1 - i]) {
            return false;
        }
    }
    return true;
}

字典树

通用 通过 hash 表存储

class TrieNode {
    constructor() {
        this.children = new Map();
        this.isEnd = false;
    }
}

class Trie {
    constructor() {
        this.root = new TrieNode();
    }

    insert(word) {
        let node = this.root;
        for (let i = 0; i < word.length; i++) {
            let c = word[i];
            if (!node.children.has(c)) {
                node.children.set(c, new TrieNode());
            }
            node = node.children.get(c);
        }
        node.isEnd = true;
    }

    search(word) {
        let node = this.root;
        for (let i = 0; i < word.length; i++) {
            let c = word[i];
            if (!node.children.has(c)) {
                return false;
            }
            node = node.children.get(c);
        }
        return node.isEnd;
    }
}

数组存储

仅限于只包含小写字母 a-z 的字符串。 word[i].charCodeAt(0) - 97 是通过字符的编码,算出在 26 个小写字母中,该字母的 index

class TrieNode {
    constructor() {
        this.children = new Array(26).fill('');
        this.isEnd = false;
    }
}

class Trie {
    constructor() {
        this.root = new TrieNode();
    }

    insert(word) {
        let node = this.root;
        for (let i = 0; i < word.length; i++) {
            let index = word[i].charCodeAt(0) - 97;
            if (!node.children[index]) {
                node.children[index] = new TrieNode();
            }
            node = node.children[index];
        }
        node.isEnd = true;
    }

    search(word) {
        let node = this.root;
        for (let i = 0; i < word.length; i++) {
            let index = word[i].charCodeAt(0) - 97;
            if (!node.children[index]) {
                return false;
            }
            node = node.children[index];
        }
        return node.isEnd;
    }
}

214. 最短回文串

暴力解法

Javascript
const shortestPalindrome = s => {
    const len = s.length;
    const rev_s = s.split('').reverse().join('');
    for (let i = len; i >= 0; i--) {
        if (s.substring(0, i) == rev_s.substring(len - i)) {
            return rev_s.substring(0, len - i) + s;
        }
    }
};

KMP

28. 找出字符串中第一个匹配项的下标

暴力解法

时间复杂度 O(n^2)

Javascript
/**
 * @param {string} haystack
 * @param {string} needle
 * @return {number}
 */
var strStr = function (haystack, needle) {
    const len = needle.length;
    const rowLen = haystack.length;
    for (let i = 0; i + len - 1 < rowLen; i++) {
        // console.log(haystack.substring(i,len+i) , needle);
        if (haystack.substring(i, len + i) === needle) {
            return i;
        }
    }
    return -1;
};

336. 回文对

20. 有效的括号

辅助栈

Javascript
/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function (s) {
    if (s.length <= 1) return false;
    const stack = [];
    for (let i = 0; i < s.length; i++) {
        if (['(', '{', '['].includes(s[i])) {
            stack.push(s[i]);
        } else {
            if (stack.length <= 0) return false;
            const item = stack.pop();
            if (item === '(' && s[i] !== ')') {
                return false;
            }
            if (item === '{' && s[i] !== '}') {
                return false;
            }
            if (item === '[' && s[i] !== ']') {
                return false;
            }
        }
    }
    return stack.length === 0;
};

31. 下一个排列

Javascript
/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var nextPermutation = function (nums) {
    let i = nums.length - 2;
    while (i >= 0 && nums[i] >= nums[i + 1]) {
        i--;
    }
    if (i >= 0) {
        let j = nums.length - 1;
        while (j >= 0 && nums[i] >= nums[j]) {
            j--;
        }
        [nums[i], nums[j]] = [nums[j], nums[i]];
    }
    reverse(nums, i + 1, nums.length - 1);

    function reverse(nums, start, end) {
        while (start < end) {
            [nums[start], nums[end]] = [nums[end], nums[start]];
            start++;
            end--;
        }
    }
};

3. 无重复字符的最长子串

暴力解法

details
/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function (s) {
    let maxLength = 0;
    // Generate all substrings and check for unique characters
    for (let i = 0; i < s.length; i++) {
        for (let j = i + 1; j <= s.length; j++) {
            let substring = s.slice(i, j);
            if (new Set(substring).size === substring.length) {
                maxLength = Math.max(maxLength, substring.length);
            }
        }
    }

    return maxLength;
};

滑动窗口

Javascript
/**
 *
 * @param {string} s
 * @returns {number}
 */
var lengthOfLongestSubstring = function (s) {
    const len = s.length;
    if (s.length <= 1) return len;
    const set = new Set();

    let ans = 0;

    let left = 0;
    let right = 0;
    while (right < len) {
        const char = s[right];
        right++;

        while (set.has(char)) {
            const char = s[left];
            left++;
            set.delete(char);
        }

        ans = Math.max(ans, right - left);
        set.add(char);
    }
    return ans;
};

1616. 分割两个字符串得到回文串

暴力解法

从 0 开始划分字符串。判断划分出来的字符串是否是回文。

时间复杂度 O(n^2) 空间复杂度 O(1)

Javascript
/**
 * @param {string} a
 * @param {string} b
 * @return {boolean}
 */
var checkPalindromeFormation = function (a, b) {
    const len = a.length;
    if (len === 1) return true;
    for (let i = 0; i < len; i++) {
        if (isPalindrome(a.slice(0, i) + b.slice(i)) || isPalindrome(b.slice(0, i) + a.slice(i))) {
            return true;
        }
    }
    return false;

    function isPalindrome(str) {
        const len = str.length;
        for (let i = 0; i < len / 2; i++) {
            if (str[i] !== str[len - 1 - i]) {
                return false;
            }
        }
        return true;
    }
};

双指针

如果a[i] === b[len-i]就可以 不用判断了。减少重复判断

Javascript
/**
 * @param {string} a
 * @param {string} b
 * @return {boolean}
 */
var checkPalindromeFormation = function (a, b) {
    return check(a, b) || check(b, a);

    function check(a, b) {
        const len = a.length;

        let left = 0,
            right = len - 1;
        while (left < right && a[left] === b[right]) {
            left++;
            right--;
        }
        if (left >= right) {
            return true;
        }
        return isPalindrome(a, left, right) || isPalindrome(b, left, right);
    }

    function isPalindrome(str, left, right) {
        while (left < right) {
            if (str[left] !== str[right]) {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
};

1625. 执行操作后字典序最小的字符串

失败的尝试

Javascript
/**
 * @param {string} s
 * @param {number} a
 * @param {number} b
 * @return {string}
 */
var findLexSmallestString = function (s, a, b) {
    let cur = s,
        len = s.length;
    console.log('raw', cur, a, b);

    while (true) {
        let smaller = cur;
        let i = 0;
        while (i < len) {
            let reverse = cur.slice(-b) + cur.slice(0, len - b);
            smaller = smallerThen(reverse, smaller) ? reverse : smaller;
            console.log('smaller', smaller, reverse);
            cur = reverse;
            i++;
        }
        let next = addA(smaller, a);
        console.log('next', next);
        if (smallerThen(next, cur)) {
            cur = next;
        } else {
            break;
        }
    }
    return cur;

    function addA(str, a) {
        let len = str.length,
            i = 1,
            cur = str;
        while (i < len) {
            let num = cur.charAt(i) - 0,
                sum = num + a;
            sum = sum > 9 ? sum % 10 : sum;
            cur = cur.slice(0, i) + sum + cur.slice(i + 1);
            i += 2;
        }
        return cur;
    }

    function smallerThen(a, b) {
        let i = 0,
            len = a.length;

        while (i < len) {
            let charA = a.charCodeAt(i),
                charB = b.charCodeAt(i);
            if (charA < charB) {
                return true;
            } else if (charA > charB) {
                return false;
            }
            i++;
        }
        return false;
    }
};

console.log(findLexSmallestString('43987654', 7, 3));
// console.log(findLexSmallestString('74', 5, 1));
// console.log(findLexSmallestString('74', 5, 1));

搞了一个上午没搞出来。最后把答案粘上去了

Javascript 官方答案
var findLexSmallestString = function (s, a, b) {
    const n = s.length;
    let res = s;
    s = s + s;
    const g = gcd(b, n);

    for (let i = 0; i < n; i += g) {
        const t = [...s.slice(i, i + n)];
        add(t, n, a, 1);
        if (b % 2 !== 0) {
            add(t, n, a, 0);
        }
        const tStr = t.join('');
        if (tStr < res) {
            res = tStr;
        }
    }
    return res;
};

const add = (t, n, a, start) => {
    let minVal = 10,
        times = 0;
    for (let i = 0; i < 10; i++) {
        const added = (t[start].charCodeAt() - '0'.charCodeAt() + i * a) % 10;
        if (added < minVal) {
            minVal = added;
            times = i;
        }
    }
    for (let i = start; i < n; i += 2) {
        t[i] = String.fromCharCode('0'.charCodeAt() + ((t[i].charCodeAt() - '0'.charCodeAt() + times * a) % 10));
    }
};

const gcd = (num1, num2) => {
    while (num2 !== 0) {
        const temp = num1;
        num1 = num2;
        num2 = temp % num2;
    }
    return num1;
};

1032. 字符流

暴力解法

把后缀 存到一张 hash 表。去 hash 表查找是否存在以 letter 结尾的后缀。(超时了 ❌)

Javascript
/**
 * @param {string[]} words
 */
var StreamChecker = function (words) {
    const map = new Map();
    this.queryStr = '';
    words.forEach(val => {
        map.set(val, 1);
    });
    this.map = map;
};

/**
 * @param {character} letter
 * @return {boolean}
 */
StreamChecker.prototype.query = function (letter) {
    this.queryStr += letter;
    let right = this.queryStr.length - 1;
    while (right >= 0) {
        if (this.map.has(this.queryStr.slice(right))) {
            return true;
        }
        right--;
    }

    return false;
};

/**
 * Your StreamChecker object will be instantiated and called as such:
 * var obj = new StreamChecker(words)
 * var param_1 = obj.query(letter)
 */

优化一下 (通过了 ✅)

Javascript
/**
 * @param {string[]} words
 */
var StreamChecker = function (words) {
    this.queryStr = '';
    this.words = words;
};

/**
 * @param {character} letter
 * @return {boolean}
 */
StreamChecker.prototype.query = function (letter) {
    this.queryStr += letter;
    for (let word of this.words) {
        // 最后一个字符相等
        let last = word.length - 1;
        let letterLast = this.queryStr.length - 1;
        while (word[last] === this.queryStr[letterLast] && last >= 0 && letterLast >= 0) {
            last--;
            letterLast--;
        }
        if (last === -1) {
            return true;
        }
    }
    return false;
};

/**
 * Your StreamChecker object will be instantiated and called as such:
 * var obj = new StreamChecker(words)
 * var param_1 = obj.query(letter)
 */

字典树

时间复杂度: 初始化操作的时间复杂度为 O(mn),其中 m 是字符串数组中字符串的平均长度,n 是字符串数组中字符串的个数。 query 操作的时间复杂度为 O(m),其中 m 是当前字符流中字符的个数。 空间复杂度: 字典树的空间复杂度为 O(mn),其中 m 是字符串数组中字符串的平均长度,n 是字符串数组中字符串的个数。 StringBuilder 的空间复杂度为 O(m),其中 m 是当前字符流中字符的个数。

Javascript
class TrieNode {
    constructor() {
        this.isEnd = false;
        this.children = new Array(26).fill(null);
    }
}

/**
 * @param {string[]} words
 */
var StreamChecker = function (words) {
    this.root = new TrieNode();
    this.sb = '';
    // 将所有字符串插入到字典树中
    for (let word of words) {
        let node = this.root;
        for (let i = word.length - 1; i >= 0; i--) {
            // 计算出 index  a 的charcode 是 97
            let idx = word.charCodeAt(i) - 97;
            if (!node.children[idx]) {
                node.children[idx] = new TrieNode();
            }
            node = node.children[idx];
        }
        node.isEnd = true;
    }
};

/**
 * @param {character} letter
 * @return {boolean}
 */
StreamChecker.prototype.query = function (letter) {
    this.sb += letter;
    let node = this.root;
    // 从根节点开始往下遍历字典树
    for (let i = this.sb.length - 1; i >= 0 && node; i--) {
        let idx = this.sb.charCodeAt(i) - 97;
        node = node.children[idx];
        if (node && node.isEnd) {
            return true;
        }
    }
    return false;
};

/**
 * Your StreamChecker object will be instantiated and called as such:
 * var obj = new StreamChecker(words)
 * var param_1 = obj.query(letter)
 */

1574. 删除最短的子数组使剩余数组有序

双指针

Javascript
/**
 * @param {number[]} nums
 * @return {number}
 */
var findLengthOfShortestSubarray = function (nums) {
    const n = nums.length;
    let l = 0,
        r = n - 1;
    // 找到最右侧的降序子数组
    while (l < n - 1 && nums[l] <= nums[l + 1]) {
        l++;
    }
    if (l === n - 1) {
        // 数组已经有序
        return 0;
    }
    // 找到最左侧的降序子数组
    while (r > 0 && nums[r] >= nums[r - 1]) {
        r--;
    }
    let ans = Math.min(n - l - 1, r);

    let i = 0,
        j = r;
    while (i <= l && j < n) {
        if (nums[i] <= nums[j]) {
            ans = Math.min(ans, j - i - 1);
            i++;
        } else {
            j++;
        }
    }
    return ans;
};

1638. 统计只差一个字符的子串数目

双指针

题目分析 首先,我们可以先将 s 中的每个非空子串都枚举出来,然后将每个子串的每个字符都分别替换成 a-z 中的字符,并判断是否等于 t。这种做法时间复杂度为 O(n^3 * 26),无法通过本题。 其次,我们考虑优化上述做法。我们可以将枚举子串和替换字符的过程合并起来,用一个双指针分别指向 s 和 t,当 s[i] != t[j] 时,我们可以将 i 和 j 分别右移一位,这样就可以快速地找到 s 和 t 中恰好只有一个字符不同的所有子字符串对。时间复杂度为 O(n^2)。

Javascript
function countSubstrings(s, t) {
    let res = 0;
    for (let i = 0; i < s.length; i++) {
        for (let j = 0; j < t.length; j++) {
            let diff = 0;
            for (let k = 0; i + k < s.length && j + k < t.length; k++) {
                if (s[i + k] !== t[j + k]) {
                    diff++;
                    if (diff > 1) break;
                }
                if (diff === 1) res++;
            }
        }
    }
    return res;
}
Last Updated:
Contributors: rosendo