Leetcode题解 - 分治算法

本系列为Leetcode刷题笔记,刷题平台为Leetcode中文站, 刷题按Tag分类。本系列题解汇总如下 (持续更新…):

本文主要是分治算法相关题目题解总结。

[TOC]

分治算法

基本概念

分治法字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或者更多的相同或相似的子问题,再把子问题分成更小的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。如快速排序,归并排序。

基本思想及策略

分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
分治法的策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。

分治法使用场景

分治法所能解决的问题一般具有以下几个特征:

  1. 该问题的规模缩小到一定的程度就可以容易地解决;
  2. 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质;
  3. 利用该问题分解出的子问题的解可以合并为该问题的解;
  4. 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题;
  • 第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;
  • 第二条特征是应用分治法的前提,它也是大多数问题可以满足的,此特征反映了递归思想的应用;
  • 第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑使用贪心法或者动态规划法;
  • 第四条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可以使用分治法,但一般动态规划较好。

分治法的基本步骤

分治法在每一层递归上都有三个步骤:

  1. 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
  2. 解决:若子问题规模较小容易被解决则直接解,否则递归地解各个子问题;
  3. 合并:将各个子问题的解合并为原问题的解。

分治法的应用

二分搜索
大整数乘法
Strassen矩阵乘法
棋盘覆盖
合并排序
快速排序
线性时间选择
最接近点对问题
循环赛日程表
汉诺塔

二分搜索

  • 二分搜索的要求:线性表为有序表,并且要用向量作为表的存储结构;
  • 二分搜索得基本思想:先确定待查找记录所在的范围,然后逐步缩小范围直至找到或找不到该记录位置。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Solution(object):
    def searchRange(self, nums, key):
    """
    :type nums: List[int]
    :type target: int
    :rtype: List[int]
    """
    return self.bSearch(nums, 0, len(nums)-1,key)

    def bSearch(self, nums, left, right, key):
    mid = (left+right) // 2
    if nums[mid] == key:
    return mid
    elif nums[mid] > key:
    return self.bSearch(nums,left,mid-1,key)
    else:
    return self.bSearch(nums, mid+1,right,key)

汉诺塔

从左到右 A B C 柱 大盘子在下, 小盘子在上, 借助B柱将所有盘子从A柱移动到C柱, 期间只有一个原则: 大盘子只能在小盘子的下面。
求解思路:

  1. 当盘子只有一个的时候,只有一个动作 从 A 移动到 C 即结束;
  2. 当有N个盘子的时候, 中间的动作是从 A 移动到 C, 表示最下面的第N个盘子移动完毕;
  3. 中间动作之上都可以认为是: 从 A 移动到 B;
  4. 中间动作之下都可以认为是: 从 B 移动到 C。



1
2
3
4
5
6
7
8
9
10
class Solution(object):
def move(self, n, a, b, c):
if n == 1:
print(a+'->'+c)
else:
self.move(n-1, a, c, b)
print(a+'->'+ c)
self.move(n-1, b, a, c)
if __name__ == '__main__':
Solution().move(3,'a','b','c')

参考

https://www.cnblogs.com/xsyfl/p/6921687.html
https://blog.csdn.net/not_guy/article/details/72823951

241. 为运算表达式设计优先级

题目描述

给定一个含有数字和运算符的字符串,为表达式添加括号,改变其运算优先级以求出不同的结果。你需要给出所有可能的组合的结果。有效的运算符号包含 +, - 以及 * 。

  • 示例 1:

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

  • 示例 2:

    输入: “23-45”
    输出: [-34, -14, -10, -10, 10]
    解释:
    (2(3-(45))) = -34
    ((23)-(45)) = -14
    ((2(3-4))5) = -10
    (2((3-4)5)) = -10
    (((23)-4)5) = 10

解题思路

如果字符串为数字直接返回;
使用分治法,遍历字符串,当遇到运算符时,将字符串分为运算符前及运算符后两部分,根据运算符做相应运算。

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
class Solution(object):
def diffWaysToCompute(self, input):
"""
:type input: str
:rtype: List[int]
"""
if input.isdigit():
return [int(input)]
res = []
for i in range(len(input)):
if input[i] in '+-*':
left = self.diffWaysToCompute(input[:i])
right = self.diffWaysToCompute(input[i+1:])
for j in left:
for k in right:
res.append(self.helper(j, k, input[i]))
return res

def helper(self, j, k, op):
if op == '+':
return j + k
elif op == '-':
return j - k
elif op == '*':
return j * k

if __name__ == '__main__':
result = Solution().diffWaysToCompute("2*3-4*5")
print(result)

169. 求众数

题目描述

给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在众数。

  • 示例 1:

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

  • 示例 2:

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

解题思路

摩尔投票法

维护一个候选数candidate和计数器counter。遍历数组中所有的元素, 设当前的元素为x,若 counter = 0,则 candidate = x, counter = 1; 否则, 根据candidate 与x是否相等来更新counter(相等+1,不等-1)
在遍历一次,判断候选数是否为合法的主元素。
为什么这样做是对的呢?因为若在有解的情况下,一个元素y出现>n/2次,那么要抵消掉它,必然也要有相同的元素才行,而总的元素才n个,也就是说元素y在这样的计数中不会被抵消。保证有解的情况最后的候选数就是主要元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution(object):
def majorityElement(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
candidate = 0
count = 0
for x in nums:
if count == 0:
count = 1
candidate = x
elif x == candidate:
count += 1
else:
count -= 1
return candidate
if __name__ == '__main__':
result = Solution().majorityElement([6,5,5])
print(result)

215. 数组中的第K个最大元素

题目描述

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

  • 示例 1:

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

  • 示例 2:

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

  • 说明:

    你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

解题思路

  1. 堆排序。

    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
    class Solution(object):
    def findKthLargest(self, nums, k):
    """
    :type nums: List[int]
    :type k: int
    :rtype: int
    """

    if len(nums) < k or k <= 0:
    return 0

    res = nums[:k]
    change = True
    for i in range(k, len(nums)+1):
    if change:
    for j in range(k//2, -1, -1):
    self.adjust(res, j, k)

    for j in range(k-1, 0, -1):
    res[j], res[0] = res[0], res[j]
    self.adjust(res, 0, j)

    change = False
    if i != len(nums) and nums[i] > res[-1]:
    res[-1] = nums[i]
    change = True

    return res[-1]

    def adjust(self, res, parent, length):
    temp = res[parent]
    child = 2*parent+1
    while child < length:
    if child+1 < length and res[child+1] < res[child]:
    child += 1
    if temp < res[child]:
    break
    res[parent] = res[child]
    parent = child
    child = 2*parent+1
    res[parent] = temp
  2. 分治算法,这里使用快速排序算法。

    • 通过一趟排序将要排序的数据分割成两个独立的两部分:左部分都是比它小的数,右部分都是比它大的数;
    • 然后在按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,最终使整个数据变成有序序列。
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
class Solution(object):
def findKthLargest(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""

if len(nums) < k or k <= 0:
return 0

self.Qsort(nums, 0, len(nums)-1)
return nums[-k]

def Qsort(self, nums, left, right):
if left < right:
index = self.division(nums, left, right)
self.Qsort(nums, left, index-1)
self.Qsort(nums, index+1, right)

def division(self, nums, left, right):
base = nums[left]
while left < right:

while right > left and nums[right] >= base:
right -= 1
nums[left] = nums[right]
while left < right and nums[left] <= base:
left += 1
nums[right] = nums[left]
nums[left] = base
return left
Donate comment here
------------The End------------
0%