首页 > 技术文章 > leetcode刷题-- 1. 双指针

ivan-blog 2020-02-18 13:50 原文

这里的题是根据 CS-Notes里的顺序来一步步复习。

双指针

165两数之和 II - 输入有序数组

题目描述

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。

函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。

说明:

返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

示例:

输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

题解(python)

class Solution(object):
    def twoSum(self, numbers, target):
        """
        :type numbers: List[int]
        :type target: int
        :rtype: List[int]
        """
        if (numbers == None):
            return None
        i = 0
        j = len(numbers)-1
        while(i<j):
            s = numbers[i] + numbers[j]
            if ( s == target):
                return [i+1,j+1]
            elif(s < target):
                i = i + 1
            else:
                j = j - 1
        return [-1,-1]

1 两数之和 (这道不是双指针,一遍哈希表)

题目描述

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

题解

# 用一遍哈希表,也就是python里的字典
# 将每个数先存进去,相同的key,会因为hash_map[n]=i这句而覆盖掉,边存便判断hash_map里是否存在满足条件的key
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        hash_map = {}
        for i,n in enumerate(nums):
            if (target-n) in hash_map.keys():
                return [hash_map[target-n], i]
            hash_map[n] = i
        return [-1,-1]

633平方数之和

题目描述

给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a2 + b2 = c。

示例1:

输入: 5
输出: True
解释: 1 * 1 + 2 * 2 = 5

示例2:

输入: 3
输出: False

题解

class Solution:
    def judgeSquareSum(self, c: int) -> bool:
        i = 0
        j = int(math.sqrt(c))

        while(i<=j):
            s = i*i + j*j
            if(s == c):
                return True
            elif(s > c):
                j = j - 1
            else:
                i = i + 1
        
        return False

680验证回文字符串 Ⅱ

题目描述

给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。

示例 1:

输入: "aba"
输出: True

示例 2:

输入: "abca"
输出: True

解释: 你可以删除c字符。

注意:

字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。

题解

解法一

class Solution:
    def validPalindrome(self, s: str) -> bool:            
        if(s[::-1] == s):
            return True
        else:
            n = len(s)
            i,j = 0,n-1
            while(i<j):
                if(s[i]==s[j]):
                    i += 1
                    j -= 1
                    continue
                else:
                    a = s[i+1:j+1]
                    b = s[i:j]
                    return a[::-1]==a or b[::-1] == b

解法二

class Solution:
    def validPalindrome(self, s: str) -> bool:            
        if s == s[::-1]:
            return True
        i,j = 0, len(s) - 1
        while i<j:
            if s[i]==s[j]:
                i, j = i+1, j-1
            else:
                a = s[i+1: j+1]
                b = s[i: j]
                return a == a[::-1] or b == b[::-1]

python里判断回文很简单a == a[::-1]就行,而这里我发现a[::-1] == a比 a == a[::-1]要耗时更多。

这道题关键就是删去一个字符

88合并两个有序数组

题目描述

给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。

说明:

初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

示例:

输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6],       n = 3

输出: [1,2,2,3,5,6]

题解

class Solution:
    def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
        """
        Do not return anything, modify nums1 in-place instead.
        """
        i,j,k = m-1, n-1, m+n-1
        while i>=0 or j>=0:
            if i<0:
                nums1[k] = nums2[j]
                k -= 1
                j -= 1
            elif j<0:
                nums1[k] = nums1[i]
                k -= 1
                i -= 1
            elif nums1[i] > nums2[j]:
                nums1[k] = nums1[i]
                k -= 1
                i -= 1
            else:
                nums1[k] = nums2[j]
                k -= 1
                j -= 1
        
# 另一种32 ms
class Solution:
    def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
        p1=m-1
        p2=n-1
        p=m+n-1
        while p1>=0 and p2>=0:
            if nums1[p1]>nums2[p2]:
                nums1[p]=nums1[p1]
                p1-=1
            else:
                nums1[p]=nums2[p2]
                p2-=1
            p-=1
        print(p2)
        if p2>=0:
            nums1[:p2+1]=nums2[:p2+1]

难点在于从后开始往前扫。这个32ms的例子的思想在于:p每移动一格,p1或p2肯定会移动一格去替换掉p处的值,当p1或者p2变成-1移到末尾时,分两种情况:

  • p1 为 -1, 这时将nums2剩下元素填入即nums1[:p2+1] = nums2[:p2+1]
  • p2 为 -1, 这时nums1剩下元素基本不用动。
  • 或者考虑两种极端情况:nums1 = [1,2,3,0,0,0] nums2= [4,5,6]; nums1 = [4,5,6,0,0,0] nums2 = [1,2,3]也是如此

141环形链表

题目描述

给定一个链表,判断链表中是否有环。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

题解

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        # 问题在于NoneType没有next这个attribute
        if head == None:
            return False
        
        l,r = head, head.next
        while r!=None and r.next!=None:
            if l==r:
                return True
            else:
                l = l.next
                r = r.next.next
        return False

使用双指针,快慢指针。一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇。

一开始l,r分别初始化为head和head.next,l走得慢,r走得快。如果在前走的快的r还与l相遇了,那么肯定存在环。

这里的r只需要判断r和r.next不为None就可以了。判断r的目的是为了r.next不报错不然r.next直接报错。
需注意,一开始给l,r赋值之前要判断下head是否为None,不然head.next直接报错。

*142环形链表 II(附加题,熟悉下链表)

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

说明:不允许修改给定的链表。

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。

题解

一开始没想出来,参考讨论区里的解法

