leetcode刷题记录(14)-中等

1.逆波兰表达式求值

题目:

根据 逆波兰表示法,求表达式的值。

有效的运算符包括 +-*/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

 

说明:

  • 整数除法只保留整数部分。
  • 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况

思路:基本思路,用一个栈保存数字,遇到数字推入栈,遇到字符,则取出栈顶的两个字符计算,并累加。用eval方便,不过内存消耗更大一点

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

/**
 * @param {string[]} tokens
 * @return {number}
 */
var evalRPN = function(tokens) {
  const pression = ["+", "-", "*", "/"];
  const stack = [];
  for (const s of tokens) {
    if (pression.includes(s)) {
      const v2 = stack.pop();
      const v1 = stack.pop();
      switch (s) {
        case "+":
          stack.push(+v1 + +v2);
          break;
        case "-":
          stack.push(v1 - v2);
          break;
        case "*":
          stack.push(v1 * v2);
          break;
        case "/":
          stack.push(~~(v1 / v2));
          break;
      }
    } else {
      stack.push(s);
    }
  }
  return stack[0];
};

优化:可以从后往前递归处理。从后往前,遇到字符,说明前面要对两个数字的值进行操作,遇到数字则转为数字返回,减法和除法注意顺序问题

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

/**
 * @param {string[]} tokens
 * @return {number}
 */
var evalRPN = function(tokens) {
    let fun = () => {
        let char = tokens.pop();
        let num;
        switch (char) {
        case "+":
            return fun() + fun();
        case "-":
            num = fun();
            return fun() - num;
        case "*":
            return fun() * fun();
        case "/":
            num = fun();
            return ~~(fun() / num);
        default:
            return +char;
        }    
    }
    return fun();
};

2.翻转字符串里面的单词

题目:给定一个字符串,逐个翻转字符串中的每个单词。

思路:先用自带的方法,split分割然后反转

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

/**
 * @param {string} s
 * @return {string}
 */
var reverseWords = function(s) {
  return s
    .trim()
    .split(" ")
    .filter((i) => i).reverse().join(" ");
};

也可以自己反转,就是把字符串推入一个数组,新成员放在开头

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

/**
 * @param {string} s
 * @return {string}
 */
var reverseWords = function(s) {
  let left = 0;
  let right = s.length - 1;
  while (s[left] === " ") left++;
  while (s[right] === " ") right--;
  const res = [];
let word = "";
  while (left <= right) {
    if (s[left] === " " && word) {
      res.unshift(word);
      word = "";
    } else if (s[left] !== " ") {
      word += s[left];
    }
    left++;
  }
  res.unshift(word)
  return res.join(" ");
};

3.乘积最大子数组

题目:给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

思路:乘积最大,因为0的特殊性,用0将数组分割成一个个小数组,对每一个小数组进行处理。如果数组的累积大于0,那么这个数就是最大乘积,如果小于0,从两边开始计算遇到第一个负数的乘积,左侧值更大,则除以左侧的乘积,反之右侧(负数越大,绝对值越小),最终对所有的子数组求最大值即可

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


/**
 * @param {number[]} nums
 * @return {number}
 */
const maxP = (nums) => {
  if (nums.length < 2) return nums[0];
  let max = nums.reduce((a, b) => a * b);
  if (max > 0) return max;
  let index = 0;
  let sumleft = 1;
  let sumRight = 1;
  while (sumleft > 0) {
    sumleft *= nums[index];
    index++;
  }
  index = nums.length - 1;
  while (sumRight > 0) {
    sumRight *= nums[index];
    index--;
  }
  if (sumleft > sumRight) return max / sumleft;
  return max / sumRight;
};
var maxProduct = function (nums) {
  if (nums.length < 2) return nums[0];
  const res = [];
  let temp = [];
  for (const n of nums) {
    if (n === 0 && temp.length) {
      res.push(temp);
      temp = [];
    } else if (n !== 0) {
      temp.push(n);
    }
  }
  if (temp.length) res.push(temp);
  return Math.max(...res.map((item) => maxP(item)), 0);
};

优化:转换思路,动态规划。乘积最大有两种情况,要么自身是正数,乘积就是前面数的最大乘积乘以自己。要么自身的负数,乘积就是前面数的最小值乘以自己,所以要保存最大的正数和最小的负数。当然为了区分0的情况,我们可以再将当前的积和自身比较,最终最后的结果就是最大值

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

/**
 * @param {number[]} nums
 * @return {number}
 */

var maxProduct = function (nums) {
  let res = nums[0]
  let prevMin = nums[0]
  let prevMax = nums[0]
  let temp1 = 0, temp2 = 0
  for (let i = 1; i < nums.length; i++) {
    temp1 = prevMin * nums[i]
    temp2 = prevMax * nums[i]
    prevMin = Math.min(temp1, temp2, nums[i])
    prevMax = Math.max(temp1, temp2, nums[i])
    res = Math.max(prevMax, res)
  }
  return res

};

4.寻找旋转数组的最小值

题目:

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

请找出其中最小的元素。

你可以假设数组中不存在重复元素。

思路:先用自带的方法:

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

/**
 * @param {number[]} nums
 * @return {number}
 */
var findMin = function(nums) {
return Math.min(...nums)
};

然后可以用二分法。先比较开头和末尾的值的关系。如果开头的值小于末尾的值,相当于没有旋转,最小值就是开头的数。

取中点,比较中点和开头的值的关系。如果left的值小于middle的值,那么最小值在右半段(这说明left到middle一直是升序),反之在左半段

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

/**
 * @param {number[]} nums
 * @return {number}
 */
var findMin = function(nums) {
  let left = 0,
    right = nums.length - 1;
if (nums[left] < nums[right]) return nums[0];
  while (left !== right) {
    let middle = ~~((left + right) / 2);
    if (middle === left) {
      if (nums[left] > nums[right]) {
        left = right;
      } else {
        right = left;
      }
    } else if (nums[middle] > nums[left]) {
      left = middle;
    } else {
      right = middle;
    }
  }
  return nums[left];
};

5.寻找峰值

题目:

峰值元素是指其值大于左右相邻值的元素。

给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。

数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞。

思路:先用迭代的方式,因为开头和末尾可视为负无穷,那么第一个比后面的数大的数就是峰值

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

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

也可以用二分法。如果middle的值比下一个值更大,那么肯定有个峰值在左侧,反之肯定有个峰值在右侧

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

/**
 * @param {number[]} nums
 * @return {number}
 */
var findPeakElement = function(nums) {
  let left = 0;
  let right = nums.length - 1;

  while (left < right) {
    let mid = ~~((left + right) / 2);

    if (nums[mid] > nums[mid + 1]) {
      right = mid;
    } else {
      left = mid + 1;
    }
  }

  return left;
};

 

你可能感兴趣的:(逆波兰表达式求值,翻转字符串里面的单词,乘积最大子数组,寻找旋转数组的最小值,寻找峰值)