Leetcode: 剑指offer

剑指 Offer 03. 数组中重复的数字

题目描述

  • 找出数组中重复的数字。
    在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
  • 示例 1:
    输入:
    [2, 3, 1, 0, 2, 5, 3]
    输出:2 或 3
    限制:
    2 <= n <= 100000
  • 来源:力扣(LeetCode)
    链接:数组中重复的数字
    著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

这题简单,考的不是数组去重的简单版么,一下子就A掉了;事实上这道题针对不同要求有不同的解法;
目前我想到的两种:1. 先排序再遍历对比;2. 哈希 3. 也算哈希吧,不过用的是辅助数组映射,js中的数组的栈操作比对操作快一点;

Solutions

  1. 排序+遍历
/**
 1. @param {number[]} nums
 2. @return {number}
 */
var findRepeatNumber = function(nums) {
    // 原地排序+指针 88ms 44.1mb
    nums.sort((a,b) => a-b);
    for(let i = 0, len = nums.length-1; i < len; i++) {
       if(nums[i] === nums[i+1]) {
          return nums[i]
       }
    }
};
  1. 哈希
/**
 1. @param {number[]} nums
 2. @return {number}
 */
var findRepeatNumber = function(nums) {
    // 哈希:652ms 60.2mb
     let map = new Map();
     let res = [];
     nums.forEach(item => {
         map.has(item) ? map.set(item, map.get(item) + 1) : map.set(item, 1);
     })
     map.forEach((value, key) => {
         console.log(value);
         if(value > 1) {
             res.push(key);
         }
     })
     return res[0];
};
  1. 辅助数组映射

这里能用辅助数组映射主要是因为这里题目有说明是N个数,都在0-N-1范围内

/**
 * @param {number[]} nums
 * @return {number}
 */
var findRepeatNumber = function(nums) {
    // 辅助数组映射
    let len = nums.length;
    let help = new Array(len).fill(0);
    nums.forEach((item, index) => {
        help[item]++;
    })
    for(let i = 0; i < len; i++) {
        if(help[i] > 1) return i;
    }
};

Results

执行结果

经过对比,使用数组映射确实比用Map做哈希快,上图最慢的就是用Map哈希的。。。最快的是排序后遍历的

剑指offer 04: 二维数组中的查找

题目描述

  • 在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
  • 示例:
    现有矩阵 matrix 如下:
    [
    [1, 4, 7, 11, 15],
    [2, 5, 8, 12, 19],
    [3, 6, 9, 16, 22],
    [10, 13, 14, 17, 24],
    [18, 21, 23, 26, 30]
    ]
    给定 target = 5,返回 true。
    给定 target = 20,返回 false。

来源:力扣(LeetCode)
链接:二维数组中的查找
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

  1. 暴力法:双重循环遍历二维数组,这样的话题设给的行列已经排序的条件就没用了,也不能当作一道medium的题目,复杂度O(M*N)
  2. 从右上角或者左下角开始遍历,双指针,时间复杂度 O(M+N)

Solutions

/**
 * @param {number[][]} matrix
 * @param {number} target
 * @return {boolean}
 */
 // 双重循环
var findNumberIn2DArray = function(matrix, target) {
    if(!matrix.length) return false;
    let lenOne = matrix.length, lenTwo = matrix[0].length;
    for(let i = 0; i < lenOne; i++) {
        for(let j = 0; j < lenTwo; j++) {
            if(matrix[i][j] === target) return true;
        }
    }
    return false;
};
/**
 * @param {number[][]} matrix
 * @param {number} target
 * @return {boolean}
 */
 // 双指针
var findNumberIn2DArray = function(matrix, target) {
    if(!matrix || !matrix.length) return false;
    let m = matrix.length, n = matrix[0].length;
    let row = 0, col = n - 1;
    while(row < m && col >= 0) {
        if(matrix[row][col] > target) {
            col--;
        } else if(matrix[row][col] < target) {
            row++;
        } else {
            return true;
        }
    }
    return false;
};

Results

二维数组中的查找执行结果

这里的执行时间差距看不出来。。。一阶和二阶的差距无法体现;执行出错是边界没有处理,测试用例有空数组

剑指offer 05: 替换空格

