一诺爸爸的LeetCode之旅-第178周周赛(4/4)

LeetCode新手一枚,还请多多指教~

第一题:有多少小于当前数字的数字

解题思路

先对数组进行排序,比最小的数字还小的个数自然为0,比次小的数字还小的个数为最小的数字的个数,以此类推。

解法1

先对数组进行排序,然后遍历数组,在考虑有相同数字的情况下,依次统计数组中比当前数字更小的数字的个数。

var smallerNumbersThanCurrent = function (nums) {
    let arr = nums.slice().sort();
    let obj = {};

    arr.forEach((num, index) => {
        obj[num] = (obj[num] === undefined) ? index : obj[num];
    });

    return nums.map((num) => obj[num]);
};

let input = [8, 1, 2, 2, 3];
console.log('NASA: input', input);
console.log('NASA: output', smallerNumbersThanCurrent(input));

解法2

根据题目的说明,数组中的数字是0到100的整数,所以有个取巧的办法:直接利用数组来对其进行排序。代码如下:

var smallerNumbersThanCurrent = function (nums) {
    let arr = [];
    let obj = {};

    nums.forEach((num) => {
        arr[num] = (arr[num] || 0) + 1
    });

    let count = 0;
    arr.forEach((item, index) => {
        if (item != undefined) {
            obj[index] = count;
            count += item;
        }
    });

    return nums.map((num) => obj[num]);
};

let input = [8, 1, 2, 2, 3];
console.log('NASA: input', input);
console.log('NASA: output', smallerNumbersThanCurrent(input));

第二题:通过投票对团队排名

解题思路

根据题目的说明,最终各个team是一定会有一个先后顺序排名的,即便是投票上完全打成平手,也会根据字母顺序进行排序。所以,这一题我的解题思路是:
1、使用一个正方形的二维数组来存储每个字母在每个位置上的得票数;
2、考虑到最终有可能会完全平票,所以在每个数组的末尾添加当前字母的CharCode值的反码。所谓反码是指“如果是A就添加Z - A = 25,如果是Z就添加Z - Z = 0,方便最后的排序操作。
3、为二维数组中的子数组中的每个数值做前置补0处理,方便最后的排序操作。
4、对二维数组进行排序,排序的数值就是每个子数组的元素join之后的字符串。
说了这么多,我估计我也没讲明白,还是看代码吧:

var rankTeams = function(votes) {
    const getCharCode = (input) => input.charCodeAt(0);
    const fill0 = (input = 0) => ('0'.repeat(4) + input).slice(-4);

    let obj = {};
    let dualArr = [];
    let codeZ = getCharCode('Z');
    let len = votes[0].length;


    for (const vote of votes) {
        for (let index = 0; index < vote.length; index++) {
            let letter = vote[index];
            let arr = (obj[letter] = obj[letter] || []);
            arr[index] = (arr[index] || 0) + 1;
        }
    }

    for (const [letter, arr] of Object.entries(obj)) {
        for (let index = 0; index < len; index++) {
            arr[index] = fill0(arr[index]);
        }
        arr.push(fill0(codeZ - getCharCode(letter)));
        dualArr.push(arr);
    }

    dualArr.sort((a, b) => b.join('') - a.join(''));

    return dualArr.map((arr) => String.fromCharCode(codeZ - arr.pop())).join('');
};

let votes = ["ABC", "ACB", "ABC", "ACB", "ACB"];
console.log(`NASA: (${rankTeams(votes)})`);

第三题:二叉树中的列表

解题思路

这道题的考点有两个,一个是如何通过root数组构造二叉树,另一个是如何在二叉树上匹配head数组。

先说第一个考点:如何通过root数组构造二叉树。首先要注意,这里的树并不是完全二叉树(笔者一开始看错了,准备构造完全二叉树来着,结果发现不太对)。构造这棵二叉树要在root数组上使用两个index,分别是指向当前正在构造的树节点的index(treeIndex)以及数组的index(rootIndex)。两个index都从0开始,treeIndex所到之处都将root数组中的对应数字元素(必须是非null值,如果是null值就继续往后找)直接替换成相应的new TreeNode对象,然后rootIndex加1来构造刚刚创建的TreeNode对象的左子节点,同理再+1来构造刚刚创建的TreeNode对象的右节点,这一步完成后treeIndex继续寻找root数组的下一个非null值(注意:此时的非null值一定是一个TreeNode对象或者treeIndex直接超出root数组的边界,原因你自己想一下吧),如此反复直至treeIndex或者rootIndex超出root数组的边界。

再说第二个考点:如何在二叉树上匹配head数组。这个其实就很简单了,直接用递归的思想就可以了。从二叉树的根节点开始处理,head数组能在二叉树上匹配成功的规则就是:如果head数组的第一个值跟当前树节点的值相同,那么匹配成功的条件就是“head数组刨除第一个值后的数组能在当前树节点的左子节点或者右子节点匹配成功”,否则“head数组自身能在当前树节点的左子节点或者右子节点匹配成功”。(PS:我这里没用它提供的ListNode函数,因为没必要)
好了,思路已经说完了,配合代码一起看会更清楚哦~

