2.双指针练习

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]
解释:27 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
示例 2:

输入:numbers = [2,3,4], target = 6
输出:[1,3]
解释:24 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。
示例 3:

输入:numbers = [-1,0], target = -1
输出:[1,2]
解释:-10 之和等于目标数 -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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值