代码片段-LC简单题:大整数字符串加法等

本文介绍了多种算法问题的解决方案,包括寻找正整数序列中的第N位数字、计算丑数、字符串相加、合并有序数组、无重复字符的最长子串、树节点路径求和等。涉及到了位运算、堆数据结构、字符串操作及树的遍历策略。这些算法在编程面试和实际开发中具有重要意义。
摘要由CSDN通过智能技术生成

题库:https://codetop.cc/home
https://juejin.cn/post/6992775762491211783
https://github.com/nettee/little-algorithm
https://lucifer.ren/fe-interview/#/?id=%E7%BC%96%E7%A8%8B%E9%A2%98-%E2%9C%8D%ef%b8%8f

400. 第 N 位数字

在这里插入图片描述
首先题干意思是 把所有正整数排在一起,
1, 2, … 9,10,11, 12…一位一位地看成1 2 … 9 1 0 1 1 1 2
输入一个n,计算第n个位置上的数是几;
前9个数都是1位的直接返回n就行了,但后面有那么多两位数、三位数…得仔细考虑:
即使数值被拆成多个数位了,我们仍可以把原数值想象成 用[]包裹的一个个单元
1,2,...,9,10,11,...99,100,101 => [1][2]...[9][1,0][1,1]...[9,9][1,0,0][1,0,1]
对正整数划分若干区间:0~9, 10~99, 100~999, 1000~9999…
那么在相同的区间内的单元 所占的位数相同,区间如果向后移动 单元的占位会增加
第1个区间 单元占位均为1,第N个区间 单元占位均为N,
那么每个区间总共占了多少位 是可以计算的,输入一个n肯定可以计算出是它在第几个区间 以及区间内的偏移量。

// https://leetcode-cn.com/problems/nth-digit/
const findNthDigit = (n) => {
    if (n < 10) return n;
    // 按照数值所占位数 可对正整数划分若干区间:0~9, 10~99, 100~999, 1000~9999... 位数递增1
    // 0实际上并不应该存在 为了形式一致才写成这样,对答案没影响 因为1~9其实已经特殊处理直接返回n了
    let digitUnit = 1                           // 区间内每个数值占几位,起个名叫“单元”吧Unit
    let [bottom, top] = [0, 10]                 // 区间数值的上下限,左闭右开 [0,10) [10,100) [100,1000)...
    while (n > (top - bottom) * digitUnit) {    // 当n大于 当前区间所有单元 占的位数
        n -= (top - bottom) * digitUnit         // 我们希望不断右移区间,让n减掉当前区间占的位数
        digitUnit += 1                          // 每个数占的位数+1
        bottom = top                            // 更新下限
        top = top * 10                          // 更新上限,左闭右开的好处就是可以直接*10
    }
    // 剩下的n除以当前区间的单元占位 得到的商num即表示 它是此区间的第几个单元,余数r是单元内字符的位置偏移量
    let [num, r] = [Math.floor(n / digitUnit), n % digitUnit] 
    return +(num + bottom + '')[r] // +''转string访问下标再用+转number
};

264. 丑数

在这里插入图片描述

自己定义最小堆 每次弹堆顶 即可得到递增的结果
答案:

// https://leetcode-cn.com/problems/chou-shu-lcof/submissions/
var nthUglyNumber = function (n) {
    //利用堆能够自动保持自身的堆顶元素是最大元素或者最小元素
    //这里需要用到集合Set保证没有向堆中插入相同大小的元素,假如插入了就不能保证堆顶元素是第n个丑数了
    //因为要循环遍历n次
    let set = new Set();
    set.add(1);
    let heap = new minHeap();
    heap.insert(1);
    let top = 0;
    let list = [2, 3, 5];
    for (let j = 0; j < n; j++) {
        top = heap.pop();
        for (let i of list) {
            if (!set.has(i * top)) {
                set.add(i * top);
                heap.insert(i * top)
            }
        }
    }
    return top;
};

最小堆定义:

var swap = (arr, i, j) => {
    [arr[i], arr[j]] = [arr[j], arr[i]]
}
class minHeap {
    constructor(arr = []) {
        this.container = [];
        arr.forEach(this.insert.bind(this))
    }
    insert(data) {
        const { container } = this;
        //插入需要插入对尾,向前比较
        container.push(data);
        let index = container.length - 1;
        while (index) {
            let parent = Math.floor((index - 1) / 2);
            if (container[index] >= container[parent]) {
                break;
            }
            swap(container, index, parent)
            index = parent
        }
    }
    pop() {
        const { container } = this;
        if (!container.length) return null;
        //删除,首尾互换,取出原来堆顶元素,减少数组长度,从上到下依次比较
        swap(container, 0, container.length - 1)
        let res = container.pop();
        let index = 0;
        let exchange = index * 2 + 1;
        while (exchange < container.length) {
            if (exchange + 1 < container.length && container[exchange] > container[exchange + 1]) {
                exchange++;
            }
            if (container[exchange] >= container[index]) {
                break;
            }
            swap(container, index, exchange)
            index = exchange;
            exchange = index * 2 + 1;
        }
        return res;
    }
    top() {
        const { container } = this;
        if (container.length) return container[0];
        return null
    }
}

