双指针算法模板和一些题目

什么是同向双指针? 什么是相向双指针?
双指针的鼻祖题 —— 两数之和 Two Sum 链表上的快慢指针算法
快速排序 & 归并排序

 

同向双指针 • 相向双指针
• 几乎全部 Two Sum 变种 • Partition
• Quick Select • 分红两个部分 • 分红三个部分
• 一些你没听过的(可是面试会考的)排序算法

 

 

一个典型的相向双指针问题就是翻转字符串的问题。html

相向双指针模板1node

Python:python

""" @param s: a list of characters """ def reverse(s): left, right = 0, len(s)-1 while left < right: s[left], s[right] = s[right], s[left] left += 1 right -= 1 

另一个双指针的经典练习题,就是回文串的判断问题。给一个字符串,判断这个字符串是否是回文串。面试

咱们能够用双指针的算法轻易的解决:算法

Python:数组

def isPalindrome(s): i, j = 0, len(s)-1 while i < j: if s[i] != s[j]: return False i += 1 j -= 1 return True 

 双指针的鼻祖:两数之和

题目描述

给一个整数数组,找到两个数使得他们的和等于一个给定的数 target。
返回这两个数。markdown

相向双指针模板2ide

Python:函数

class Solution: def twoSum(self, numbers, target): numbers.sort() L, R = 0, len(numbers)-1 while L < R: if numbers[L]+numbers[R] == target: return (numbers[L], numbers[R]) if numbers[L]+numbers[R] < target: L += 1 else: R -= 1 return None 
  1. 首先咱们对数组进行排序。
  2. 用两个指针(L, R)从左右开始:
    • 若是numbers[L] + numbers[R] == target, 说明找到,返回对应的数。
    • 若是numbers[L] + numbers[R] < target, 此时L指针右移,只有这样才可能让和更大。
    • 反之使R左移。
  3. L和R相遇尚未找到就说明没有解。

 

同向双指针

同向双指针的问题,是指两根指针都从头出发,朝着同一个方向前进。咱们经过下面 5 个题目来初步认识同向双指针:优化

  1. 数组去重问题 Remove duplicates in an array
  2. 滑动窗口问题 Window Sum
  3. 两数之差问题 Two Difference
  4. 链表中点问题 Middle of Linked List
  5. 带环链表问题 Linked List Cycle

问题描述

给你一个数组,要求去除重复的元素后,将不重复的元素挪到数组前段,并返回不重复的元素个数。

LintCode 练习地址:http://www.lintcode.com/problem/remove-duplicate-numbers-in-array/

问题分析

这个问题有两种作法,第一种作法比较容易想到的是,把全部的数扔到 hash 表里,而后就能找到不一样的整数有哪些。可是这种作法会耗费额外空间 O(n)O(n)O(n)。面试官会追问,如何不耗费额外空间。

此时咱们须要用到双指针算法,首先将数组排序,这样那些重复的整数就会被挤在一块儿。而后用两根指针,一根指针走得快一些遍历整个数组,另一根指针,一直指向当前不重复部分的最后一个数。快指针发现一个和慢指针指向的数不一样的数以后,就能够把这个数丢到慢指针的后面一个位置,并把慢指针++。

同向双指针模板1

# O(nlogn) time, O(1) extra space
class Solution:
    # @param {int[]} nums an array of integers
    # @return {int} the number of unique integers
    def deduplication(self, nums):
        # Write your code here
        n = len(nums)
        if n == 0:
            return 0
            
        nums.sort()
        result = 1
        for i in range(1, n):
            if nums[i - 1] != nums[i]:
                nums[result] = nums[i]
                result += 1
                
        return result

 

问题描述

求出一个数组每 kkk 个连续整数的和的数组。如 nums = [1,2,3,4], k = 2 的话,window sum 数组为 [3,5,7]
http://www.lintcode.com/problem/window-sum/

问题分析

