167.两数之和(二)——输入有序数组
题目
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9,输出: [1,2]。
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
题解
(1)双指针解法:初始时两个指针分别指向第一个元素位置和最后一个元素的位置。每次计算两个指针指向的两个元素之和,并和目标值比较。如果两个元素之和等于目标值,则发现了唯一解。如果两个元素之和小于目标值,则将左侧指针右移一位。如果两个元素之和大于目标值,则将右侧指针左移一位。移动指针之后,重复上述操作,直到找到答案。
复杂度分析
时间复杂度:O(n),其中 nnn 是数组的长度。两个指针移动的总次数最多为 n 次。
空间复杂度:O(1)。
1 | var twoSum = function(numbers, target) { |
(2)二分查找
在数组中找到两个数,使得它们的和等于目标值,可以首先固定第一个数,然后寻找第二个数,第二个数等于目标值减去第一个数的差。利用数组的有序性质,可以通过二分查找的方法寻找第二个数。为了避免重复寻找,在寻找第二个数时,只在第一个数的右侧寻找。
复杂度分析
时间复杂度:O(nlogn),其中 n 是数组的长度。需要遍历数组一次确定第一个数,时间复杂度是 O(n),寻找第二个数使用二分查找,时间复杂度是 O(logn),因此总时间复杂度是 O(nlogn)。
空间复杂度:O(1)
1 | var twoSum = function(numbers, target) { |
(3)使用map方法存储值,遍历数组中的每个元素,算出target与遍历元素的差,再去map中寻找是否有这么一个差。这样的话相当于空间复杂度变高了,是O(n)。
1 | var twoSum = function(numbers, target) { |
633.两数平方和
题目
给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a2 + b2 = c 。
示例 1:
输入:c = 5
输出:true
解释:1 * 1 + 2 * 2 = 5
示例 2:
输入:c = 3
输出:false
示例 3:
输入:c = 4
输出:true
示例 4:
输入:c = 2
输出:true
示例 5:
输入:c = 1
输出:true
题解
(1)双指针解法(这里注意求某数的平方和时,可以直接从1到它的平方根遍历寻找)
1 | var judgeSquareSum = function(c) { |
因为最多只需要遍历一次 0~sqrt(target),所以时间复杂度为 O(sqrt(target))。又因为只使用了两个额外的变量,因此空间复杂度为 O(1)。
(2)直接判断开方数
1 | var judgeSquareSum = function (c) { |
时间复杂度为O(根号c) 空间复杂度为O(1)
(3)二分查找
1 | var judgeSquareSum = function (c) { |
这种方法效果不是很好,但是在一定情况下也可以用。在力扣平台中显示超过限制。
345. 反转字符串中的元音字母
题目
编写一个函数,以字符串作为输入,反转该字符串中的元音字母。
示例 1:
输入:”hello”
输出:”holle”
示例 2:
输入:”leetcode”
输出:”leotcede”
提示:
元音字母不包含字母 "y" 。
题解
(1)还是使用双指针(这里注意还可以注意一下continue的用法)
1 | var reverseVowels = function(s) { |
另一种双指针方式:
1 | var reverseVowels = function(s) { |
(2)用set来判断是否存在元音字母,双指针查找,es6解构赋值交换(这个就是先遍历左边,如果一直没有,就一直遍历,有的话,则再遍历右边指针,如果正好也有,那么就交换,交换完了左边加;如果没有,则右边减。如果遍历的左边就没有的话,那么就左边加),详情见代码。 这里也需要注意set的用法。
1 | var reverseVowels = function(s) { |
680.验证回文字符串||
题目
给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。
示例 1:
输入: “aba”
输出: True
示例 2:
输入: “abca”
输出: True
解释: 你可以删除c字符。
注意:
字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。
题解
(1)判断是否是回文串,用「双指针」的思路肯定没错:
设置头尾指针,如果指向的字符相同,则指针内移,继续检查。
如果指向的字符不同,还不能判死刑,看看能否通过删一个字符(要么删左指针指向的字符,要么删右指针指向的字符),使得剩下的字串是回文串
这里要写一个判断回文串的辅助函数,去判断「删去一个字符后」的子串,是否是回文串。
1 | function isPali(str, l, r) { // 判断str是否回文 |
88.合并两个有序数组
题目
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
示例 2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
提示:
nums1.length == m + n
nums2.length == n
0 <= m, n <= 200
1 <= m + n <= 200
-109 <= nums1[i], nums2[i] <= 109
题解
(1)从后往前——从尾部设置三个指针,向前遍历,这样就不需要额外使用空间了。(目前最好的方法)
1.用2个指针分别指向已有nums1和nums2的末尾,一个k指针指向nums1新的末尾。
2.进行2个指针数比较,哪个数大,就在nums1[k–]地方放置新的数
3.当nums2的数全部比较结束后终止循环。
1 | var merge = function(nums1, m, nums2, n) { |
另一种(代码风格不一样)遍历完后
当 n > 0 时
- 说明 nums2 中还有剩余没有比较的数字
- 将其插入替换 nums1 数组前面n个数字即可
1 | var merge = function(nums1, m, nums2, n) { |
- 时间复杂度 : O(n+m)。
- 空间复杂度 : O(1)。
(2)从前往后:一般而言,对于有序数组可以通过 双指针法 达到O(n+m)的时间复杂度。
最直接的算法实现是将指针p1 置为 nums1的开头, p2为 nums2的开头,在每一步将最小值放入输出数组中。
由于 nums1 是用于输出的数组,需要将nums1中的前m个元素放在其他地方,也就需要 O(m+n) 的空间复杂度。
1 | var merge = function(nums1, m, nums2, n) { |
141.环形列表
题目
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false 。
进阶:
你能用 O(1)(即,常量)内存解决此问题吗?
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
提示:
链表中节点的数目范围是 [0, 104]
-105 <= Node.val <= 105
pos 为 -1 或者链表中的一个 有效索引 。
题解
(1)快慢指针:定义快指针 fast , 慢指针 slow .
快指针每次走两格, 慢指针每次走一格.
若将两指针, 放在环中的任意位置 (环的节点数为 n ) .
在环中, 每次 fast 比 slow 多移动一格. 即在 fast 移动的方向上, fast 与 slow 之间的距离减一.
则至多 n 步后, fast 与 slow 相遇.
1 | var hasCycle = function(head) { |
时间复杂度: O(n), n 为链表 head 长度.
链表 head 中有环时.
在环中, 每次 fast 比 slow 多移动一格. 即在 fast 移动的方向上, fast 与 slow 之间的距离减一.
fast “追赶” slow 最长距离为 n ( 此时, head 为首尾相连的环形链表 ). 即, 最多 n 步后, fast 与 slow 相遇.
空间复杂度: O(1)
(2)标记(不是特别懂这个神奇的操作)
1 | var hasCycle = function(head) { |
时间复杂度: O(n), n 为链表 head 长度.
空间复杂度: O(1)
524. 通过删除字母匹配到字典里最长单词
题目
给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。
示例 1:
输入:
s = “abpcplea”, d = [“ale”,”apple”,”monkey”,”plea”]
输出:
“apple”
示例 2:
输入:
s = “abpcplea”, d = [“a”,”b”,”c”]
输出:
“a”
题解
(1)使用双指针,在s与d[i]匹配的过程中,假设已匹配字母为count,d[i]的剩余未匹配长度为len,已有匹配最大值为maxL,如果len+count < maxL,那么就没有继续比较的必要,直接退出
如:s = “abpcplea”, d = [“ale”,”apple”,”monkey”,”plea”]
1、s与匹配完apple后,maxL为5,
2、匹配abpcplea和monkey,monkey前两个字母不匹配,剩余字母只有“nkey”,已经不可能有更大的maxL,可以直接退出
2、匹配abpcplea和plea,plea的长度<maxL,也可以直接退出
(不是自己的想法,不是特别好理解,能看懂但是如果是自己做的话估计想不到这么复杂。)
1 | var findLongestWord = function (s, d) { |
(2)这题的关键就是怎么在字符串字典中找到那个对应的字符串。其实很简单。(目前js代码还没有写)
只要利用两个指针i,j,一个指向s字符串,一个指向sstr字符串,每一次查找过程中,i依次后移,若i,j对应的两个字符相等,则j后移,如果j可以移到sstr.length(),那么说明sstr中对应的字符s中都有,即s中删除一些字符后,可以得到sstr字符串,最后一步就是比较当前的结果字符与找到的sstr字符,按照题目的需求来决定是否改变结果字符,是不是还挺简单的呀。下面第一个是java版本,第二个是用js复现的(但是目前超出时间限制了)。
1 | class Solution { |
1 | var findLongestWord = function (s, d) { |
Author: Amanda-Zhang
Link: http://chunchunya.github.io/2021/01/12/1.12_%E5%8F%8C%E6%8C%87%E9%92%88/
Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.