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

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

 

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

 

 

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

相向双指针模板1

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。
返回这两个数。

相向双指针模板2  ==》要体会其精髓,L,R两个指针是在靠target接近的方向移动的,因此,在移动的循环过程中,可以找到所有尽可能接近的数,对于那些没有必要的求和就省去了。举例:1,3,4,8,10 target=7,1+10>7,要移动R指针,因为移动L指针没有意义!3+10>1+10是离target更远了!==》体会这个精髓以后,对于变种类题目才能应对自如。

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相遇还没有找到就说明没有解。

 

看一个题目:是比较巧妙的双指针使用!!!

277. 搜寻名人

假设你是一个专业的狗仔,参加了一个 n 人派对,其中每个人被从 0 到 n - 1 标号。在这个派对人群当中可能存在一位 “名人”。所谓 “名人” 的定义是:其他所有 n - 1 个人都认识他/她,而他/她并不认识其他任何人。

现在你想要确认这个 “名人” 是谁,或者确定这里没有 “名人”。而你唯一能做的就是问诸如 “A 你好呀,请问你认不认识 B呀?” 的问题,以确定 A 是否认识 B。你需要在(渐近意义上)尽可能少的问题内来确定这位 “名人” 是谁(或者确定这里没有 “名人”)。

在本题中,你可以使用辅助函数 bool knows(a, b) 获取到 A 是否认识 B。请你来实现一个函数 int findCelebrity(n)

派对最多只会有一个 “名人” 参加。若 “名人” 存在,请返回他/她的编号;若 “名人” 不存在,请返回 -1

 

示例 1:

输入: graph = [
  [1,1,0],
  [0,1,0],
  [1,1,1]
]
输出: 1
解释: 有编号分别为 0、1 和 2 的三个人。graph[i][j] = 1 代表编号为 i 的人认识编号为 j 的人,而 graph[i][j] = 0 则代表编号为 i 的人不认识编号为 j 的人。“名人” 是编号 1 的人,因为 0 和 2 均认识他/她,但 1 不认识任何人。

示例 2:

输入: graph = [
  [1,0,1],
  [1,1,0],
  [0,1,1]
]
输出: -1
解释: 没有 “名人”

 

class Solution:
    def findCelebrity(self, n: int) -> int:
        left = 0
        right = n - 1
        # 先排除掉不是名人的候选人
        while (left != right):
            if (knows(left, right)):
                left += 1            
            else:
                right -= 1                
        # 最后left和right会相遇,这个人是潜在的名人
        res = left 
        for i in range(n):
            if res == i:
                continue

            if knows(res, i) or not knows(i, res):
                return -1                    

        return res

 

 

同向双指针

同向双指针的问题,是指两根指针都从头出发,朝着同一个方向前进。我们通过下面 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 = 0
for i in range(len(nums)):
if j == i:
j = i+1
while j < len(nums) and nums[j]-nums[i] < target: j += 1 if nums[j]-nums[i] == target: # 找到答案

 

如果不加前面if j==i的判断,则当target=0的时候,nums=[1,2,3,4,4]:跟踪下i,j发现i跑在j前面的,这就GG了。

Before i= 0 j= 1
After i= 0 j= 1
Before i= 1 j= 1
After i= 1 j= 1
Before i= 2 j= 1
After i= 2 j= 2
Before i= 3 j= 2
After i= 3 j= 3
Before i= 4 j= 3
After i= 4 j= 3
(4, 4)

 

最长的不重复子串。==>这个题目比较典,可以作为模板

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:

输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        ans = 0
        r = 0
        longest_unrepeated= set()
        n = len(s)
        for l in range(n):
            while r < n and s[r] not in longest_unrepeated:
                longest_unrepeated.add(s[r])                
                r += 1
 
            ans = max(ans, r - l)
            longest_unrepeated.remove(s[l])
 
        return ans

 

 

 

两数和 - 差等于目标值 · Two Sum - Difference equals to target

描述

给定一个排序后的整数数组,找到两个数的 等于目标值。
你需要返回一个包含两个数字的列表 [num1, num2], 使得 num1num2 的差为 target,同时 num1 必须小于 num2

 

保证只有一个答案。
注意:要求用O(1)空间复杂度完成。

样例

例1:

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

例2:

输入: nums = [1, 1], target = 0
输出: [1, 1] 
解释:
(1 - 1 = 0)
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

上面写法有点蛋疼:尤其是j=1,if i != j属于亡羊补牢的做法,应该防患于未然!如下:

