力扣1-99
1.两数之和(哈希表)
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function (nums, target) {
// new Map() 遍历数组用声明式
// map中键是数字,值是index
// 如果map中有target-x,用get拿到target-x的index,和当前i作为[i,index]return出去
// 不管怎样本次循环用set设一下当前nums[i],i
// 循环结束return []
let map = new Map();
for (let i = 0; i < nums.length; i++) {
const x = nums[i];
if (map.has(target - x)) {
return [i, map.get(target - x)];
}
map.set(x, i);
}
return [];
};
// O(n)
2.两数相加(链表)
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
const addTwoNumbers = function (l1, l2) {
let dummy = new ListNode(0);
let p = dummy;
let carry = 0; //该位和,在下一次的进位
while (l1 || l2) {
let sum = 0; //该位的和为0,每次循环会刷新
if (l1) {
sum += l1.val;
l1 = l1.next;
}
if (l2) {
sum += l2.val;
l2 = l2.next;
}
sum += carry;
p.next = new ListNode(sum % 10);
carry = Math.floor(sum / 10);
p = p.next;
}
// 最后全加完了,发现carry还有,如6+6=12
// carry必为1
if (carry === 1) {
p.next = new ListNode(carry);
}
return dummy.next;
};
3.无重复字符的最长子串(滑动窗口)
// 集合l在-1位外循 环追逐遍历,第二次删除,内循环往后扩展,l走动,挑战最大值
var lengthOfLongestSubstring = function (s) {
let l = -1;
let r = 0;
let max = 0;
let set = new Set();
while (l < r && r < s.length) {
// 到第二次循环(l!==-1),重复已经被破坏
if (l !== -1) {
set.delete(s[l]);
}
//l在任何情况都要自增
l++;
// set中有记录过的,破坏
while (r < s.length && !set.has(s[r])) {
set.add(s[r++]);
}
max = Math.max(max, r - l);
}
return max;
};
4.*寻找两个正序数组的中位数(二分)
https://leetcode.cn/problems/median-of-two-sorted-arrays/description/
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
有两个数组A[7,8,9]和B[1,2,3,4,5,6],两个数组的总长度 len=9 中位数的位置 k=5 比较两个数组第 k/2 项,如果 A[k/2] > B[k/2] 即 B[1],B[2]...B[k/2] 为这两个数组的最小前 k/2 项,则可以直接排除掉,排除掉之后长度和k同步缩小,然后进行下一轮的比较,直到 k=1 的时候,只需要找到两个正序数组第一个元素最小值即可,其中有两个地方需要特殊处理:
1、一个数组为空的情况,直接取另一个数组的第 k 项即可
2、一个数组的长度小于 k/2 的时候,取数组的最后一项
var findMedianSortedArrays = function (nums1, nums2) {
const len1 = nums1.length;
const len2 = nums2.length;
const mid = (len1 + len2 + 1) >> 1;
const left = getK(nums1, nums2, 0, 0, mid);
// 判断总长度奇偶
if ((len1 + len2) % 2) {
return left;
} else {
const right = getK(nums1, nums2, 0, 0, mid + 1);
return (left + right) / 2;
}
};
// 取第k小的数
function getK(nums1, nums2, start1, start2, k) {
const len1 = nums1.length;
const len2 = nums2.length;
/* 特例 */
// nums1 数组的元素排除完
if (len1 === start1) return nums2[start2 + k - 1];
if (len2 === start2) return nums1[start1 + k - 1];
// 排除到只剩两个元素取最小 即剩余元素的最小值
if (k === 1) return Math.min(nums1[start1], nums2[start2]);
/* 通常情况 */
// 取k的一半 同 时注意可能会超出数组长度 最多取数组最后一个元素
const i = start1 + Math.min(len1, k >> 1) - 1;
const j = start2 + Math.min(len2, k >> 1) - 1;
// j 前面的所有元素被排除了 同时缩减k的值
if (nums1[i] > nums2[j]) {
return getK(nums1, nums2, start1, j + 1, k - (j - start2 + 1));
} else {
return getK(nums1, nums2, i + 1, start2, k - (i - start1 + 1));
}
}
let n = nums1.length + nums2.length;
let nums = nums1.concat(nums2).sort((a, b) => a - b);
let result =
n % 2 == 0 ? (nums[n / 2] + nums[n / 2 - 1]) / 2 : nums[Math.floor(n / 2)];
return result;
5.最长回文子串(中心开花)
// 特殊s.length<=1 return s
// target = ''
// 设置求回文函数(left,right)
// 循环当left和right不越界且left值等于right值时
// left right向外走动
// 循环结束,挑战最大值,用s.substring(left+1,right).length和target.length
// 比较,更新target为新字符串
// 启动函数,通过一个遍历循环,启动函数(i,i)和(i,i+1)
const longestPalindrome = function (s) {
if (s.length <= 1) {
return s;
}
let target = "";
//从哪个字符开始向两边找?
for (let i = 0; i < s.length; i++) {
fun(i, i);
fun(i, i + 1);
}
//怎么求回文?
function fun(left, right) {
// 这里第一次就会执行,比如ab答案是0
while (left >= 0 && right <= s.length - 1 && s[left] == s[right]) {
--left;
right++;
}
//substring:从第一个参数开始,到第二个参数的前一个(包含第一个,不包含第二个)
// 例: 'asdfg'.substring(1,3)=>'sd'
if (right - left > target.length) {
target = s.substring(left + 1, right);
}
}
return target;
};
6.Z 字形变换
将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下:
P A H N
A P L S I I G
Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
var convert = function (s, numRows) {
const m = numRows;
const res = new Array(m).fill().map(() => new Array());
let current = 0;
let column = 0;
while (current < s.length) {
let i = 0;
for (; i < m && s[current]; i++) {
res[i][column] = s[current++];
}
i = m - 1;
while (s[current] && i > 1) {
res[--i][++column] = s[current++];
}
column++;
//一遍循环读了PAYP
// 第二遍读了ALIS
}
const sres = [];
// 一行行读取模拟的结果即可
for (let i = 0; i < res.length; i++) {
for (let j = 0; j < res[i].length; j++) {
if (res[i][j]) {
sres.push(res[i][j]);
}
}
}
return sres.join("");
};
7.整数反转
var reverse = function (x) {
let rev = 0;
while (x !== 0) {
const digit = x % 10;
x = ~~(x / 10);
rev = rev * 10 + digit;
if (rev < Math.pow(-2, 31) || rev > Math.pow(2, 31) - 1) {
return 0;
}
}
return rev;
};
8. 字符串转整数
/**
* @param {string} s
* @return {number}
*/
var myAtoi = function (s) {
let res = 0,
// 正负号,默认正号
negativeSymbol = 1;
// 把首尾的空格都去掉
s = s.trim();
for (let i = 0; i < s.length; i++) {
// 负数
if (i == 0 && s[i] == "-") {
negativeSymbol = -1;
continue;
// 正数
} else if (i == 0 && s[i] == "+") continue;
// 因为空格会被转成0,所以要排除空格的情况,也就是说在数字范围内就加上
if (s[i] >= 0 && s[i] <= 9 && s[i] != " ") {
res = res * 10 + (s[i] - 0);
// 为什么在这里就判断呢,因为这里如果就溢出的话,就直接跳出,不需要再后面无意义的计算了
if (res * negativeSymbol <= -2147483648) return -2147483648;
else if (res * negativeSymbol >= 2147483647) return 2147483647;
} else break; // 如123fffe
}
return res * negativeSymbol;
};
9. 回文数
/**
* @param {number} x
* @return {boolean}
*/
// 小于0不是回文数,
// 满足最后一位是0,且是0才是回文数
// 设回文数为0,循环当前数大于回文数。回文数*10+当前数最后位(%10)
// 当前数去掉最后位(Math.floor(x/10))
// 返回x与回文数 相等或x与Floor回文数/10相等(x有奇数位)
var isPalindrome = function (x) {
// // 特殊情况:
if (x < 0 || (x % 10 === 0 && x !== 0)) return false;
return (x + "").split("").reverse().join("") === x + "";
// // 如上所述,当 x < 0 时,x 不是回文数。
// // 同样地,如果数字的最后一位是 0,为了使该数字为回文,
// // 则其第一位数字也应该是 0
// // 只有 0 满足这一属性
// if (x < 0 || (x % 10 === 0 && x !== 0)) {
// return false;
// }
let revertedNumber = 0;
while (x > revertedNumber) {
revertedNumber = revertedNumber * 10 + (x % 10);
x = Math.floor(x / 10);
}
// 当数字长度为奇数时,我们可以通过 revertedNumber/10 去除处于中位的数字。
// 例如,当输入为 12321 时,在 while 循环的末 尾我们可以得到 x = 12,revertedNumber = 123,
// 由于处于中位的数字不影响回文(它总是与自己相等),所以我们可以简单地将其去除。
return x === revertedNumber || x === Math.floor(revertedNumber / 10);
};
10. *正则匹配
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符 '*' 匹配零个或多个前面的那一个元素 所谓匹配,是要涵盖 整个 字符串 s 的,而不是部分字符串
const isMatch = (s, p) => {
if (s == null || p == null) return false;
const sLen = s.length,
pLen = p.length;
const dp = new Array(sLen + 1);
for (let i = 0; i < dp.length; i++) {
dp[i] = new Array(pLen + 1).fill(false); // 将项默认为false
}
// base case,
dp[0][0] = true;
// aaaa和a*必匹配,但是和b*不匹配
for (let j = 1; j < pLen + 1; j++) {
if (p[j - 1] == "*") dp[0][j] = dp[0][j - 2];
}
// 迭代
for (let i = 1; i < sLen + 1; i++) {
for (let j = 1; j < pLen + 1; j++) {
// a 和 .
// a 和 a
if (s[i - 1] == p[j - 1] || p[j - 1] == ".") {
// 如果上一个匹配则匹配,否则就不匹配
dp[i][j] = dp[i - 1][j - 1];
} else if (p[j - 1] == "*") {
// a 和 a*
// a 和 .*
if (s[i - 1] == p[j - 2] || p[j - 2] == ".") {
//把*看作0试试 (ab->abb*
//把a*跟a换掉试试
// (abbbb-> ab*)
dp[i][j] = dp[i][j - 2] || dp[i - 1][j - 2] || dp[i - 1][j];
} else {
// 把*看作0试试
dp[i][j] = dp[i][j - 2];
}
}
}
}
return dp[sLen][pLen]; // 长sLen的s串 是否匹配 长pLen的p串
};
11.盛最多水的容器
https://leetcode.cn/problems/container-with-most-water/description/
双指针,向中遍历
若向内 移动长板 ,水槽的短板min(h[i],h[j])min(h[i], h[j])min(h[i],h[j])
不变或变小,因此下个水槽的面积 一定变小
向内移动长板,面积不变或变大
/**
* @param {number[]} height
* @return {number}
*/
Area = function (height) {
//两根主子间的最大面积
let max = 0;
//使用双指针求面积
for (let i = 0, j = height.length - 1; i < j; ) {
//求两根柱子之间高度低的哪一个
let minHeight = height[i] < height[j] ? height[i++] : height[j--];
//两个柱子之间的宽度
let leng = j - i + 1;
//求面积
let area = leng * minHeight;
//取最大值
max = Math.max(max, area);
}
return max;
};
12.整数转罗马数字
https://leetcode.cn/problems/integer-to-roman/description/
/**
* @param {number} num
* @return {string}
*/
var intToRoman = function (num) {
// 从大到小排列,准备好400 900的特殊情况
const valueSymbols = [
[1000, "M"],
[900, "CM"],
[500, "D"],
[400, "CD"],
[100, "C"],
[90, "XC"],
[50, "L"],
[40, "XL"],
[10, "X"],
[9, "IX"],
[5, "V"],
[4, "IV"],
[1, "I"],
];
const res = [];
for (let [val, roma] of valueSymbols) {
while (num >= val) {
num -= val;
res.push(roma);
}
}
return res.join("");
};
13.罗马数字转整数
https://leetcode.cn/problems/roman-to-integer/description/
/**
* @param {string} s
* @return {number}
*/
var romanToInt = function (s) {
const symbolValues = new Map();
// 从小到大排
symbolValues.set("I", 1);
symbolValues.set("V", 5);
symbolValues.set("X", 10);
symbolValues.set("L", 50);
symbolValues.set("C", 100);
symbolValues.set("D", 500);
symbolValues.set("M", 1000);
let ans = 0;
const n = s.length;
for (let i = 0; i < n; ++i) {
const value = symbolValues.get(s[i]);
// 如果遇到IV,则-1+5
if (i < n - 1 && value < symbolValues.get(s[i + 1])) {
ans -= value;
} else {
ans += value;
}
}
return ans;
};
14.最长公共前缀
/**
* @param {string[]} strs
* @return {string}
*/
// 特殊strs.length==0,return ""
// 假设ans为strs[0]
// 从第二个开始遍历strs
// 设j为0
// 当j<ans.length&&j<strs[i].length时
// 如果ans[j]!=strs[i][j] break
// j++
// 跳出循环。ans = ans.substr(0,j)
var longestCommonPrefix = function (strs) {
if (strs.length == 0) return "";
let ans = strs[0];
for (let i = 1; i < strs.length; i++) {
let j = 0;
while (j < ans.length) {
if (ans[j] != strs[i][j]) {
//第i个单词的第j个字母
break;
}
j++;
}
ans = ans.substr(0, j); //ans截取前j个字母
}
return ans;
};
15.三数之和(三指针)
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,
同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
/**
* @param {number[]} nums
* @return {number[][]}
*/
// 特殊nums.len<3
// 将数组升序排列
// 循环遍历
// 1.如果当前值大于0,break
// 2.如果当前位不为0且与上一位值相等,continue去重
// 3.l=i+1三指针第二个,r=len-1三指针第三
// 4.while l<r
// 计算sum
// 41如果sum=0,三值加入res,内部while l<r l值<l+1 l++去重;r同理
// 内循环结束,注意让lr走动
// 42如果sum<0 l++
// 43同理
// 时间复杂度:O(n^2)
// n 为数组长度
var threeSum = function (nums) {
let ans = [];
let len = nums.length;
nums.sort((a, b) => a - b);
for (let i = 0; i < len - 2; i++) {
if (nums[i] > 0) break;
if (i > 0 && nums[i] === nums[i - 1]) continue;
// 最大情况
if (nums[i] + nums[len - 2] + nums[len - 1] < 0) continue;
let l = i + 1;
let r = len - 1;
while (l < r) {
sum = nums[i] + nums[l] + nums[r];
if (sum === 0) {
ans.push([nums[i], nums[l], nums[r]]);
while (l < r && nums[l] === nums[l + 1]) l++;
while (l < r && nums[r] === nums[r - 1]) r--;
l++;
r--;
}
if (sum < 0) l++;
if (sum > 0) r--;
}
}
return ans;
};
16.最接近的三数之和
给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。
返回这三个数的和。
// 只存在一个解
var threeSumClosest = function (nums, target) {
let N = nums.length;
let res = Infinity;
// 也是升序
nums.sort((a, b) => a - b);
for (let i = 0; i < N; i++) {
let left = i + 1;
let right = N - 1;
while (left < right) {
let sum = nums[i] + nums[left] + nums[right];
if (sum === target) return sum;
//每次收缩得到一个相对最接近target的解
// 类似记录max,反正最后也是输出max
if (Math.abs(sum - target) < Math.abs(res - target)) {
res = sum;
}
if (sum < target) left++;
if (sum > target) right--;
}
}
return res;
};
17.电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
/**
* @param {string} digits
* @return {string[]}
*/
var letterCombinations = function (digits) {
const k = digits.length;
const map = [
"",
"",
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz",
];
if (!k) return [];
if (k === 1) return map[digits].split("");
const res = [],
path = [];
backtracking(0);
return res;
function backtracking(a) {
if (path.length === k) {
res.push(path.join(""));
return;
}
for (const v of map[digits[a]]) {
path.push(v);
backtracking(a + 1);
path.pop();
}
}
};
18.四数之和
**注意四数之和有 target 而不是像三数之和为 0
/**
* @param {number[]} nums
* @param {number} target
* @return {number[][]}
*/
// 先升序
// 三层循环,第一层确定第一位~length-4,第二层确定第二位~length-3
// 还是三种剪枝
// 第三层确定left right窗口
var fourSum = function (nums, target) {
const quadruplets = [];
if (nums.length < 4) {
return quadruplets;
}
nums.sort((x, y) => x - y);
const length = nums.length;
for (let i = 0; i < length - 3; i++) {
// --- 外层和内层都能剪三次
if (i > 0 && nums[i] === nums[i - 1]) {
continue;
}
if (nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
break;
}
if (
nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] <
target
) {
continue;
}
// ===
// ** * *
// ij固定,left,right动
for (let j = i + 1; j < length - 2; j++) {
// ---
if (j > i + 1 && nums[j] === nums[j - 1]) {
continue;
}
if (nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) {
break;
}
if (nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target) {
continue;
}
//---
let left = j + 1,
right = length - 1;
while (left < right) {
const sum = nums[i] + nums[j] + nums[left] + nums[right];
if (sum === target) {
quadruplets.push([nums[i], nums[j], nums[left], nums[right]]);
while (left < right && nums[left] === nums[left + 1]) {
left++;
}
left++;
while (left < right && nums[right] === nums[right - 1]) {
right--;
}
right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
}
}
return quadruplets;
};
19.删除链表的倒数第 N 个结点
var removeNthFromEnd = function (head, n) {
// ret为虚拟头节点
// 有虚拟头节点,可以不判断是不是直接删掉第一个节点
let hair = new ListNode(0, head),
slow = (fast = hair);
while (n--) fast = fast.next;
while (fast.next !== null) {
fast = fast.next;
slow = slow.next;
}
// 拿到的是被删除的节点的前一个! 因此八倒三,要拿5号
slow.next = slow.next.next;
return hair.next;
};
20.有效的 括号(栈)
/**
* @param {string} s
* @return {boolean}
*/
var isValid = function (str) {
// 对象键名左值右,准备栈,let of遍历,如果是键(左)(map[i]存在),加入栈,如果不是键是值且
// 栈顶不是对应键map[stk.pop()]!==i,return false
// 循环结束,栈空返回true否则false
let map = {
"{": "}",
"(": ")",
"[": "]",
};
let stk = [];
for (let i of str) {
// 如果是左括号
if (map[i]) {
stk.push(i);
//如果是右括号,但对不上栈顶
} else if (map[stk.pop()] !== i) {
return false;
}
}
return stk.length === 0;
};
21.合并有序链表(递归)
将两个升序链表合并为一个新的 升序 链表并返回。
// 递归函数(l1,l2),如果!l1 returnl2 如果!l2 return l1
// 如果l1值小于l2,l1.next = 递归函数(l1.next,l2) return l1
// else情况对称
var mergeTwoLists = function (l1, l2) {
if (l1 === null) {
return l2;
}
if (l2 === null) {
return l1;
}
if (l1.val > l2.val) {
l2.next = mergeTwoLists(l1, l2.next);
//这种情况,最小的是l2开头,因此返回l2
return l2;
} else {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
}
//通过(l1,l2.next)将问题转为更小的问题
//return返回符合条件的(偏大的)
//lx.next赋值能将return的接到链表后面,且lx肯定是前面同一位置较小的一方(因为在if里
};
迭代法(双指针)
const merge = (head1, head2) => {
let dummy = new ListNode(0);
let cur = dummy;
// 新链表,新的值小就先接谁
while (head1 && head2) {
if (head1.val < head2.val) {
cur.next = head1;
head1 = head1.next;
} else {
cur.next = head2;
head2 = head2.next;
}
cur = cur.next;
}
// 如果后面还有剩余的就把剩余的接上
cur.next = head1 == null ? head2 : head1;
return dummy.next;
};
22.括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
var generateParenthesis = function (n) {
const res = [];
const track = [];
function trackCracket(left, right) {
if (left < 0 || right < 0) return;
if (left > right) return;
if (left == 0 && right == 0) {
res.push(track.join(""));
return;
}
track.push("(");
trackCracket(left - 1, right);
track.pop();
track.push(")");
trackCracket(left, right - 1);
track.pop();
}
trackCracket(n, n);
return res;
};
23.合并 K 个升序链表(归并)
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
/**
* @param {ListNode[]} lists
* @return {ListNode}
*/
var mergeKLists = function (lists) {
// 当是空数组的情况下
if (!lists.length) {
return null;
}
// 合并两个排序链表
const merge = (head1, head2) => {
let dummy = new ListNode(0);
let cur = dummy;
// 新链表,新的值小就先接谁
while (head1 && head2) {
if (head1.val < head2.val) {
cur.next = head1;
head1 = head1.next;
} else {
cur.next = head2;
head2 = head2.next;
}
cur = cur.next;
}
// 如果后面还有剩余的就把剩余的接上
cur.next = head1 == null ? head2 : head1;
return dummy.next;
};
const mergeLists = (lists, start, end) => {
// base case 只有一个链接的情况
if (start + 1 === end) {
return lists[start];
}
// 输入的k个排序链表,可以分成两部分,前k/2个链表和后k/2个链表
// 如果将这前k/2个链表和后k/2个链表分别合并成两个排序的链表,再将两个排序的链表合并,那么所有链表都合并了
let mid = start + ((end - start) >> 1);
let head1 = mergeLists(lists, start, mid);
let head2 = mergeLists(lists, mid, end);
return merge(head1, head2);
};
// 前闭后开
return mergeLists(lists, 0, lists.length);
};
24.两两交换链表中的节点
1,2,3,4->2,1,4,3
var swapPairs = function (head) {
if (head === null || head.next === null) {
return head;
}
const newHead = head.next;
head.next = swapPairs(newHead.next);
newHead.next = head;
return newHead;
};
25.K 个一组翻转链表
/**
* @param {ListNode} head
* @param {number} k
* @return {ListNode}
*/
// 先实现反转函数(head,tail),return [tail,head]
// prev初始值为4,curr初始值为1
// [1,2,3] 4, 1连上4,prev变为1,curr变为[2,3]
// 主函数(head,k)
// 在head节点的前面加一个hair节点0
// [0,1,2,3,4]
// pre初始值是0,当head存在时,
// 设tail为pre,遍历k次,tail一直next,找到这一group的尾部
// 如果越界了,return hair.next,即哑节点后面的一个,直接结束
// 跳出循环,先用next记录tail.next,
// [head,tail] = myReverse(head,tail),反转这一group
// 把这一group接到pre的后面,把新tail(let出来的可为新)的后面接上
// next,更新pre为新tail,head为tail.next
// 最终返回hair.next,即哑节点后面的一个
const myReverse = (head, tail) => {
let prev = null;
let curr = head;
// 由于本题tail的下一个不是null,所以终止条件是 prev 已经到头了
while (prev !== tail) {
const next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return [tail, head];
};
const reverseKGroup = function (head, k) {
const hair = new ListNode(0);
hair.next = head;
let originPre = hair;
while (head) {
let oneGroupTail = pre;
// 查看剩余部分长度是否大于等于 k,如果小于,直接return hair.next
for (let i = 0; i < k; ++i) {
oneGroupTail = oneGroupTail.next;
if (!oneGroupTail) {
// 已经是最后一个group,把最终链表返回出去
return hair.next;
}
}
//
const orginNext = oneGroupTail.next;
[head, oneGroupTail] = myReverse(head, oneGroupTail);
// 把子链表重新接回原链表
originPre.next = head;
oneGroupTail.next = originNext;
originPre = oneGroupTail;
head = oneGroupTail.next;
}
return hair.next;
};
26.有序数组去重
更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。 返回 k 。 并返回 nums 中唯一元素的个数
/**
* @param {number[]} nums
* @return {number}
*/
// 双指针都从1开始,fast负责扩展,一旦出现不连续,就将当前的fast赋给slow位(去重),slow才进
// 只需要一遍遍历
// 最后要求返回个数,就是slow的值,当然slice()
var removeDuplicates = function (nums) {
const n = nums.length;
if (n === 0) {
return 0;
}
let fast = 1,
slow = 1;
while (fast < n) {
if (nums[fast] !== nums[fast - 1]) {
nums[slow] = nums[fast];
++slow;
}
++fast;
}
return slow;
};