这个问题并无什么难度,可是若是你过于暴力的用户 O(n∗k)O(n * k)O(nk) 的算法去作是并不合适的。好比当前的 window 是 |1,2|,3,4。那么当 window 从左往右移动到 1,|2,3|,4 的时候,整个 window 内的整数和是增长了3,减小了1。所以只须要模拟整个窗口在滑动的过程当中,整数一进一出的变化便可。这就是滑动窗口问题。

class Solution:
    # @param nums {int[]} a list of integers
    # @param k {int} size of window
    # @return {int[]} the sum of element inside the window at each moving
    def winSum(self, nums, k):
        # Write your code here
        n = len(nums)
        if n < k or k <= 0:
            return []
        sums = [0] * (n - k + 1)
        for i in range(k):
            sums[0] += nums[i];

        for i in range(1, n - k + 1):
            sums[i] = sums[i - 1] - nums[i - 1] + nums[i + k - 1]

        return sums

 

两数之差问题

610. 两数和 - 差等于目标值

中文
English

给定一个整数数组,找到两个数的 等于目标值。index1必须小于index2。注意返回的index1和index2不是 0-based。

样例

例1:

输入: nums = [2, 7, 15, 24], target = 5 
输出: [1, 2] 
解释:
(7 - 2 = 5)

例2:

输入: nums = [1, 1], target = 0
输出: [1, 2] 
解释:
(1 - 1 = 0)

注意事项

保证只有一个答案。

问题分析

做为两数之和的一个 Follow up 问题,在两数之和被问烂了之后,两数之差是常常出现的一个面试问题。
咱们能够先尝试一下两数之和的方法,发现并不奏效,由于即使在数组已经排好序的前提下,nums[i] - nums[j] 与 target 之间的关系并不能决定咱们淘汰掉 nums[i] 或者 nums[j]。

那么咱们尝试一下将两根指针同向前进而不是相向而行,在 i 指针指向 nums[i] 的时候,j 指针指向第一个使得 nums[j] - nums[i] >= |target| 的下标 j:

  1. 若是 nums[j] - nums[i] == |target|,那么就找到答案
  2. 不然的话,咱们就尝试挪动 i,让 i 向右挪动一位 => i++
  3. 此时咱们也同时将 j 向右挪动,直到 nums[j] - nums[i] >= |target|

能够知道,因为 j 的挪动不会从头开始,而是一直递增的往下挪动,那么这个时候,i 和 j 之间的两个循环的就不是累乘关系而是叠加关系。

同向双指针模板2

Python:

nums.sort()
target = abs(target)

j = 1 for i in range(len(nums)): while j < len(nums) and nums[j]-nums[i] < target: j += 1 if nums[j]-nums[i] == target: # 找到答案 

 

class Solution:
    """
    @param nums: an array of Integer
    @param target: an integer
    @return: [index1 + 1, index2 + 1] (index1 < index2)
    """
    def twoSum7(self, nums, target):
        # write your code here
        target = abs(target)
        nums2 = [(n, i) for i,n in enumerate(nums)]
        nums2.sort(key=lambda x: x[0])
        result = []
        j = 1
        for i in range(len(nums2)):
            while j < len(nums2) and nums2[j][0]-nums2[i][0] < target:
                j += 1
            if nums2[j][0]-nums2[i][0] == target:
                if i != j:
                    result = (nums2[i][1]+1, nums2[j][1]+1)
                    break
        if result[0] > result[1]:                        
            return [result[1], result[0]]
        return result

类似问题

G家的一个类似问题:找到一个数组中有多少对二元组,他们的平方差 < target(target 为正整数)。
咱们能够用相似放的方法来解决,首先将数组的每一个数进行平方,那么问题就变成了有多少对两数之差 < target。
而后走一遍上面的这个流程,当找到一对 nums[j] - nums[i] >= target 的时候,就至关于一口气发现了:

nums[i + 1] - nums[i]
nums[i + 2] - nums[i]
...
nums[j - 1] - nums[i]

一共 j - i - 1 对知足要求的二元组。累加这个计数,而后挪动 i 的位置 +1 便可。

 

链表中点问题

问题描述

求一个链表的中点