from typing import (
    List,
)

class Solution:
    """
    @param nums: an array of Integer
    @param target: an integer
    @return: [num1, num2] (index1 < index2)
    """
    def two_sum7(self, nums: List[int], target: int) -> List[int]:
        # write your code here
        target = abs(target)
        n = len(nums)
        j = 0
        for i in range(n):            
            if j == i: # 两个指针追尾了,要让j往前挪一步,确保二者不重复
                j = i + 1

            while j < n and nums[j] - nums[i] < target:
                j += 1
            
            if j == n:
                break
                
            if nums[j] - nums[i] == target:
                return (nums[i], nums[j])
        
        return ()

 

 

相似问题

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 即可。

 

406. 和大于S的最小子数组

中文
English

给定一个由 n 个正整数组成的数组和一个正整数 s ,请找出该数组中满足其和 ≥ s 的最小长度子数组。如果无解,则返回 -1。

样例

样例 1:

输入: [2,3,1,2,4,3], s = 7
输出: 2
解释: 子数组 [4,3] 是该条件下的最小长度子数组。

样例 2:

输入: [1, 2, 3, 4, 5], s = 100
输出: -1

挑战

如果你已经完成了O(nlogn)时间复杂度的编程,请再试试 O(n)时间复杂度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Solution:
    """
    @param nums: an array of integers
    @param s: An integer
    @return: an integer representing the minimum size of subarray
    """
    def minimumSize2(self, nums, s):
        # write your code here
        ans = float('inf')
        n = len(nums)
 
        j = 0
        s2 = 0
        for i in range(n):
            while j < n and s2 + nums[j] < s:
                s2 += nums[j]
                j += 1
 
            if j == n:
                break
 
            ans = min(ans, j - i + 1)
 
            # s2 += nums[j]
            s2 -= nums[i]
 
 
        return ans if ans != float('inf') else -1
         
     
    def minimumSize(self, nums, s):
        # write your code here
        ans = float('inf')
        n = len(nums)
 
        j = 0
        s2 = 0
        for i in range(n):
            while j < n and s2 < s:
                s2 += nums[j]
                j += 1
 
            if s2 >= s:
                ans = min(ans, j - i)
 
            s2 -= nums[i]
 
        return ans if ans != float('inf') else -1

 

同向双指针, 模版1是强化班侯卫东老师介绍的 模版2是高频班老顽童老师介绍的

# 模版1==》我的话,选择这种!!!
class Solution:
    """
    @param nums: an array of integers
    @param s: An integer
    @return: an integer representing the minimum size of subarray
    """
    def minimumSize(self, nums, s):
        # write your code here
        
        
        left, right = 0, 0 
        
        n = len(nums)
        
        target = s 
        
        addup = 0 
        
        ans = sys.maxsize 
        
        for left in range(n):
            
            while right < n and addup < target: # 不满足条件去扩张right
                
                addup += nums[right]
                
                right += 1 
                
            if addup >= target: # 满足条件
                
                ans = min(right - left, ans)
                
            addup -= nums[left] # 收缩left
            
            
        return -1 if ans == sys.maxsize else ans 

# 模版2: 枚举右端点,左端点不回头
class Solution:
    """
    @param nums: an array of integers
    @param s: An integer
    @return: an integer representing the minimum size of subarray
    """                
            
        
    def minimumSize(self, nums, s):
        
        ans = sys.maxsize 
        
        left = 0 
        
        addup = 0 
        
        for right in range(len(nums)):
            
            addup += nums[right]
            
            while addup >= s:
                
                ans = min(ans, right - left + 1)
                
                addup -= nums[left]
                
                left += 1 
                
        return ans if ans != sys.maxsize else -1

384. 最长无重复字符的子串 经典双指针题目((⊙o⊙)…,当初美团面试遇到过)

中文
English

给定一个字符串,请找出其中无重复字符的最长子字符串。

样例

样例 1:

输入: "abcabcbb"
输出: 3
解释: 最长子串是 "abc".

样例 2:

输入: "bbbbb"
输出: 1
解释: 最长子串是 "b".

挑战

O(n) 时间复杂度

class Solution:
    """
    @param s: a string
    @return: an integer
    """
    def lengthOfLongestSubstring(self, s):
        # write your code here
        ans = 0
        l, r = 0, 0
        found = set()
        n = len(s)
        for l in range(n):
            while r < n and s[r] not in found:
                found.add(s[r])
                ans = max(ans, r-l+1)
                r += 1

            found.remove(s[l])

        return ans

 

