算命法练习-双指针
Two Sum
167. Two Sum II - Input array is sorted (Easy)
题目:给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。
如果设这两个数分是 numbers[index1] 和 numbers[index2] ,
则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
示例 1:
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
示例 2:
输入:numbers = [2,3,4], target = 6
输出:[1,3]
解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。
示例 3:
输入:numbers = [-1,0], target = -1
输出:[1,2]
解释:-1 与 0 之和等于目标数 -1 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
提示:
2 <= numbers.length <= 3 * 104
-1000 <= numbers[i] <= 1000
numbers 按 非递减顺序 排列
-1000 <= target <= 1000
仅存在一个有效答案
分析
如果用到双指针,那么最快的方法就是一个指针指向数组开始,另一个指向数组结束。再将两个指针对应的数据相加,和大了,左边指针右移,和小了,右边指针左移(数组是非递减)。循环终止条件是两个指针重合或者是找到目标和。
var twoSum = function (numbers, target) {
// 如果是返回多个的结果的话,可以存在二维数组里,本题只用返回一次,而且还一定有答案
let right = numbers.length - 1;
let i = 0;
while (i < right) {
if (numbers[i] + numbers[right] == target) {
return [i + 1, right + 1]
} else if (numbers[i] + numbers[right] > target) {
right--;
} else {
i++;
}
}
};
归并两个有序数组
88. 合并两个有序数组 (Easy)
题目:给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
示例 2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。
示例 3:
输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。
提示:
nums1.length == m + n
nums2.length == n
0 <= m, n <= 200
1 <= m + n <= 200
-109 <= nums1[i], nums2[j] <= 109
进阶:你可以设计实现一个时间复杂度为 O(m + n) 的算法解决此问题吗?
分析
这题用最简单的方法,可以创建一个新数组,然后对原来的两个数组排序,之后插入。然后再将新数字的值赋给nums1.但是这种做法和题目想让我们用的不太一样,不然nums1的长度就不会是m+n了。所以考虑双指针的做法。已知nums1的长度是(m+n),而且nums1和nums2是非递减的。那么可以在nums1的后面倒着插入,原先nums1前半部分和nums中最大的那个数值,取完一个之后,移动指针继续向下取。如果nums2的指针取到头,说明nums2已经完全被插入到nums1了,否则还是要继续将nums2插入。并且nums1前半部分是否走完是不影响排序的,因为本来就是从nums1里面取的,前面取一个后面补一个,会把重复的直接覆盖。
var merge = function (nums1, m, nums2, n) {
let p1 = m-1;
let p2 = n-1;
let p3 = m+n-1;
while (p1 >= 0 && p2 >= 0) {
if (nums1[p1] <= nums2[p2]) {
nums1[p3] = nums2[p2]
p2--
} else {
nums1[p3] = nums1[p1]
p1--
}
p3--;
}
// 如果nums2已经插入完毕可以直接返回。如果nums2没有插入完毕,还需要继续插入
while(p2>=0){
nums1[p3] = nums2[p2]
p2--;
p3--;
}
return nums1;
};
快慢指针
142. 环形链表 II
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
提示:
链表中节点的数目范围在范围 [0, 104] 内
-105 <= Node.val <= 105
pos 的值为 -1 或者链表中的一个有效索引
进阶:你是否可以使用 O(1) 空间解决此题?
分析
这题涉及到数学计算,用快慢指针的思量来写就是,快慢指针起始位置都在head上,快指针每次走两步,慢指针每次走一步。如果有环,最后快指针会将间距一步一步缩小,最终快慢指针会指向同一个位置。此时假设从初始位置到入圈位置间距为a,相遇位置距入圈位置为b,从相遇再回到入口位置间距为c,那么2(a+T1(b+c)+b)=a+T2(b+c)+b,最后a=c+(T2-2T1-1)(b+c),而b+c是一个环的长度,也就是说从开头会到达起始入口点,但是从相遇处走a步,也会回到起始入口点,只是会多转几圈。所以本题的切入点在于,找到第一次快慢指针相遇的地方,再让一个指针从开头开始走,另一个从相遇点开始走,每次移动一位,最终会在入口处相遇。
var detectCycle = function (head) {
// 使用不同的step,进行快慢指针跳转,如果有环那么会指向同一个地方
let quick = head;
let slow = head;
while (quick != null && slow != null) {
if (quick.next == null) return null;
quick = quick.next.next;
slow = slow.next;
if (slow == quick) {
quick = head;
// 一次相遇后,间距再走一个第一次出现环的距离就会回到环
while (slow != quick) {
quick = quick.next;
slow = slow.next;
}
// 再次相遇
return slow;
}
}
return null;
};
滑动窗口
76. 最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
示例 2:
输入:s = "a", t = "a"
输出:"a"
解释:整个字符串 s 是最小覆盖子串。
示例 3:
输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
提示:
m == s.length
n == t.length
1 <= m, n <= 105
s 和 t 由英文字母组成
进阶:你能设计一个在 o(m+n) 时间内解决此问题的算法吗?
分析
这题是利用双指针划分出来一个区间,一个指向区间的开头,一个指向区间的结尾。每一次指针移动,不管是头指针还是尾指针移动,都要判断一个区间是否包含目标字符串中字符的种类与数量,如果不包含,那么尾指针右移。如果包含就判断一下当前字符串的长度是不是最短的,同时移动头指针,看还能不能缩短字符串,已经是当前最短的情况下,再向右移动一位去找下一段包含目标字符串的种类和数量的字符串。其中比较麻烦的是将区间内的字符串与目标字符串进行比较,但是借助对象或者map存储这些特征,查起来会快很多。
var minWindow = function (s, t) {
let l = 0;
let r = 0;
let m = s.length;
let arr = new Map();
let arrType = 0
let res = ''
for (let i = 0; i < t.length; i++) {
let c = t[i]
if (!arr.has(c)) {
// 记录种类和每种出现的频率
arrType++;
arr.set(c, 1)
} else {
arr.set(c, arr.get(c) + 1)
}
}
while (r < m) {
let temp = s[r]
if (arr.has(temp)) {
arr.set(temp, arr.get(temp) - 1)
if (arr.get(temp) == 0) arrType--;
while (arrType == 0) {
// 范围内容已全部满足条件
let newres = s.substring(l, r + 1)
if (!res || (newres.length < res.length)) {
res = newres;
}
if (arr.has(s[l])) {
arr.set(s[l], arr.get(s[l]) + 1)
if (arr.get(s[l]) == 1)
arrType++;
}
l++
}
}
r++
}
return res;
};
练习
633. 平方数之和
给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a2 + b2 = c 。
示例 1:
输入:c = 5
输出:true
解释:1 * 1 + 2 * 2 = 5
示例 2:
输入:c = 3
输出:false
提示:
0 <= c <= 231 - 1
分析
又是有关两个数怎么怎么样的,可以联想到上面two sum类型,使用首尾指针,从两面向中间缩进,直到两指针重合。但是由于这里是平方,容易超出时间范围,所以可以适当地使用二分法剪枝,每次如果中间区间也不符合,直接跳到另外一半区间去,节约时间。
var judgeSquareSum = function (c) {
let start = 0;
let temp = 0;
let mid = 0;
let result = 0;
let end = Math.ceil(c / 2);
while (start <= end) {
result = start * start + end * end;
mid = Math.ceil((end + start) / 2)
if (result == c) return true;
else if (result < c) {
temp = mid * mid + end * end;
if (temp < c) start = mid + 1
else
start++
}
else {
temp = mid * mid + start * start;
if (temp > c) {
end = mid - 1
} else {
end--
}
}
}
return false;
};
680. 验证回文串 II
给你一个字符串 s,最多 可以从中删除一个字符。
请你判断 s 是否能成为回文字符串:如果能,返回 true ;否则,返回 false 。
示例 1:
输入:s = "aba"
输出:true
示例 2:
输入:s = "abca"
输出:true
解释:你可以删除字符 'c' 。
示例 3:
输入:s = "abc"
输出:false
提示:
1 <= s.length <= 105
s 由小写英文字母组成
分析
感觉和贪心算法里面判断递减数列的一题很像,都是可以改贬义词。这题可以用贪心+首尾指针写,两边区间收缩,如果出现不一样的,有两种修改方式,修改左边和修改右边。那么如果都再次出现修改那么就是不符合条件的。
var validPalindrome = function (s) {
let start = 0;
let end = s.length - 1;
return isHui(s, start, end, 0)
};
function isHui(s, start, end, count) {
while (start <= end) {
if (s[start] != s[end]) {
if (count < 1) {
count++;
return (isHui(s, start + 1, end, count) || isHui(s, start, end - 1, count))
} else {
return false
}
}
start++;
end--;
}
return true
}
524. 通过删除字母匹配到字典里最长单词
给你一个字符串 s 和一个字符串数组 dictionary ,找出并返回 dictionary 中最长的字符串,该字符串可以通过删除 s 中的某些字符得到。
如果答案不止一个,返回长度最长且字母序最小的字符串。如果答案不存在,则返回空字符串。
示例 1:
输入:s = "abpcplea", dictionary = ["ale","apple","monkey","plea"]
输出:"apple"
示例 2:
输入:s = "abpcplea", dictionary = ["a","b","c"]
输出:"a"
提示:
1 <= s.length <= 1000
1 <= dictionary.length <= 1000
1 <= dictionary[i].length <= 1000
s 和 dictionary[i] 仅由小写英文字母组成
分析
这题要找出符合条件的最长字符串,并且有相同长度的字符串要返回小的。第一步:找出符合条件的所有字符串,比较并记录max的值。我这里使用map,将所有符合条件的字符串,以长度为key值,存储在数组的value中。
第二步:根据变量max,从map中找到最长字符串对应的数组
第三部:如果数组长度大于1,从小到大排序,否则直接返回。
这里第一步可能会复杂一点。判断是否符合条件,使用while,数组内的某个item,与s从头到尾比较,有相同字母,指针均向右移,不相同的话s的指针右移,因为s删除某些字母后就可以得到item,如果item遍历结束出来,说明是符合条件的,否则就不符合条件。
var findLongestWord = function (s, dictionary) {
let n = dictionary.length;
let m = s.length;
let arr = new Map();
let max = 0;
let p1 = 0;
let p2 = 0;
let temp = '';
for (let i = 0; i < n; i++) {
temp = dictionary[i];
p1 = 0;
p2 = 0;
while (p1 < temp.length && p2 < m) {
if (temp[p1] == s[p2]) {
p1++;
p2++
} else {
p2++;
}
if (p1 == temp.length) {
// 该字符串通过s删减得到
if (p1 > max) max = p1
if (arr.has(p1)) {
let c = arr.get(p1)
c.push(temp)
arr.set(p1, c)
}
else arr.set(p1, [temp])
}
}
}
if (max == 0) return ''
else {
return arr.get(max).sort()[0];
}
};
进阶
340.至多包含K种字符的子串
题目:给定一个长度为 n 的字符串 s ,找出最多包含 k 种不同字符的最长连续子串的长度。
数据范围:
1≤ n ≤ 10^5
1≤n≤10^5
, 字符串中仅包含小写英文字母
分析
这里说最多包含K种的最长字符串长度,和上面的覆盖最小子串很像。不同点在于上面的种类是我们自己循环出来的,并且字符串最短。本题种类数先给我们了,要返回最长的字符串。那么依然可以借助Map记录当前滑动区间内字母的种类和数量。并且每滑动一次指针判断是否符合条件,符合条件的话,和之前最长的比较记录唱的字符串。如果不符合条件,那么做指针就要向右滑把多余的种类给滑到区间外面去
function longestSubstring(s, k) {
// write code here
let p1 = 1;
let arr = [s[0]];
let n=s.length;
let kind=1;
let map = new Map()
map.set(s[0],1)
let max=1;
while(p1<n){
let c= s[p1]
if(arr.includes(c)){
arr.push(c)
map.set(c,map.get(c)+1)
p1++
}else{
if(kind<k){
kind++
p1++
arr.push(c)
if(map.has(c)){
map.set(c,map.get(c)+1)
}else{
map.set(c,1)
}
}else{
map.set(arr[0],map.get(arr[0])-1)
if(map.get(arr[0])==0){
kind--
}
arr.shift()
}
}
if(max<arr.length) max = arr.length
}
return max;
}