剑指offer打卡1

看了一下,LeetCode剑指OFFER的题目大概有70道左右,难度大多为简单到中等。但是如果放开思路,深究多种解法,难度也并不低。总之适合我等菜鸟,先记录在此,并立下🏳️🏳️🏳️ 🚩🚩🚩:

这些算法题,我要全部吃掉!!!

当然,这里只记录一些常规的,或者比较不错的算法,其中也有自己的思考,但是文字注释并不多,很适合下次光临回味的那种。每一篇blog大概3~4道题的记录量,那么,话不多说。

287. 寻找重复数

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1n 之间(包括 1n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1:

输入: [1,3,4,2,2]
输出: 2

示例 2:

输入: [3,1,3,4,2]
输出: 3

说明:

  1. 不能更改原数组(假设数组是只读的)。
  2. 只能使用额外的 O(1)的空间。
  3. 时间复杂度小于 O(n2)
  4. 数组中只有一个重复的数字,但它可能不止重复出现一次。

如果没有说明中的限制,这里有很多种做法。比如开辟一个哈希表,或者开辟一个数组,其索引对应的就是其应该正确放置的值。这些方法就保证了时间优先的原则。

var findRepeatNumber = function(nums) {
    // set
    const s = new Set();
    for (let num of nums) {
        if (s.has(num)) {
            return num;
        }
        s.add(num);
    }
};

此外,也可以利用O(nlogn)的时间复杂度将数组排序,那么重复的数字也就很明显了。

如果对空间排序有要求,就像题目所说的那样,就可以通过抽屉原理来解决,抽屉原理是啥?

抽屉原理的一般含义为:“如果每个抽屉代表一个集合,每一个苹果就可以代表一个元素,假如有n+1个元素放到n个集合中去,其中必定有一个集合里至少有两个元素。

对此,我们可以通过不断将元素放置到其正确的抽屉处,如此循环。这样就能保证在一段时间内发现一个抽屉具有两个元素,从而找到重复的数字。具体思路如下:

从头扫描数组,遇到下标为i的元素如果不等于i的话(假设等于j),就将其与下标为j的元素互换,每一次互换就确保了某个数字正确归位,因此,如果数组中存在重复的元素,则必然会引起冲突,即当前元素值与待交换的值相等,这样就找到了重复的元素。

var findRepeatNumber = function(nums) {
    // 抽屉原理
    const n = nums.length;
    for (let i = 0; i < n; i++) {
        let val = nums[i]
        while (val !== i) {
            if (val === nums[val]) {
                return val
            }
            // python 这里有坑
            [nums[val], nums[i]] = [nums[i], nums[val]];
        }
    }
};
面试题07. 重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

例如,给出

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]

返回如下的二叉树:

    3
   / \
  9  20
    /  \
   15   7

限制:

0 <= 节点个数 <= 5000

方法一:递归

整体来看逻辑比较复杂,但是用递归进行分而治之能很简单地解决。算法的基本思路在于将重建二叉树的复杂度平摊到各个子树的递归构建上,要构建子树,就需要确定子树相应的前序遍历和中序遍历。对此就可以利用前序遍历和中序遍历的特点来求解。首先preorder的第一个节点为当前根节点,而中序遍历的遍历策略为左=>根=>右,因此利用inorder求上面根节点的索引就不难找出相应的左右子树范围,递归建树即可,停止条件为当前仅当列表中只有一个节点,直接返回。

var buildTree = function(preorder, inorder) {
    if (preorder.length < 1)   return null;
    const root = new TreeNode(preorder[0]);
    if (preorder.length === 1) return root;
    const ind = inorder.findIndex(node => node === root.val);
    const leftInorder = inorder.slice(0, ind);
    const rightInorder = inorder.slice(ind+1);
    root.left = buildTree(preorder.slice(1, 1+ind), leftInorder);
    root.right = buildTree(preorder.slice(1+ind), rightInorder);
    return root;
};
面试题05. 替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

示例 1:

输入:s = "We are happy."
输出:"We%20are%20happy."

限制:

0 <= s 的长度 <= 10000

方法一:快捷方法

最简单的方法就是通过循环遍历原字符串,一旦遇到空格则在后续str += '20%'即可,问题不大。当然也可以做顺风车,直接利用字符串中成熟的API就可以得到结果:

str.split(' ').join('20%');

方法二:双指针从后向前填充法

对于源字符串而言,只要存在一个空格字符,就将其变为20%,也就是每一个空格就会使得结果字符串的长度多出2个。出于对原字符串就地修改的想法,首先遍历原字符串找到空格个数,然后在原字符串的末尾扩充相应的空白待填空间,设置两个指针p1, p2分别指向原字符串末尾和扩充后的字符串末尾,然后两个指针向前移动,如果p1指针指向了原字符中的空格字符,则在p2指针处依次填充0、2、%字符,否则就简单将p1指针的字符赋值给p2即可,直到p1,p2的指向相同,则退出。

var replaceSpace = function(s) {
    const n = s.length;
    if (n === 0) return '';
    // 统计空格个数
    let res = [],
        cnt = 0;

    for (let i of s) {
        if (i === ' ') {
            cnt += 1;
        }
        res.push(i);
    }
    // JS 字符 immutable 性质
    // 转换为数组操作
    res = [...res, ...new Array(2*cnt).fill(' ')];
    let [p1, p2] = [n-1, res.length-1];
    while (p1 >= 0 && p2 > p1) {
        if (res[p1] === ' ') {
            res[p2--] = '0';
            res[p2--] = '2';
            res[p2--] = '%';
            p1--;
            continue;
        } 
        res[p2--] = res[p1--];
    }
    return res.join('');
};

虽然此方法的初衷在于减小空间复杂度。但是因为JavaScriptimmutable性质,似乎只有通过开辟结果数组来辅助实现,或许我还没有想到新的方法。。。

面试题09. 用两个栈实现队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTaildeleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

示例 1:

输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]

示例 2:

输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]

提示:

  • 1 <= values <= 10000
  • 最多会对 appendTail、deleteHead 进行 10000 次调用

将其中一个栈stack1作为数据压入存放的栈,将第二个栈stack2作为数据弹出的栈。基本思路为:当执行所谓队列的appendTail操作的时候,stack1执行相应逻辑。如果执行deleteHead,分两种情况讨论,如果stack2为空的话,则考虑将stack1中的所有数据popstack2中,然后弹出stack2顶部即可,这样就实现了队列的弹出顺序,当然stack1里也没有数据的时候,就如题意返回-1即可。

var CQueue = function() {
	this.stack1 = [];
    this.stack2 = [];
};

/** 
 * @param {number} value
 * @return {void}
 */
CQueue.prototype.appendTail = function(value) {
	this.stack1.push(value);
};

/**
 * @return {number}
 */
CQueue.prototype.deleteHead = function() {
	if (this.stack2.length === 0) {
        if (this.stack1.length > 0) {
            while (this.stack1.length > 0) {
                const val = this.stack1.pop();
                this.stack2.push(val);
            }         
        } else {
            return -1;
        }
    }
    const top = this.stack2.pop();
    return top;
};

当然这里利用两个栈实现了队列,换一个思想,我们也可以通过两个队列来实现一个栈…具体怎么操作,闭上眼睛,感受一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Key Board

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值