32. 最小子串覆盖

中文
English

给定两个字符串 sourcetarget. 求 source 中最短的包含 target 中每一个字符的子串.

样例

样例 1:

输入: source = "abc", target = "ac"
输出: "abc"

样例 2:

输入: source = "adobecodebanc", target = "abc"
输出: "banc"
解释: "banc" 是 source 的包含 target 的每一个字符的最短的子串.

样例 3:

输入: source = "abc", target = "aa"
输出: ""
解释: 没有子串包含两个 'a'.

挑战

O(n) 时间复杂度

注意事项

  1. 如果没有答案, 返回 "".
  2. 保证答案是唯一的.
  3. target 可能包含重复的字符, 而你的答案需要包含至少相同数量的该字符.

 

class Solution:
    """
    @param source : A string
    @param target: A string
    @return: A string denote the minimum window, return "" if there is no such a string
    """

    def minWindow(self, source, target):
        # write your code here
        ans = ""
        length = float("inf")
        r = 0
        n = len(source)

        target_cnt = collections.Counter(target)
        found = collections.Counter()
        for l in range(n):
            while r < n and not self.contains_all(target_cnt, found):
                found[source[r]] += 1
                r += 1

            if self.contains_all(target_cnt, found):
                if length > r - l:
                    ans = source[l:r]
                    length = r - l

            found[source[l]] -= 1

        return ans

    def contains_all(self, target_cnt, found):
        for c, n in target_cnt.items():
            if found[c] < n:
                return False

        return True

模子是一样的,虽然有少数用例还超时,需优化!!优化思路:就是记录char出现次数,加进去的时候,看是否满足目标!!!移走的时候也看是否小于了目标次数!!!

 

 

链表中点问题

问题描述

求一个链表的中点

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---中等难度------------不建议使用

注意:都是<=,否则定出错。

 

上面的模板我自己用起来并不满意,还是使用同向双指针的写法更直观。

另外一种直观的模板写法,快速排序,使用同向双指针模板(基本上和前面没有区别)

def partition(arr, left, right):
    pivot = arr[left]
    index = left + 1  # 注意index起始位置是left+1
    for i in range(left + 1, right + 1):  # 循环的起始位置也是left+1
        if arr[i] < pivot:  # <= 也是可以的
            arr[i], arr[index] = arr[index], arr[i]
            index += 1

    pivot_pos = index - 1
    arr[left], arr[pivot_pos] = arr[pivot_pos], arr[left]
    return pivot_pos

def qsort(arr, start, end):
    if start >= end: return
    index = partition(arr, start, end)
    qsort(arr, start, index - 1)  # 注意是index-1,因为index这个pivot位置必定排序好了
    qsort(arr, index + 1, end)  # 注意是index+1

arr = [1221,21, 21212,222,-1,100,100,220, 330,192321983]
qsort(arr, 0, len(arr)-1)
print(arr)

  

 

