leetcode刷题记录(12)

    技术2025-09-19  10

    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; };

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

    Processed: 0.010, SQL: 10