LintCode 练习地址:http://www.lintcode.com/problem/middle-of-linked-list/

228. 链表的中点

中文
English

找链表的中点。

样例

样例 1:

输入:  1->2->3
输出: 2	
样例解释: 返回中间节点的值

样例 2:

输入:  1->2
输出: 1	
样例解释: 若是长度是偶数,则返回中间偏左的节点的值。

挑战

若是链表是一个数据流,你能够不从新遍历链表的状况下获得中点么?

同向双指针模板3--针对链表

"""
Definition of ListNode
class ListNode(object):
    def __init__(self, val, next=None):
        self.val = val
        self.next = next
"""

class Solution:
    """
    @param head: the head of linked list.
    @return: a middle node of the linked list
    """
    def middleNode(self, head):
        # write your code here
        slow, fast = head, head
        while fast and fast.next and fast.next.next:
            slow = slow.next
            fast = fast.next.next
        return slow

其中,fast.next.next条件表示能够往前跨两步。

问题分析

这个问题可能你们会以为,WTF 这么简单有什么好作的?你可能的想法是:

先遍历一下整个链表,求出长度 L,而后再遍历一下链表找到第 L/2 的那个位置的节点。

可是在你抛出这个想法以后,面试官会追问你:若是只容许遍历链表一次怎么办?

能够看到这种 Follow up 并非让你优化算法的时间复杂度,而是严格的限制了你遍历整个链表的次数。你可能会认为,这种优化有意义么?事实上是颇有意义的。由于遍历一次这种场景,在真实的工程环境中会常常遇到,也就是咱们常说的数据流问题(Data Stream Problem)。

数据流问题 Data Stream Problem

所谓的数据流问题,就是说,你须要设计一个在线系统,这个系统不断的接受一些数据,并维护这些数据的一些信息。好比这个问题就是在数据流中维护中点在哪儿。(维护中点的意思就是提供一个接口,来获取中点)

相似的一些数据流问题还有:

  1. 数据流中位数 http://www.lintcode.com/problem/data-stream-median/
  2. 数据流最大 K 项 http://www.lintcode.com/problem/top-k-largest-numbers-ii/
  3. 数据流高频 K 项 http://www.lintcode.com/problem/top-k-frequent-words-ii/

这类问题的特色都是,你没有机会第二次遍历全部数据。上述问题部分将在《九章算法强化班》中讲解。

用双指针算法解决链表中点问题

咱们可使用双指针算法来解决链表中点的问题,更具体的,咱们能够称之为快慢指针算法。该算法以下:

Python:

slow, fast = head, head.next
while fast != None and fast.next != None: slow = slow.next fast = fast.next.next return slow 

完整参考程序

在上面的程序中,咱们将快指针放在第二个节点上,慢指针放在第一个节点上,while 循环中每一次快指针走两步,慢指针走一步。这样当快指针走到头的时候,慢指针就在中点了。

快慢指针的算法,在下一小节的“带环链表”中,也用到了。======>这种写法容易出错,个人预判可以走两步的作法更好!

一个小练习

将上述代码改成提供接口的模式,即设计一个 class,支持两个函数,一个是 add(node) 加入一个节点,一个是 getMiddle() 求中间的那个节点。

 

102. 带环链表

中文
English

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

样例

```
样例 1:
	输入: 21->10->4->5,  then tail connects to node index 1(value 10).
	输出: true
	
样例 2:
	输入: 21->10->4->5->null
	输出: false

```

挑战

不要使用额外的空间

"""
Definition of ListNode
class ListNode(object):
    def __init__(self, val, next=None):
        self.val = val
        self.next = next
"""

class Solution:
    """
    @param head: the head of linked list.
    @return: a middle node of the linked list
    """
    def middleNode(self, head):
        # write your code here
        slow, fast = head, head
        while fast and fast.next and fast.next.next:
            slow = slow.next
            fast = fast.next.next
        return slow

 

 

快速排序(Quick Sort)和归并排序(Merge Sort)是算法面试必修的两个基础知识点。不少的算法面试题,要么是直接问这两个算法,要么是这两个算法的变化,要么是用到了这两个算法中一样的思想或者实现方式,要么是挑出这两个算法中的某个步骤来考察。