# use leftest for pivot index
def partition(arr, left, right):
    pivot = arr[left]
    """
    # 使用中位数作为pivot:
    mid = (left + right) // 2
    pivot = arr[mid]
    arr[left], arr[mid] = arr[mid], arr[left]
    """
   ....

 


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 arr: The integer array you should partition
    @param k: An integer
    @return: The index after partition
    """
    def partitionArray(self, arr, k):
        # write your code here
        ans = 0
        index = 0
        for i in range(0, len(arr)):
            if arr[i] < k:
                arr[i],arr[index] = arr[index],arr[i]
                index += 1
                ans = index
        return ans

 

双向双指针做法,写起来比较痛苦,不建议用:

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

 

461 · 无序数组K小元素

算法
中等
通过率48%

 
题目
题解
笔记
讨论
排名
 
 
描述

找到一个无序数组中第 K 小的数(K 从1开始)。

样例

样例 1:

输入: [3, 4, 1, 2, 5], k = 3
输出: 3

样例 2:

输入: [1, 1, 1], k = 2
输出: 1
挑战

O(nlogn)的算法固然可行, 但如果你能 O(n) 解决, 那就非常棒了.

from typing import (
    List,
)

class Solution:
    """
    @param k: An integer
    @param nums: An integer array
    @return: kth smallest element
    """
    def kth_smallest(self, k: int, nums: List[int]) -> int:
        # write your code here
        kth = k - 1
        n = len(nums) - 1
        index = self.find_kth(nums, 0, n, kth)
        return nums[index]

    def find_kth(self, nums, low, high, kth):        
        mid = self.partition(nums, low, high)
        if mid == kth:
            return mid
        elif mid < kth:
            return self.find_kth(nums, mid+1, high, kth)
        else:
            return self.find_kth(nums, low, mid-1, kth)
    
    def partition(self, nums, low, high):
        pivot = nums[low]
        i = j = low + 1
        for j in range(low+1, high+1):
            if nums[j] <= pivot:
                nums[i], nums[j] = nums[j], nums[i]
                i += 1
        index = i - 1
        nums[index], nums[low] = nums[low], nums[index]
        return index

 

 

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 patition(self, arr, left, right):
        pivot = arr[left]
        index = left+1
        for i in range(left+1, right+1):
            if arr[i] < pivot:
                arr[i],arr[index] = arr[index],arr[i]
                index += 1
        pivot_index = index-1
        arr[left], arr[pivot_index] = arr[pivot_index], arr[left]
        return pivot_index
        
    def qsort(self, nums, start, end):
        if start >= end:
            return
        
        index = self.patition(nums, start, end)
        self.qsort(nums, start, index-1)
        self.qsort(nums, index+1, end)

 相向双指针写法(更复杂):

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)

 

 

461. 无序数组K小元素

中文
English

找到一个无序数组中第K小的数

样例

样例 1:

输入: [3, 4, 1, 2, 5], k = 3
输出: 3

样例 2:

输入: [1, 1, 1], k = 2
输出: 1

挑战

O(nlogn)的算法固然可行, 但如果你能 O(n) 解决, 那就非常棒了.

class Solution:
    """
    @param k: An integer
    @param nums: An integer array
    @return: kth smallest element
    """
    def kthSmallest(self, k, nums):
        # write your code here
        return self.find_kth(k-1, nums, left=0, right=len(nums)-1)
        
    def patition(self, arr, left, right):
        pivot = arr[left]
        index = left+1
        for i in range(left+1, right+1):
            if arr[i] < pivot:
                arr[i],arr[index] = arr[index],arr[i]
                index += 1
        pivot_index = index-1
        arr[left], arr[pivot_index] = arr[pivot_index], arr[left]
        return pivot_index
        
        
    def find_kth(self, k, nums, left, right):    
        index = self.patition(nums, left, right)
        if index == k:
            return nums[k]
        elif index > k:
            return self.find_kth(k, nums, left, index-1)
        else:
            return self.find_kth(k, nums, index+1, right)

            

 相向双指针写法(更复杂):

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]

373. 奇偶分割数组

中文
English

分割一个整数数组,使得奇数在前偶数在后。

样例

样例1:

输入: [1,2,3,4]
输出: [1,3,2,4]

样例2:

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

挑战

在原数组中完成,不使用额外空间。

注意事项

答案不唯一。你只需要给出一个合法的答案。

class Solution:
    """
    @param: nums: an array of integers
    @return: nothing
    """
    def partitionArray(self, nums):
        # write your code here
        index = 0
        for i in range(0, len(nums)):
            if nums[i] & 1:
                nums[index], nums[i] = nums[i], nums[index]
                index += 1
        

 

144. 交错正负数

中文
English

给出一个含有正整数和负整数的数组,重新排列成一个正负数交错的数组。

样例

样例 1

输入 : [-1, -2, -3, 4, 5, 6]
输出 : [-1, 5, -2, 4, -3, 6]
解释 : 或者仍和满足条件的答案 

挑战

完成题目,且不消耗额外的空间。

注意事项

不需要保持正整数或者负整数原来的顺序。

 

没啥意思的题目:

class Solution:
    """
    @param: A: An integer array.
    @return: nothing
    """
    def rerange(self, A):
        # write your code here
        index = 0
        for i in range(0, len(A)):
            if A[i] > 0:
                A[index], A[i] = A[i], A[index]
                index += 1
        # A[0:index] > 0, A[index:] < 0
        assert A[index] < 0
        j = 0 if index == len(A)//2 else 1
        while index < len(A) and j < len(A):
            A[j], A[index] = A[index], A[j]
            index += 1
            j += 2

 

49. 字符大小写排序

中文
English

给定一个只包含字母的字符串,按照先小写字母后大写字母的顺序进行排序。

样例

样例 1:
	输入:  "abAcD"
	输出:  "acbAD"

样例 2:
	输入: "ABC"
	输出:  "ABC"
	

挑战

在原地扫描一遍完成

注意事项

小写字母或者大写字母他们之间不一定要保持在原始字符串中的相对位置。

class Solution:
    """
    @param: chars: The letter array you should sort by Case
    @return: nothing
    """
    def sortLetters(self, chars):
        # write your code here
        chars2 = list(chars)
        index = 0
        for i in range(0, len(chars2)):
            if ord('a') < ord(chars2[i]) < ord('z'):
                chars2[index], chars2[i] = chars2[i], chars2[index]
                index += 1
        return "".join(chars2)

 

148. 颜色分类

中文
English

给定一个包含红,白,蓝且长度为 n 的数组,将数组元素进行分类使相同颜色的元素相邻,并按照红、白、蓝的顺序进行排序。

我们可以使用整数 0,1 和 2 分别代表红,白,蓝。

样例

样例 1

输入 : [1, 0, 1, 2]
输出 : [0, 1, 1, 2]
解释 : 原地排序。

挑战

一个相当直接的解决方案是使用计数排序扫描2遍的算法。

首先,迭代数组计算 0,1,2 出现的次数,然后依次用 0,1,2 出现的次数去覆盖数组。

你否能想出一个仅使用常数级额外空间复杂度且只扫描遍历一遍数组的算法?

注意事项

不能使用代码库中的排序函数来解决这个问题。
排序需要在原数组中进行。

做两次 Partition。先把0和非0分开,再把1和非1分开。

class Solution:
    """
    @param nums: A list of integer which is 0, 1 or 2 
    @return: nothing
    """
    def sortColors(self, nums):
        # write your code here
        index = 0
        for i in range(0, len(nums)):
            if nums[i] == 0:
                nums[i], nums[index] = nums[index], nums[i]
                index += 1
        assert nums[index-1] == 0
        assert nums[index] != 0
    
        for i in range(index, len(nums)):
            if nums[i] == 1:
                nums[i], nums[index] = nums[index], nums[i]
                index += 1
        
        assert nums[index-1] == 1
        assert nums[index] == 2
        

 计数排序:

class Solution:
    """
    @param nums: A list of integer which is 0, 1 or 2 
    @return: nothing
    """
    def sortColors(self, nums):
        # write your code here
        cnt_map = {0:0, 1:0, 2:0}
        for n in nums:
            cnt_map[n] += 1
        
        i = 0
        for n in (0, 1, 2):
            for j in range(0, cnt_map[n]):
                nums[i] = n
                i += 1
        

 

 

143. 排颜色 II

中文
English

给定一个有n个对象(包括k种不同的颜色,并按照1到k进行编号)的数组,将对象进行分类使相同颜色的对象相邻,并按照1,2,...k的顺序进行排序。

样例

样例1

输入: 
[3,2,2,1,4] 
4
输出: 
[1,2,2,3,4]

样例2

输入: 
[2,1,1,2,2] 
2
输出: 
[1,1,2,2,2]

挑战

一个相当直接的解决方案是使用计数排序扫描2遍的算法。这样你会花费O(k)的额外空间。你否能在不使用额外空间的情况下完成?

注意事项

  1. 不能使用代码库中的排序函数来解决这个问题
  2. k <= n
class Solution:
    """
    @param nums: A list of integer
    @param k: An integer
    @return: nothing
    """
    def sortColors2(self, nums, k):
        # write your code here
        index = 0
        for target in range(1, k+1):
            for i in range(index, len(nums)):
                if nums[i] == target:
                    nums[i], nums[index] = nums[index], nums[i]
                    index += 1

 

539. 移动零

中文
English

给一个数组 nums 写一个函数将 0 移动到数组的最后面,非零元素保持原数组的顺序

样例

例1:

输入: nums = [0, 1, 0, 3, 12],
输出: [1, 3, 12, 0, 0].

例2:

输入: nums = [0, 0, 0, 3, 1],
输出: [3, 1, 0, 0, 0].

注意事项

1.必须在原数组上操作
2.最小化操作数

class Solution:
    """
    @param nums: an integer array
    @return: nothing
    """
    def moveZeroes(self, nums):
        # write your code here
        index = 0
        for i in range(0, len(nums)):
            if nums[i] != 0:
                nums[index], nums[i] = nums[i], nums[index]
                index += 1

 

posted @ 2019-11-03 21:43  bonelee  阅读(3863)  评论(10编辑  收藏  举报