题目描述

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
示例 1:
输入:s = “We are happy.”
输出:“We%20are%20happy.”

来源:力扣(LeetCode)
链接:替换空格
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

正则表达式

/**
 * @param {string} s
 * @return {string}
 */
var replaceSpace = function(s) {
    return s.replace(/\s/g, "%20");
};

执行时间中规中矩

06: 从尾到头打印链表

题目描述:

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]

思路: 先从头到尾获取节点值,再反转数组

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @return {number[]}
 */
var reversePrint = function(head) {
    let res = [];
    while(head) {
        res.push(head.val);
        head = head.next
    }
    return res.reverse();
};

07:重建二叉树

题目描述

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
二叉树

来源:力扣(LeetCode)
链接:重建二叉树
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

  • 前序遍历:根节点–>左节点–>右节点
  • 中序遍历:左节点–>根节点–>右节点
  • 后序遍历:左节点–>右节点–>根节点
  • 因此前序遍历的第一个元素就是根节点,然后可以根据中序遍历中根节点的位置,根节点左边的就是二叉树的全部左节点,根节点的右边元素就是二叉树的右节点,进行递归,最后重建二叉树

Solutions

递归

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {number[]} preorder
 * @param {number[]} inorder
 * @return {TreeNode}
 */
var buildTree = function(preorder, inorder) {
    if(!preorder.length || !inorder.length) return null; // 这里不能直接return, 题设要求,没有的叶子节点用null代替
    let rootVal = preorder[0];
    let rootIndex = inorder.indexOf(rootVal)
    let root = new TreeNode(rootVal);
    root.left = buildTree(preorder.slice(1,rootIndex+1), inorder.slice(0,rootIndex));
    root.right = buildTree(preorder.slice(rootIndex+1), inorder.slice(rootIndex+1));
    return root;
};

所有的递归版本都可以改为迭代版本,待补充。。。

10-I: 斐波那契亚数列

题目描述

  • 写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:
    F(0) = 0, F(1) = 1
    F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
    斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
  • 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
    示例 1:
    输入:n = 2
    输出:1
    示例 2:
    输入:n = 5
    输出:5

来源:力扣(LeetCode)
链接:斐波那契亚数列
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

看到斐波那契亚数列首先想到的肯定是递归,但是这题贼鸡贼,递归就会超时,但是所有递归都可以变迭代,多用几个变量的事情
记忆化,使用一个数组对每一项斐波那契亚数进行储存,额外空间复杂度O(N); 也可以不使用数组,用三个变量进行变量替换

Solutions

  1. 递归
/**
 * @param {number} n
 * @return {number}
 */
var fib = function(n) {
    // 递归---超时
    if(n < 0) return
    if(n <= 1) return n;
    return (fib(n-1) + fib(n-2)) % 1000000007
};

这里有个奇怪的点就是n为81的时候,得到的结果是107920472,而不是107920470,一开始怀疑超出边界了,但是在代码编辑器中结果是正确的;最后结果要取模1e9+7, dp的时候每一步都取模就可以解决

  1. 迭代
/**
 * @param {number} n
 * @return {number}
 */
var fib = function(n) {
    // 时间复杂度O(N), 空间复杂度O(N)---前n项斐波那契亚数都可以获取到
    if(n < 0) return
    if(n <= 1) return n
    let res = [0,1]
    for(let i = 2; i <= n; i++) {
        res[i] = (res[i-1] + res[i-2]) % 1000000007;
    }
    return res[n];
};
  1. 不用数组
/**
 * @param {number} n
 * @return {number}
 */
var fib = function(n) {
    // 节省空间,不用数组,用三个变量,相差无几
    if(n < 0) return
    if(n <= 1) return n
    let num1 = 0, num2 = 1, res = 0;
    for(let i = 2; i <= n; i++) {
        res = (num1 + num2) % 1000000007;
        num1 = num2;
        num2 = res;
    }
    return res;
};

Results

执行结果

四次提交错误顺利拉低了通过率

10- II. 青蛙跳台阶问题

