【leetcode】剑指offer题目实践

295 阅读5分钟

03. 数组中重复的数字

题目介绍

找出数组中重复的数字。

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

示例

输入:
[2, 3, 1, 0, 2, 2, 5, 3]
输出:[ [ 2, 3 ], [ 3, 2 ] ]

限制:2 <= n <= 100000

答案

时间空间复杂度:O(n), O(n)

/**
 * @param {number[]} nums
 * @return {number}
 */
var findRepeatNumber = function(nums) {
  const numSet = {}
  nums.forEach(num => {
    if(numSet[num]) numSet[num] += 1;
    else numSet[num] = 1;
  })
  const res = []
  Object.entries(numSet).forEach(arr => {
    const [key, value] = arr;
    if(value >= 2) res.push([Number(key), value])
  })
  return res;
};

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。

限制:
0 <= n <= 1000
0 <= m <= 1000

答案

时间空间复杂度:O(nlogn), O(1)

/**
 * @param {number[][]} matrix
 * @param {number} target
 * @return {boolean}
 */
var findNumberIn2DArray = function (matrix, target) {
  return matrix.some(array => {
    return search(array, target) !== -1
  })
};
const search = function (nums, target) {
  let left = 0,
    right = nums.length - 1,
    mid;
  while (left <= right) {
    mid = Math.floor((left + right) / 2);
    if (nums[mid] < target) {
      left = mid + 1;
    } else if (nums[mid] > target) {
      right = mid - 1;
    } else {
      return mid;
    }
  }
  return -1;
}

05. 替换空格

题目介绍

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

示例

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

0 <= s 的长度 <= 10000

答案

时间空间复杂度:O(n), O(1)

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

09. 用两个栈实现队列

题目介绍

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

示例

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

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

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

答案

时间空间复杂度:插入和删除O(1), O(n)

栈类的实现

const addStack = new Stack();
const deleteStack = new Stack();
function CQueue(commands = [], values = []) {
  const res = [];
  commands.forEach((item, index) => {
    if (['CQueue', 'appendTail'].includes(item)) {
      res.push(null)
      values[index].forEach(value => {
        addStack.push(value);
      });
    }
    if (['deleteHead'].includes(item)) {
      if (deleteStack.size() === 0) {
        while (addStack.size() > 0) {
          deleteStack.push(addStack.pop());
        }
      } 
      res.push(deleteStack.pop() || -1)
    }
  });
  return res;
}

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。

示例

输入:n = 2
输出:1

输入:n = 5
输出:5

答案

时间空间复杂度:插入和删除O(n), O(1)

/**
 * @param {number} n
 * @return {number}
 */
var fib = function (n) {
  if ([0, 1].includes(n)) return n;
  let prev = 0;
  let prevLast = 1;
  let temp = null;
  for (let i = 2; i <= n; i++) {
    temp = prevLast;
    prevLast = (prevLast + prev) % 1000000007;
    prev = temp;
  }
  return prevLast;
};

06. 从尾到头打印链表

题目介绍

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

示例

输入:head = [1,3,2]
输出:[2,3,1]

答案

时间空间复杂度:O(1), O(n)

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @return {number[]}
 */
var reversePrint = function (head) {
  const result = [];
  while(head!==null) {
    result.unshift(head.val);
    head = head.next
  }
  return result
};

13. 机器人的运动范围

题目介绍

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

示例

输入:m = 2, n = 3, k = 1
输出:3

输入:m = 3, n = 1, k = 0
输出:1

1 <= n,m <= 100
0 <= k <= 20

答案

时间空间复杂度:O(mn), O(mn)

/**
 * @param {number} m
 * @param {number} n
 * @param {number} k
 * @return {number}
 */
var movingCount = function (m, n, k) {
  const arr = []
  for (let i = 0; i < m; i++) {
    arr[i] = []
    for (let j = 0; j < n; j++) {
      arr[i][j] = '-';
    }
  }
  return dfs(arr, m, n, k, 0, 0)
}

var dfs = function (arr, m, n, k, i, j) {
  if (i < 0 || i > m - 1 || j < 0 || j > n - 1 || arr[i][j] !== '-') return 0;
  const addValue = (String(i) + String(j)).split('').reduce((total, val) => {
    return Number(total) + Number(val);
  })
  if (addValue > k) return 0;
  const x = [1, 0, -1, 0];
  const y = [0, 1, 0, -1];
  let res = 1;
  arr[i][j] = '*'
  for (let idx = 0; idx < 4; idx++) {
    res = res + dfs(arr, m, n, k, i + x[idx], j + y[idx])
  }
  return res;
}

18. 删除链表的节点

题目介绍

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

返回删除后的链表的头节点。

示例

输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

答案

时间空间复杂度:O(n), O(1)
核心:head是一条链,pre和node都是上面的指针

/**
 * 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 pre = head;
  let node = pre.next;
  if(pre.val === val) {
    return head.next;
  }
  while(node) {
    if(pre.val === val) {
      pre.next = node.next;
      break;
    }
    pre = node;
    node = node.next;
  }
  return head;
};

47. 礼物的最大价值

题目介绍

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

示例

输入: 
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 12
解释: 路径 13521 可以拿到最多价值的礼物

0 < grid.length <= 200
0 < grid[0].length <= 200

答案

时间空间复杂度:O(n), O(mn)

/**
 * @param {number[][]} grid
 * @return {number}
 */
var maxValue = function (grid) {
  const m = grid.length;
  const n = grid[0].length;
  const dp = [];
  for (let i = 0; i < m; i++) {
    dp[i] = []
    for (let j = 0; j < n; j++) {
      dp[i][j] = 0;
    }
  }
  dp[0][0] = grid[0][0];

  // 初始化第一行和第一列值
  for (let i = 1; i < m; i++) {
    dp[i][0] = grid[i][0] + dp[i - 1][0];
  }
  for (let j = 1; j < n; j++) {
    dp[0][j] = grid[0][j] + dp[0][j - 1];
  }

  // 从grid[1][1]开始循环
  for (let i = 1; i < m; i++) {
    for (let j = 1; j < n; j++) {
      dp[i][j] = grid[i][j] + Math.max(dp[i - 1][j], dp[i][j - 1]);
    }
  }
  return dp[m - 1][n - 1];
};

16. 数值的整数次方

题目介绍

实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。

示例

输入: 2.00000, 10
输出: 1024.00000

输入: 2.10000, 3
输出: 9.26100

输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25

-100.0 < x < 100.0
n 是 32 位有符号整数,其数值范围是 [−231, 2311] 

答案

时间空间复杂度:O(n), O(mn)

思路

递归,成倍进行乘积。

/**
 * @param {number} x
 * @param {number} n
 * @return {number}
 */
var myPow = function (x, n) {
  if (n === 0) return 1;

  let res = x;
  for (let i = 2; i <= Math.abs(n); i = i * 2) {
    res *= res;
    if (i * 2 > Math.abs(n) && i !== Math.abs(n)) {
      res *= myPow(x, Math.abs(n) - i);
    }
  }

  if (n < 0) {
    res = 1 / res
  }
  return res
};