var isSubPath = function (head, root) {
    function TreeNode(val) {
        this.val = val;
        this.left = this.right = null;
    }

    const getTree = (root) => {
        let treeIndex = 0;
        let rootIndex = 0;
        let rootLen = root.length;

        root = root.slice(0);

        let treeNode = root[rootIndex] = new TreeNode(root[rootIndex]);

        while ((rootIndex < rootLen) && (treeIndex < rootLen)) {
            if ((++rootIndex < rootLen) && (root[rootIndex] !== null)) {
                treeNode.left = root[rootIndex] = new TreeNode(root[rootIndex]);
            }
            if ((++rootIndex < rootLen) && (root[rootIndex] !== null)) {
                treeNode.right = root[rootIndex] = new TreeNode(root[rootIndex]);
            }

            while ((++treeIndex < rootLen) && (root[treeIndex] == null)) continue;
            treeNode = root[treeIndex];
        }

        return root[0];
    };

    const gotyou = (treeNode, head) => {
        if (head.length === 0) return true;
        if (treeNode === null) return false;

        let next = head.slice(1);
        return (treeNode.val === head[0]) ? (gotyou(treeNode.left, next) || gotyou(treeNode.right, next)) : (gotyou(treeNode.left, head)) || (gotyou(treeNode.right, head));
    };

    let rootNode = getTree(root);
    return gotyou(rootNode, head);
};

let head = [1, 4, 2, 6];
let root = [1, 4, 4, null, 2, 2, null, 1, null, 6, 8, null, null, null, null, 1, 3];
console.log(`NASA: (${isSubPath(head, root)})`);

第四题:使网格图至少有一条有效路径的最小代价

解题思路

首先,这道题千万不要试图去想有什么方法是能够最快(最小代价)地抵达右下角终点站的(比如说从起点开始一直往右下方向走),因为由于单元格内方向的随机性,所以任何奇奇怪怪的最小代价路径都是有可能的。那么这道题的解题思路是怎样的呢?首先,我们需要一只虫子!对,就是一只小虫子:)

  1. 从起点(0, 0)开始,有只第0代小虫子开始沿着箭头方向在单元格内爬行,所爬过的单元格内都留下它的虫卵(这是第0代虫卵),小虫子一直爬到爬不动然后就消失了(也许是因为即将越过边界,也许是因为路径堵住了,也许是因为直接爬到了终点)。
  2. 这时虫卵开始孵化,向它的上下左右单元格内散布第1代小虫子,前提是单元格内既没有虫卵也没有虫子,然后第1代小虫子继续沿袭第0代小虫子的做法。
  3. 就这样一直下去,直至有第N代小虫子抵达右下角的终点站为止,此时的N就是最小代价。

为什么要这么做呢?因为从第0代小虫子开始,它能够爬过的单元格就是代价为0即可抵达的单元格,如果它此时没有到达终点的话,那么所有它经过的单元格都能1的代价调转方向抵达它上下左右的任意一个单元格,然后再从这些所有代价为1的单元格去寻找抵达终点的路径……以此类推,最终就一定能抵达终点,同时求得此时的最小代价。
最后是代码实现部分,请君笑纳:

var minCost = function (grid) {
    let ti = grid.length - 1;
    let tj = grid[0].length - 1;

    grid = grid.map((row, i) => row.map((value, j) => ({ i, j, cost: -1, value })));

    let bugArr = [grid[0][0]];
    let curCost = 0;
    let targetCost = -1;
    let footArr = []

    const getBlankCell = (i, j) => (i >= 0) && (j >= 0) && (i <= ti) && (j <= tj) && (grid[i][j].cost === -1) && grid[i][j];

    while (true) {
        for (const cell of bugArr) {
            let cur = cell;
            let ni = cell.i;
            let nj = cell.j;

            if (cur.cost !== -1) continue;

            while (true) {
                cur.cost = curCost;
                footArr.push(cur);

                if (ni === ti && nj === tj) {
                    targetCost = curCost;
                    break;
                }

                switch (cur.value) {
                    case 1:
                        nj += 1;
                        break;
                    case 2:
                        nj -= 1;
                        break;
                    case 3:
                        ni += 1;
                        break;
                    case 4:
                        ni -= 1;
                        break;
                }

                let next = getBlankCell(ni, nj);

                if (next) {
                    cur = next;
                } else {
                    break;
                }
            }
        }

        if (~targetCost) break;

        bugArr = [];
        for (const cell of footArr) {
            let blank;

            if (blank = getBlankCell(cell.i, cell.j + 1)) {
                bugArr.push(blank);
            }

            if (blank = getBlankCell(cell.i, cell.j - 1)) {
                bugArr.push(blank);
            }

            if (blank = getBlankCell(cell.i + 1, cell.j)) {
                bugArr.push(blank);
            }

            if (blank = getBlankCell(cell.i - 1, cell.j)) {
                bugArr.push(blank);
            }
        }

        curCost += 1;
        footArr = [];
    }

    return targetCost;
};

let grid = [[1, 1, 1, 1], [2, 2, 2, 2], [1, 1, 1, 1], [2, 2, 2, 2]];
console.log('NASA: ', minCost(grid));

你可能感兴趣的:(一诺爸爸的LeetCode之旅-第178周周赛(4/4))