leetcode刷题记录(12)

1.2的幂  *简单

题目:给定一个整数,编写一个函数来判断它是否是 2 的幂次方。

思路:这题算简单的,递归、迭代、位运算都行

迭代:

/**
 * @param {number} n
 * @return {boolean}
 */
var isPowerOfTwo = function (n) {
  if (n <= 0) return false;
  if (n == 1) return true;
  while (n >= 2) {
    n = n / 2;
    if (!Number.isInteger(n)) return false;
  }
 return true;
};

递归:

/**
 * @param {number} n
 * @return {boolean}
 */
var aa = (n) => {
  if (!Number.isInteger(n)) return false;
  if (n == 1) return true;
  return aa(n / 2);
};
var isPowerOfTwo = function (n) {
  if (n <= 0) return false;
  if (n == 1) return true;
  return aa(n);
};

位运算:

/**
 * @param {number} n
 * @return {boolean}
 */

var isPowerOfTwo = function (n) {
    if(n<1){
        return false;
    }

   return !(n & (n-1))
};

2.回文链表  *简单

题目:请判断一个链表是否为回文链表。

思路:这和之前的回文字符串比较像

最容易想到的,是把出现的值放进数组里,然后判断是否回文

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @return {boolean}
 */
var isPalindrome = function(head) {
 const v = [];
  while (head) {
    v.push(head.val);
    head = head.next;
  }
  while (v.length > 1) {
    if (v.pop() !== v.shift()) return false;
  }
  return true;
};

下面优化一下。

我们可以从中间一分为二,分成两个链表,然后把前一半翻转(把后一半翻转是一样的),这样得到两个链表,比较两个链表的每个值即可。如果是奇数长度的链表,那么后半部分要再往后取一次

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @return {boolean}
 */
var isPalindrome = function(head) {
    if(head === null || head.next === null) return true;
    let mid = head;
    let pre = null;
    let reversed = null;
    while(head !== null && head.next !== null) {
        pre = mid
        mid = mid.next
        head = head.next.next
        pre.next = reversed
        reversed = pre
    }
    if(head) mid = mid.next
    while(mid) {
        if(reversed.val !== mid.val) return false
        reversed = reversed.next
        mid = mid.next
    }
    return true

};

3.二叉搜索树的最近公共父节点 *简单

题目:

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

首先要明确以定义,。什么是二叉搜索树。二叉搜索树的意思是,每个节点,它的左节点比当前节点的值小,右节点比当前节点的值更大。企鹅同一级,从左到右节点的值也是一次增大。

所以思路就很简单,如果一个节点,它的值比p大比q小,那么它肯定就是结果。如果比两个都要大,那么递归它的左树,比两个都要小,递归它的右树。

/**
 * @param {TreeNode} root
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {TreeNode}
 */
const lowestCommonAncestor = (root, p, q) => {
  while (
    (root.val > p.val && root.val > q.val) ||
    (root.val < p.val && root.val < q.val)
  ) {
    if (root.val > p.val && root.val > q.val) {
      root = root.left;
    } else {
      root = root.right;
    }
  }
  return root;
};

4.Z形变换  *中等

题目:

将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下:

L   C   I   R
E T O E S I I G
E   D   H   N
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"LCIRETOESIIGEDHN"。

请你实现这个将字符串进行指定行数变换的函数:

思路:首先,用一个二维数组记录每一行的结果,用一个变量记录行数,用另一变量记录方向。行数是在0-rumrows-1之间取值,每记录一个字符串,就+1或者-1,方向则是到边界之后需要掉头。所以代码就出来了

/**
 * @param {string} s
 * @param {number} numRows
 * @return {string}
 */
var convert = function(s, numRows) {
      if (numRows < 2) return s;
  const res = new Array(numRows).fill(null).map(() => []);
  let i = 0;
  let flag = true;
  let v = "";
  while (s.length) {
    v = s[0];
    s = s.substring(1);
    res[i].push(v);
    if (i == numRows - 1) {
      flag = false;
    } else if (i == 0) {
      flag = true;
    }
    i += flag?1:-1;
  }
  return res.reduce((r, v) => {
    r += v.join("");
    return r;
  }, "");
};

接下来换种思路,字符串其实是以每2numrows-2为一个单位,所以用2numrows-2划分字符串,然后每个单位的字符串都是以行数最大的值为镜像的(除去第一行)

/**
 * @param {string} s
 * @param {number} numRows
 * @return {string}
 */