题目描述

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
来源:力扣(LeetCode)
链接:青蛙跳台阶问题
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这题就是斐波那契亚数列的变种,实际上还是斐波那契亚数列的问题,不过首项变成了1。
思路:假设从跳到第n阶台阶有F(n)种 跳法,由题意可知,跳到第n阶可以从第n-1阶跳1级到第n阶,也可以从第n-2阶的地方跳一级到第n阶;而跳到第n-1阶有F(n-2)种方法,跳到第n-1阶有F(n-1)种方法;因此有F(n) = F(n-1)+F(n-2);这就变成了斐波那契亚数列的问题了,注意下边界就可以A掉了。

/**
 * @param {number} n
 * @return {number}
 */
var numWays = function(n) {
    if(n < 0) return
    if(n <= 1) return 1
    let num1 = 1, num2 = 1, res = 0;
    for(let i = 2; i <= n; i++) {
        res = (num1 + num2) % 1000000007;
        num1 = num2;
        num2 = res;
    }
    return res;
};

旋转数组的最小数字

题目描述:点击链接跳转原题链接

/**
 * @param {number[]} numbers
 * @return {number}
 */
var minArray = function(numbers) {
    if(!numbers || !numbers.length) return
    for( let i = 0; i < numbers.length - 1; i++) {
        if(numbers[i] > numbers[i+1]) {
            return numbers[i+1];
        }
    }
    return numbers[0]
};

15: 二进制数中1的个数

原题链接

api调用

/**
 * @param {number} n - a positive integer
 * @return {number}
 */
var hammingWeight = function(n) {
    let arr = n.toString(2).split('');
    let res = 0;
    arr.forEach(item => {
        if(~~item === 1) {
            res++;
        }
    })
    // console.log(typeof num);
    return res;
};

17:打印从1到最大的n位数

原题链接

/**
 * @param {number} n
 * @return {number[]}
 */
var printNumbers = function(n) {
    if(n < 0) return
    let res = [];
    let temp = '9';
    for(let i = 1; i < n; i++) {
        temp += '9'
    }
    let end = ~~temp;
    for(let j = 1; j <= end; j++) {
        res.push(j);
    }
    return res;
};

18:删除链表的节点

点击跳转链接

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
var deleteNode = function(head, val) {
    let preNode = head;
    let nextNode = head.next;
    if(preNode.val === val) {
        return head.next;
    }
    while(nextNode) {
        if(nextNode.val === val) {
            preNode.next = nextNode.next
        }        
        preNode = nextNode
        nextNode = nextNode.next
    }
    return head
};

思路:遍历找到对应的节点,将前一个节点的指针指向下一个节点,当前节点就从链表中删除了。这题用递归也可以,但是感觉没有必要。。。

21.调整数组顺序使奇数位于偶数前面

原题链接

思路:1. 一次从头到尾遍历+辅助数组,时间复杂度O(N),空间复杂度 O(N), 这种方法的耗时和内存都比较大;
2.双指针(头尾指针遍历原地交换):
个人感觉不最求性能的话借助js的函数来解决最优雅,虽然有点流氓。。。

遍历+辅助数组:

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var exchange = function(nums) {
    // 一次遍历,借助辅助数组,O(N),空间复杂度O(N)
     let arr = [];
     nums.map(item => {
         item % 2 === 0 ? arr.push(item) : arr.unshift(item)
     })
     return arr;
};

双指针:

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var exchange = function(nums) {
    // 一次遍历,借助辅助数组,O(N),空间复杂度O(N)
    // let arr = [];
    // nums.map(item => {
    //     item % 2 === 0 ? arr.push(item) : arr.unshift(item)
    // })
    // return arr;

    // 双指针
    let left = 0, right = nums.length - 1;
    while(left < right) {
        while(left < right && nums[left] % 2 !== 0) {
            left++
        }
        while(right > left && nums[right] % 2 === 0) {
            right--;
        }
        if(left < right) {
            let temp = nums[left];
            nums[left] = nums[right];
            nums[right] = temp;
        }
    }
    return nums;
};

js hack做法:利用js的排序函数Array.sort(),可以自定义排序方法,传送门
一行代码搞定

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var exchange = function(nums) {
    // 流氓写法
    return nums.sort((a,b) => b % 2 - a % 2);
};
  • 后续待补充,这波是清库存。。。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值