指针
6.三数之和
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j、i != k 且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
leetcode 15. 三数之和
题解
该题需要先从小到大排序,后需要建立三个指针,从头到尾完成遍历,第一层遍历决定第一个元素(first
),第二层遍历决定第二个元素(second = first + 1
)跟第三个元素(third
,初始化为最大的元素)。其中对于确认了first
后,就要对second
和third
进行首尾遍历。原则为:三者相加,大于target
则左移third
,小于则右移second
。另外需要注意,不能输出相同的元素,又因为该数组一开始就经过排序,则可以跳过相等的两个元素。
function threeSum(nums: Array<number>): Array<Array<number>> {
const res: Array<Array<number>> = [];
// 先进行排列
nums.sort((a, b) => a - b);
for(let first = 0; first < nums.length; first ++) {
// 第一层遍历
if (first > 0 && nums[first] === nums[first - 1]) {
// 相邻的两个元素相等,则跳过
continue;
}
// 第三个元素初始化为排列后的最后一个,即数组最大值
let third = nums.length - 1;
// tagget + nums[first] = 0
const target = -1 * nums[first];
// 第二个元素初始化为第一个元素的后一个元素
for(let second = first + 1; second < nums.length - 1; second ++) {
// 对数组进行《首尾收缩遍历》
if (second > first + 1 && nums[second] === nums[second - 1]) {
// 相邻的两个元素相等,则跳过
continue;
}
while(second < third && nums[second] + nums[third] > target) {
// 第二第三元素相加大于target,则向头部收缩
third--;
}
if (second === third) {
// 相等了,则直接跳去循环
// 因为元素不能相等,则该数组经过排序,若相等,后面的《首尾收缩遍历》都会相等
break
} else if (nums[second] + nums[third] === target) {
res.push([nums[first], nums[second], nums[third]]);
}
}
}
return res;
};
// const arr = threeSum([-1, 0, 1, 2, -1, -4]);
// // { arr: [ [ -1, -1, 2 ], [ -1, 0, 1 ] ] }
// console.log({ arr });
7.最接近的两数和 - 字节-上海
给出一个数组和一个目标值,找出两个和最接近目标值的子项
题解
数组从大到小排序,双指针首尾收缩遍历,当前两者相加大于目标则收缩尾部,小于则收缩头部,用res
记录最接近目标值的值
function testNear (arrNear: Array<number>, targetNear: number): number {
// 排序
arrNear.sort((a, b) => a - b);
let left = 0, right = arrNear.length - 1;
let res: number = 0;
while(left < right) {
// 这里的结束边界不能是left <= right
// 因为arrNear[left]和arrNear[right]需要是不同的元素
// 《首尾收缩遍历》
const temp = arrNear[left] + arrNear[right];
if(temp === targetNear) {
return res;
} else if (temp > targetNear) {
// 尾部收缩
right--;
} else {
// 头部收缩
left++;
}
// 更新最接近的值
res = Math.abs(targetNear - res) > Math.abs(targetNear - temp) ? temp : res;
}
return res;
}
// const arrNear = [24,69,14,37];
// const targetNear = 60;
// const nearP = testNear(arrNear, targetNear);
// console.log({ nearP }); // { nearP: 61 }
8 接雨水 - 腾讯cdg
给定 n
个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
leetcode 42. 接雨水
题解
一:动态规划
用dp
数组记录下标i及其左(leftMax
)右(rightMax
)边的所有柱子的最大高度。dp数组初始化,leftMax[0] = height[0];
和rightMax[len - 1] = height[len - 1];
。dp数组遍历过程中,左右侧的值与当值的高度进行对比,更新dp数组
leftMax[i] = Math.max(leftMax[i - 1], height[i]);
rightMax[j] = Math.max(rightMax[j + 1], height[j]);
左右侧最高的两条柱子中,矮的那条减自身高度,即为当前柱子能接的水
function trapDp(height: Array<number>): number {
// 动态规划
const len = height.length;
if (len === 0) {
return 0;
}
// 下标i及其左边的所有柱子的最大高度
const leftMax = Array(len).fill(0);
// 下标i及其右边的所有柱子的最大高度
const rightMax = Array(len).fill(0);
// 初始化
leftMax[0] = height[0];
rightMax[len - 1] = height[len - 1];
for(let i = 1; i < len; i++) {
// 更新柱子i 左侧(包括自身)的最高高度
leftMax[i] = Math.max(leftMax[i - 1], height[i]);
}
for(let j = len - 2; j >= 0; j--) {
// 更新柱子i 右侧(包括自身)的最高高度
rightMax[j] = Math.max(rightMax[j + 1], height[j]);
}
let ans = 0;
for (let k = 0; k < len; k++) {
// 左右侧最高的两条柱子中,矮的那条减自身高度,即为当前柱子能接的水
ans += Math.min(leftMax[k], rightMax[k]) - height[k];
}
return ans;
};
// const e = trapDp([0,1,0,2,1,0,1,3,2,1,2,1]);
// console.log({ e }); // 6
二、双指针
头尾双指针收缩遍历,详细见代码解
function trap(height: Array<number>): number {
// 双指针
const len = height.length;
let left = 0, right = height.length - 1;
// 初始化左右最大值
let leftMax = height[0], rightMax = height[len - 1];
let ans = 0;
while(left < right) {
// 这里循环不是为了找到某条柱子,而是要经过所有的柱子即可
// 所有不用left <= right
// 左侧最大值与左指针相比,更新最大值
leftMax = Math.max(leftMax, height[left]);
// 右侧最大值与左指针相比,更新最大值
rightMax = Math.max(rightMax, height[right]);
// 移动较矮的指针(因为矮的指针决定能存多少水--木桶原理),累加可以积累的雨水
if (height[left] < height[right]) {
ans += leftMax - height[left];
// 左指针比较小,移动左指针
left++;
} else {
ans += rightMax - height[right];
right--;
}
}
return ans;
};
// const f = trap([0,1,0,2,1,0,1,3,2,1,2,1]);
// console.log({ f }); // { f: 6 }
9 无重复字符串的最长子串
给定一个字符串s
,请你找出其中不含有重复字符的 最长
子串的长度。
leetcode 3.无重复字符串的最长子串
题解
双指针搭配set, 用set去重,左指针移动, set移除元素,右指针移动,且右指针元素不存在于set中,则加入set,最后根据左右指针位置更新最大长度
function lengthOfLongestSubstring(s: string): number {
// 左右指针
// 用set去重
const setNew = new Set();
let right = -1;
let ans = 0;
for(let left = 0; left < s.length; left++) {
if (left > 0) {
// 左指针移动, set移除左指针
setNew.delete(s[left - 1]);
}
// 右指针移动
while(right + 1 < s.length && !setNew.has(s[right + 1])) {
// 右指针不超过边界,且右指针元素不存在于set中,则加入set
setNew.add(s[right + 1]);
right++;
}
// 根据左右指针位置更新最大长度
ans = Math.max(ans, right - left + 1);
}
return ans;
};
// const g = lengthOfLongestSubstring('abcabcbb');
// // { g: 3 }
// console.log({ g });
10 找到字符串中所有字母异位词
给定两个字符串 s
和 p
,找到 s
中所有 p
的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
leetcode 438.找到字符串中所有字母异位词
题解
使用《位置数组》记录当前值在英文字母中的顺序。初始化遍历先把p
字符串和s
下标0
到pLen - 1
的子串遍历完。如这个时候如果位置数组相等,则把下标0
推进数组。后将s剩下的数据遍历完成。
function findAnagrams(s: string, p: string): Array<number> {
const sLen = s.length, pLen = p.length;
if (sLen < pLen) {
// s长度小于p长度,s中不存在p的异位词子串
return [];
}
const ans: Array<number> = [];
// 初始化位置数组
const sCount = new Array(26).fill(0);
const pCount = new Array(26).fill(0);
for (let i = 0; i < pLen; ++i) {
// 初始化遍历
// 用《位置数组》记录当前值在英文字母中的顺序
// 先把p字符串和s下标0到pLen - 1的子串遍历完
++sCount[s[i].charCodeAt(0) - 'a'.charCodeAt(0)];
++pCount[p[i].charCodeAt(0) - 'a'.charCodeAt(0)];
}
if (sCount.toString() === pCount.toString()) {
// 把p字符串和s下标0到pLen - 1的子串遍历完了,如这个时候如果位置数组相等,则把下标0推进数组
ans.push(0);
}
for (let i = 0; i < sLen - pLen; ++i) {
// 将s剩下的数据遍历完成
// 将左侧的移除
--sCount[s[i].charCodeAt(0) - 'a'.charCodeAt(0)];
// 加入右侧新遍历的(中间差了初始化遍历的pLen个值)
++sCount[s[i + pLen].charCodeAt(0) - 'a'.charCodeAt(0)];
if (sCount.toString() === pCount.toString()) {
// 位置数组相等,则把下标推进数组
ans.push(i + 1);
}
}
return ans;
};
// const arr = findAnagrams('cbaebabacd', 'abc');
// // { arr: [ 0, 6 ] }
// console.log({ arr });
关注我的公众号,回复 100905A1 获取hot100算法在线链接