var convert = function(s, numRows) {
  if (numRows < 2) return s;
  const res = new Array(numRows).fill("");
  const temp = [];
  while (s.length) {
    temp.push(s.substring(0, 2 * numRows - 2));
    s = s.substring(2 * numRows - 2);
  }
  temp.forEach((v, i) => {
    for (let i = 0; i < numRows; i++) {
      if (i == numRows - 1) {
        res[i] += v[i] || "";
      } else {
        res[i] += (v[i] || "") + (v[2 * numRows - 2 - i] || "");
      }
    }
  });
  return res.join("");
};

下面是第一种方法的改良版,不用数组,用字符串拼接即可 

/**
 * @param {string} s
 * @param {number} numRows
 * @return {string}
 */
var convert = function(s, numRows) {
   if (numRows < 2) return s;
  const res = new Array(numRows).fill("");
  let i = 0;
  let flag = true;
  while (s.length) {
    res[i] += s[0];
    s = s.substring(1);
    if (i == numRows - 1) {
      flag = false;
    } else if (i == 0) {
      flag = true;
    }
    i += flag ? 1 : -1;
  }
  return res.join("");
};

5.字符串转换整数 (atoi) *中等

题目:

请你来实现一个 atoi 函数,使其能将字符串转换成整数。

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。接下来的转化规则如下:

如果第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字字符组合起来,形成一个有符号整数。
假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成一个整数。
该字符串在有效的整数部分之后也可能会存在多余的字符,那么这些字符可以被忽略,它们对函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换,即无法进行有效转换。

在任何情况下,若函数不能进行有效的转换时,请返回 0 。

提示:

本题中的空白字符只包括空格字符 ' ' 。
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231,  231 − 1]。如果数值超过这个范围,请返回  INT_MAX (231 − 1) 或 INT_MIN (−231) 。

思路:首先,我们知道,parseInt()就是结果,不过显然是要我们用原生写

然后,我们先依次遍历字符串,如果第一次遇到空格,跳过,遇到有效的字符,如果是+、-号,记录,如果是字符,则拼接。此时,如果遇到了有效数字字符之后,后面只能遇到0-9的字符,遇到其他字符要直接返回,结束计算

/**
 * @param {string} str
 * @return {number}
 */
var myAtoi = function(str) {
  const a = "-+0123456789 ";
  let res = "";
  let flag = 0;
  while (str.length) {
    let s = str[0];
    str = str.substring(1);
    if (a.includes(s)) {
              if (flag && s === " ") break;
      if (!flag && s === " ") continue;
      if (!flag) {
        if (s === "-") {
          flag = -1;
        } else if (s === "+") {
          flag = 1;
        } else {
          res += s;
          flag = 1;
        }
      } else {
        if (s == "-" || s == "+") break;
        res += s;
      }
    } else {
      break;
    }
  }
  if (res && !(res == "-" || res == "+")) {
    const a = flag * Number(res);
    if (a >= 2147483647) return 2147483647;
    if (a <= -2147483648) return -2147483648;
    return a;
  }
  return 0;
};

6.盛最多水的容器

题目:

给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。

思路:水的体积,就是Math.min(ai,aj)*(j-i),最容易想到的,遍历所有结果,取最大值

/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function(height) {
  let res = 0;
  for (let i = 1, l = height.length; i < l; i++) {
    for (let j = 0; j < i; j++) {
      res = Math.max(res, Math.min(height[i], height[j]) * (i - j));
    }
  }
  return res;
};

下面来介绍一下双指针法。

注意一下容器的体积和它们端点高度的关系。现在我们取两边的点,左指针向右移动,右指针向左移动,怎么移动才能使体积有可能变大?或者说,怎么移动才能使体积肯定变小?

注意,我们的体积,是由高度较小的那个端点决定的。所以,如果对于边界情况,左指针的高度小于右指针的高度,那么,如果我们移动右指针,在(j-i)确定减小的情况下,左指针的高度之前是更小的,那么移动了右指针,两个指针上的高度的最小值,不可能比高度还小(因为左指针没动,右指针的高度更大也没意义,比左指针还小,那么I最小值更小),,所以这时我们只能移动左指针,才有可能让体积更大。

如果左右指针一样呢?我们可以同时移动。为什么呢?因为如果内部如果存在更优解,那么肯定不会以此时的指针作为边界(因为x轴距离变小了,然后想要得到更优解,指针上的高度的最小值肯定要更大,所以不可能某一个指针不动还能得到最优解)。

代码:

/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function(height) {
  let a1 =0;
  let a2 = height.length - 1;
  let res = 0;
  while (a1 < a2) {
    res = Math.max(res, Math.min(height[a1], height[a2])*(a2-a1));
    if (height[a1] < height[a2]) {
      a1++;
    } else if(height[a1] > height[a2]) {
      a2--;
    }else{
        a1++;
        a2--;
    }
  }
  return res;
};

这样我们最多遍历一轮就能得到最优解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值