415. 字符串相加

维护临时工作变量carry负责收集每一位的和,carry的个位添入res,十位参与下一个位置的加法;
下标索引i, j各自从两个字符串尾部向前遍历取出字符,记得各自判断是否到头了

// https://leetcode-cn.com/problems/add-strings/
const addStrings = (numStr1, numStr2) => {
    let [res, carry, i, j] = [[], 0, numStr1.length - 1, numStr2.length - 1];
    while (i >= 0 || j >= 0 || carry != 0) {
        (i >= 0) && (carry += Number(numStr1[i--]));
        (j >= 0) && (carry += Number(numStr2[j--]));
        res.unshift(carry % 10);
        carry = Math.floor(carry / 10);
    }
    return res.join('');
};

88. 合并两个有序数组

题目说第一个数组后面空了一段空间以容纳第二个数组,因此从第一个数组的尾部t开始填空,决定此位置放的元素来自于哪一个数组;
数组

// https://leetcode-cn.com/problems/merge-sorted-array/
const merge = (nums1, m, nums2, n) => {
    let [i, j, t] = [m - 1, n - 1, nums1.length - 1];
    // 短的还剩
    while (j >= 0) {
    	// 长的还剩
        while (i >= 0 && nums1[i] > nums2[j]) {
            [nums1[t], nums1[i]] = [nums1[i], nums1[t]];
            t--; i--;
        }
        [nums1[t], nums2[j]] = [nums2[j], nums1[t]];
        t--; j--;
    }
};

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

滑动窗口 由左坐标+当前长度构成 试探增加当前长度并更新窗口内元素,本题要找最长长度,为满足无重复的条件 设置一个lookup集合检查新元素是否可填入窗口

// https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
const lengthOfLongestSubstring = (s) => {
    if (!s.length) return 0
    let [leftIdx, currLen, maxLen] = [0, 0, 0];
    let workSet = new Set();
    for (let idx in s) {
        currLen++;
        while (workSet.has(s[idx])) {
            workSet.delete(s[leftIdx]);
            leftIdx++;
            currLen--;
        }
        maxLen = Math.max(maxLen, currLen);
        workSet.add(s[idx]);
    }
    return maxLen;
}

129. 求根节点到叶节点上的数字 之和

本题结点上的数字要随路径拼成一整个数字 因此自顶向下*10加和即可得到路径代表的数字大小,到达叶节点时返回求的和

// https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/
const sumNumbers = (root) => {
    const recur = (root, acc = 0) => {
        if (!root) return 0;
        const sum = acc * 10 + root.val;
        if (!root.left && !root.right) { // 说明当前结点为叶节点 可以返回总和了
            return sum;
        };
        return recur(root.left, sum) + recur(root.right, sum)
    }
    return recur(root);
};

112. 路径总和

本题要检查一颗树中 是否存在一条根到叶节点的路径 的结点和 等于目标值,自顶向下消耗目标值,到达叶节点时判断当前节点值是否等于剩余目标值 (子问题划分视角)

// https://leetcode-cn.com/problems/path-sum/
const hasPathSum = (root, targetSum) => {
    const recur = (root, target) => {
        if (!root) return false;
        if (!root.left && !root.right) { // 说明当前结点为叶节点 可以判断总和了
            return root.val == target;
        };
        return recur(root.left, target - root.val) || recur(root.right, target - root.val);
    }
    return recur(root, targetSum);
};

凡是题目描述里提到叶结点的,都需要显式判断叶结点,在叶结点处结束递归。

113. 路径总和 II

找出所有满足上一题的条件的路径,这次并不能单纯划分子问题,而是要用遍历视角看待;
路径遍历的顺序就是 DFS 序:当 DFS 进入一个结点时,路径中就增加一个结点;当 DFS 从一个结点退出时,路径中就减少一个结点。
根据回溯操作的特性,使用栈 记录遍历时的当前路径。当进入一个结点时,做 push 操作;当退出一个结点时,做 pop 操作,进行回溯。

// https://leetcode-cn.com/problems/path-sum-ii/
const pathSum = (root, targetSum) => {
    const recur = (root, sum, path, res) => {
    	// terminator
        if (!root) return;
        // current level
        path.push(root.val);
        if (!root.left && !root.right) {
            if (sum == root.val) {
                res.push(path.slice()); // 存入res的是path的拷贝,否则后面pop会导致res中存的结果也变了
            }
        }
        // drill down
        const target = sum - root.val;
        recur(root.left, target, path, res);
        recur(root.right, target, path, res);
        // backtrace
        path.pop();
    }
    let [res, path] = [[], []];
    recur(root, targetSum, path, res);
    return res;
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值