Partition 模板

 相向双指针模板3---中等难度

注意:都是<=,不然定出错。

31. 数组划分

中文
English

给出一个整数数组 nums 和一个整数 k。划分数组(即移动数组 nums 中的元素),使得:

  • 全部小于k的元素移到左边
  • 全部大于等于k的元素移到右边

返回数组划分的位置,即数组中第一个位置 i,知足 nums[i] 大于等于 k

样例

例1:

输入:
[],9
输出:
0

例2:

输入:
[3,2,2,1],2
输出:1
解释:
真实的数组为[1,2,2,3].因此返回 1

挑战

使用 O(n) 的时间复杂度在数组上进行划分。

注意事项

你应该真正的划分数组 nums,而不只仅只是计算比 k 小的整数数,若是数组 nums 中的全部元素都比 k 小,则返回 nums.length。

class Solution:
    """
    @param nums: The integer array you should partition
    @param k: An integer
    @return: The index after partition
    """
    def partitionArray(self, nums, k):
        # write your code here
        if not nums:
            return 0
            
        left, right = 0, len(nums)-1
        while left <= right:
            while left <= right and nums[left] < k:
                left += 1
            while left <= right and nums[right] >= k:
                right -= 1
            if left <= right:
                nums[left], nums[right] = nums[right], nums[left]
                left += 1
                right -= 1
        return left

 

63. 整数排序

中文
English

给一组整数,按照升序排序,使用选择排序,冒泡排序,插入排序或者任何 O(n2) 的排序算法。

样例

样例  1:
	输入:  [3, 2, 1, 4, 5]
	输出:  [1, 2, 3, 4, 5]
	
	样例解释: 
	返回排序后的数组。

样例 2:
	输入:  [1, 1, 2, 1, 1]
	输出:  [1, 1, 1, 1, 2]
	
	样例解释: 
	返回排好序的数组。
快速排序:
class Solution:
    """
    @param A: an integer array
    @return: nothing
    """
    def sortIntegers(self, A):
        # write your code here
        self.qsort(A, start=0, end=len(A)-1)
            
    
    def qsort(self, nums, start, end):
        if start >= end:
            return
        
        left, right = start, end
        pivot = nums[(left+right)//2]
        while left <= right:
            while left <= right and nums[left] < pivot:
                left += 1
            while left <= right and nums[right] > pivot:
                right -= 1
            if left <= right:
                nums[left], nums[right] = nums[right], nums[left]
                left += 1
                right -= 1
        
        self.qsort(nums, start, right)
        self.qsort(nums, left, end)

 

5. 第k大元素

中文
English

在数组中找到第 k 大的元素。

样例

样例 1:

输入:
n = 1, nums = [1,3,4,2]
输出:
4

样例 2:

输入:
n = 3, nums = [9,3,2,4,8]
输出:
4

挑战

要求时间复杂度为O(n),空间复杂度为O(1)。

注意事项

你能够交换数组中的元素的位置

class Solution:
    # @param k & A a integer and an array
    # @return ans a integer
    def kthLargestElement(self, k, A):
        if not A or k < 1 or k > len(A):
            return None
        return self.partition(A, 0, len(A) - 1, len(A) - k)
        
    def partition(self, nums, start, end, k):
        """
        During the process, it's guaranteed start <= k <= end
        """
        if start == end:
            return nums[k]
            
        left, right = start, end
        pivot = nums[(start + end) // 2]
        while left <= right:
            while left <= right and nums[left] < pivot:
                left += 1
            while left <= right and nums[right] > pivot:
                right -= 1
            if left <= right:
                nums[left], nums[right] = nums[right], nums[left]
                left, right = left + 1, right - 1
                
        # left is not bigger than right
        if k <= right:
            return self.partition(nums, start, right, k)
        if k >= left:
            return self.partition(nums, left, end, k)
        
        return nums[k]
相关文章
相关标签/搜索