雙指針算法模板和一些題目

什麼是同向雙指針? 什麼是相向雙指針?
雙指針的鼻祖題 —— 兩數之和 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]
相關文章
相關標籤/搜索