解题思路:

  • 这类链表题目一般都是使用双指针法解决的,例如寻找距离尾部第K个节点、寻找环入口、寻找公共尾部入口等。

算法流程:

  1. 双指针第一次相遇: 设两指针 fast,slow 指向链表头部 head,fast 每轮走 22 步,slow 每轮走 11 步;

    1. 第一种结果: fast 指针走过链表末端,说明链表无环,直接返回 null;
      • TIPS: 若有环,两指针一定会相遇。因为每走 1 轮,fast 与 slow 的间距 +1,fast 终会追上 slow;
    2. 第二种结果: 当fast == slow时, 两指针在环中 第一次相遇 。下面分析此时fast 与 slow走过的 步数关系 :
      • 设链表共有 a+b 个节点,其中 链表头部到链表入口 有 a个节点(不计链表入口节点), 链表环 有 b 个节点(这里需要注意,a 和 b 是未知数,例如图解上链表 a=4,b=5b=5);设两
        指针分别走了 ff,ss 步,则有:
      1. fast 走的步数是slow步数的 2 倍,即 f = 2s;(解析: fast 每轮走 2 步)
      2. fast 比 slow多走了 n 个环的长度,即 f = s + nb;( 解析: 双指针都走过 a 步,然后在环内绕圈直到重合,重合时 fast 比 slow 多走环的长度整数倍 );
      • 以上两式相减得:f = 2nb,s = nb,即fast和slow 指针分别走了 2n,n 个 环的周长 (注意: n 是未知数,不同链表的情况不同)。
  2. 目前情况分析::

    • 如果让指针从链表头部一直向前走并统计步数k,那么所有 走到链表入口节点时的步数 是:k=a+nb(先走 a 步到入口节点,之后每绕 1 圈环( b 步)都会再次到入口节点)。
    • 而目前,slow 指针走过的步数为 nb 步。因此,我们只要想办法让 slow 再走 a 步停下来,就可以到环的入口。
    • 但是我们不知道 a 的值,该怎么办?依然是使用双指针法。我们构建一个指针,此指针需要有以下性质:此指针和slow 一起向前走 a 步后,两者在入口节点重合。那么从哪里走到入口节点需
      要 a 步?答案是链表头部head。
  3. 双指针第二次相遇:

    • slow指针 位置不变 ,将fast指针重新 指向链表头部节点 ;slow和fast同时每轮向前走 11 步;
      • TIPS:此时 f = 0f=0,s = nbs=nb ;
    • 当 fast 指针走到f = af=a 步时,slow 指针走到步s = a+nbs=a+nb,此时 两指针重合,并同时指向链表环入口 。
  4. 返回slow指针指向的节点。

复杂度分析:

时间复杂度 O(N):第二次相遇中,慢指针须走步数 a < a + b;第一次相遇中,慢指针须走步数 a + b - x < a + b,其中 x 为双指针重合点与环入口距离;因此总体为线性复杂度;
空间复杂度 O(1):双指针使用常数大小的额外空间。

当走了a+nb时,位置为入口处;而第一次相遇后慢指针走的距离为nb,注意这两个n不相等,但nb再走a步就到了入口处,这时我们可以另设一个指针从head处走,每次一步,那么他就会与慢指针在入口处相遇。

题解

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        fast, slow = head, head
        while True:
            if not(fast and fast.next):
                return 
            fast, slow = fast.next.next, slow.next
            if fast == slow:
                break
        fast = head
        while fast!=slow:
            fast = fast.next
            slow = slow.next
        return slow

这里我之前在while里判断fast != None and fast.next != None,这样造成的问题就是判断为False时,我的返回一个空说明没环,不好写。所以判断最好还是在if里,循环用while True。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        if head == None:
            return None
        fast, slow = head, head
        if fast.next!=None:
            fast = fast.next.next
            slow = slow.next
        while fast != None and fast.next != None:
            if fast!=slow:
                fast = fast.next.next
                slow = slow.next
            else:
                fast = head
                while fast!=slow:
                    fast = fast.next
                    slow = slow.next
                return slow

        return None

524通过删除字母匹配到字典里最长单词

题目描述

给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。

示例 1:

输入:
s = "abpcplea", d = ["ale","apple","monkey","plea"]

输出: 
"apple"

示例 2:

输入:
s = "abpcplea", d = ["a","b","c"]

输出: 
"a"

说明:

  1. 所有输入的字符串只包含小写字母。
  2. 字典的大小不会超过 1000。
  3. 所有输入的字符串长度不会超过 1000。

题解

def isSubStr(s:str, t:str):
    #第一种,双指针
    # l1,l2 = len(s), len(t)
    # if l1 < l2:
    #     return False
    # i,j = 0,0
    # while (i<l1 and j<l2):
    #     if (s[i] != t[j]):
    #         i += 1
    #     else:
    #         i += 1
    #         j += 1
    
    # return j == l2
    #第二种
    for i in t:
        if i in s:
            s = s[s.index(i)+1:]
        else:
            return False

    return True


class Solution:
    def findLongestWord(self, s: str, d: List[str]) -> str:
        d.sort()
        res = ''
        if d:
            for i in d:
                if (len(i) < len(res) or (len(i)==len(res) and res < i)):
                    continue
                else:
                    if (isSubStr(s, i)):
                        res = i
        
        return res

另外一种方法,先将d进行按照len进行降序排序,每一个元素那么最长的排最前面,短的后面~同样长的,就按照字典顺序:a<b<c<d这种排列。所以它不需要遍历d种所有元素

推荐阅读