leetcode_315_ reverse problem

Title Description

The title from Leetcode algorithm 315 exam questions, specific topics as follows:

Given an numsarray of integers, as required to return a countsnew array. The array countshas the properties: counts[i]The value of nums[i]the right side is smaller than the nums[i]number of elements.

Examples given:

Input: [5,2,6,1]
Output: [2,1,1,0]
Explanation:
right side 5 has two smaller elements (1 and 2)
only one of the more right 2 small elements (1).
6 right has a smaller elements (1)
on the right side has a smaller elements 0.

Most likely to think of problem-solving ideas

The subject looks very easy to read and very easy to start, but on the difficulty setting is Hard , in fact, the biggest problem is the complexity of the problem, the basic idea is to traverse the array input, comparing each element and its element after using a counter to record and added to the output array, the complexity of such an algorithm is clearly $ O (n ^ 2) $ , code is as follows:

1
2
3
4
5
6
7
8
9
10
class :
def countSmaller(self, nums):
result = []
for i in range(len(nums)):
temp = 0
for j in range(i,len(nums)):
if nums[i] > nums[j]:
temp += 1
result.append(temp)
return result

But to do so it will be submitted directly to TLE, and therefore need to find other new ideas.

Reverse problem

Observe this problem will find that you can essentially consider this issue as a reverse problem, reverse problem issues described as follows:

Given a number $ a_1, a_2, cdots, a_n $, find the number of ordered pairs $ (i, j) $ such that $ i <j $ but $ a_i> a_j $, value $ n-$ herein can be achieved $ 10 ^ 6 $

The problem is similar, violent direct solving method can be used to loop through the complexity is $ O (n ^ 2) $, is clearly time out, and therefore need to consider other solution here is to learn merge sort method.

Merge sort

Thought merge sort shown below:

Merge Sort

The core idea is to divide and conquer the idea to get in a disorderly array, we divide them binary, recursive partitioning to each interior is only one number, in reverse "and" operation carried out at all the data up orderly consolidation, ultimately orderly array. The figure just to the split that step operation. About algorithmic complexity of merge sort is also very easy to calculate, binary data is first split, the algorithm complexity be $ O (log N) $, and the complexity of the sorting process is $ O (N) $, so this complexity of the algorithm is $ O (Nlog N) $. Python code is implemented is given below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class : DEF merge_sort (Self, the nums) : IF len (the nums) == . 1 : return the nums        MID = len (the nums) // 2         left the nums = [: MID]        right = the nums [MID:]        left_list self.merge_sort = ( Self, left)        right_list = self.merge_sort (Self, right)        Result = [] # for storing the sorted array finished # define two pointers are used for the left and right sides of the array         left_pointer = right_pointer = 0 the while left_pointer <len (left_list) and right_pointer <len (right_list): IF left_list [left_pointer] <right_list [right_pointer]:














result.append (left_list [left_pointer])
left_pointer + = . 1 the else : result.append (right_list [right_pointer]) right_pointer + = . 1 # array about the last remaining elements exist, directly added to the final result + = left_list [left_pointer: ] Result + = right_list [right_pointer:] return Result







Back to the reverse of the problem

Reference method merge sort, reverse solving the problem can be divided into three steps:

  • Partitioning problem: the original sequence into two subsequences of equal length as
  • Recursive procedure: Statistics about to reverse subsequence
  • After the reverse order of the sequence of statistics about the merger: merger

A map can more clearly understand the relationship between reverse and merge sort of:

And merge sort of the reverse

When resultwhen placed one element, another array of data arrays in which it will actually constitutes its reverse pair. The reverse may be calculated on the number of reverse when the number of arrays can also be used behind a row calculation using the previous column array elements.


Calculating the code is given below in reverse order:

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
# Recursively implementation of Merge Sort and Inversion Pairs counts
number = 0
def merge(left, right):
result = []
global number
while left and right:
if left[0] <= right[0]:
result.append(left.pop(0))
#count += len(right)
else:
result.append(right.pop(0))
number += len(left)
if left:
result += left
if right:
result += right
return result

def merge_sort(L):
if len(L) <= 1:
# When D&C to 1 element, just return it
return L
mid = len(L) // 2
left = L[:mid]
right = L[mid:]
left = merge_sort(left)
right = merge_sort(right)
return merge(left, right)
# return the answer of sub-problem
if __name__ == "__main__":
test = [5,2,6,1,3]
print("original:", test)
print("Sorted:", merge_sort(test))
print("Inversion Pairs numbers:",number)

回到出发点Leetcode第315题

刚刚我们已经通过归并排序成功求解了给定一个数组其中的逆序对的个数,现在回到第315题,这个问题虽然不是直接求逆序对,但是很大程度上思想是类似的,因此我们只需要做一些简单的修改就可以实现CountSmaller的要求。

这道题目与逆序对求解最大的不同在于逆序对只要求计数,而这道题目要求的是输出每个元素的右边小于它的元素数量,是更加具体的求解。思想上可以考虑为:left数组中的元素出列时,计算right数组中已经有了多少个元素出列,这么考虑的原因在于那些right数组中已经出列的元素都是小于当前考虑的left数组中的元素。

这里我们需要使用到一个索引数组的概念,原因是基于归并排序的过程元素的位置会发生变动,而我们希望的是得到原始位置下的结果,因此需要使用一个索引来记录元素的初识位置。

index list for record the position of all elements

下面给出实现的代码:

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
49
50
class :
def countSmaller(self, nums):
if len(nums) == 0: return []
if len(nums) == 1: return [0]
#创建一个索引数组用于进行排序
#原数组nums不进行排序,仅仅用作比较大小的作用
index = [i for i in range(len(nums))]
#用于存放最终每一个位置上的smaller的个数
result = [0 for _ in range(len(nums))]
self.merge_sort(self,nums,index,result)
return result


def merge_sort(self,nums,index,result):
if len(index) <= 1:
return index
mid = len(index) // 2
left = index[:mid]
right = index[mid:]
#递归拆分数组直至全部变为一个元素的list为止
left = self.merge_sort(self,nums,left,result)
right = self.merge_sort(self,nums,right,result)
#print(left)
#print(right)
#如果出现了已经构成了有序的数组直接返回
left_pointer = 0
right_pointer = 0
if nums[left[-1]] < nums[right[0]]:
return index
else:
for i in range(len(index)):
if left_pointer >= len(left):
index[i] = right[right_pointer]
#result[index[i]] += len(right)
right_pointer += 1
elif right_pointer >= len(right):
index[i] = left[left_pointer]
left_pointer += 1
result[index[i]] += len(right)
elif nums[left[left_pointer]] <= nums[right[right_pointer]]:
index[i] = left[left_pointer]
left_pointer += 1大专栏  leetcode_315_逆序对问题an>
result[index[i]] += len(right[:right_pointer])
else:
index[i] = right[right_pointer]
#print('process is here')
right_pointer += 1
#result[index[i]] += len(left)
#print('index_list',index)
return index

其他的一些方法

实际上个人认为上面的这种基于归并排序求逆序对的算法理解起来还是很难得,我花了很多很多的时间才理解算法的整个过程,可能过不几天就把这个算法的具体流程给忘了哈哈哈。另外的话在这一道题的Leetcode的交流区还发现了一些大神给出的一些更加简单且易懂的算法。

基于二分查找的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class :
def bisearch(self,tmp,a):
left = 0
right = len(tmp)
while left < right:
mid = (left + right) //2
if tmp[mid] <a:
left = mid +1
else:
right = mid
return left

def countSmaller(self, nums: List[int]) -> List[int]:
ans=[]
tmp = []
for i in range(len(nums)-1,-1,-1):
pos = self.bisearch(tmp, nums[i])
ans.append(pos)
tmp.insert(pos,nums[i])
ans.reverse()
return ans

这个名为“望舒”的大神提供了一种很好的方法,思路基本如下:从给定数组的最后一个开始遍历,使用二分查找法将其插入一个新的数组,这个新的数组会是有序的,那么此时遍历到的那个数字在新数组中的坐标就是原来数组中其右边所有比它小的数字的个数。通过查找插入的位置来得到该数后面比其小的数字的数量。这个算法的复杂度应该是$O(N^2)$。

二叉搜索树(BST)

这里简要给出一下Binary Search Tree的说明,搜索树也被称为排序树,该树构建的核心在于所有的左子树的值都小于根结点的值,所有的右子树的值都大于根节点的值。

给出二叉搜索树的简单实现代码:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
class BTNode:
def __init__(self,data):
self.data = data
self.left = None
self.right = None

class BTree:
def __init__(self,root):
self.root = root

def is_empty(self):
if self.root == None:
return None
'''
查找指定的值
'''
def find_value(self,value):
if self.root == None:
return None
else:
return self.search(value,self.root)
#使用递归寻找数值
def search(self,value,node):
if node == None:
return None
elif node.data == value:
return node
elif value < node.data:
return self.search(value,node.left)
else:
return self.search(value,node.right)
'''
插入指定的值
'''
def insert(self,value):
#创建值为value的结点
node = BTNode(value)
#如果没有根节点则将这个node赋给root
if self.root == None:
self.root = node
else:
#从根节点开始搜索
current_node = self.root
while True:
#如果当前想要插入的值小于根节点则继续往左子树寻找
if value <= current_node.data:
if current_node.left != None:
current_node = current_node.left
else:
current_node.left = node
break
elif value > current_node.data:
if current_node.right != None:
current_node = current_node.right
else:
current_node.right = node
break
else:
break
'''
删除指定的结点
'''
def delete(self,value):
delete_node = self.find_value(value)
if delete_node == None:
raise ValueError("Sorry, Not value in Tree")
else:
#需要删除的是叶子结点则直接删除即可
if delete_node.left == None and delete_node.right == None:
delete_node.data = None
#单分支结点,只需要考虑修改左右子树的位置,这种情况只有右子树
elif delete_node.left == None:
delete_node = delete_node.right
#单分支结点,只需要考虑修改左右子树的位置,这种情况只有左子树
elif delete_node.right == None:
delete_node = delete_node.left
#考虑此时的情况删除节点的左右子树都不为空
elif delete_node.left != None and delete_node.right != None:
pre = delete_node.right
if pre.left == None:
#如果待删除节点的右孩子没有左子树,则待删除节点的整个右子树最小值为其右孩子
delete_node.data = pre.data
delete_node.right = pre.right
del pre
else:
next_node = pre.left
while next_node.left != None:
pre = next_node
next_node = next_node.left
delete_node.data = next_node.data
pre.left = next_node.right
del next_node

#二叉树中序遍历
def print_btree(self,node,result_list):
if node == None:
return node
result_list.append(node.data)
self.print_btree(node.left,result_list)
self.print_btree(node.right,result_list)

def print_function(self):
result_list = []
self.print_btree(self.root,result_list)
print(result_list)

回到原来的题目,使用BST如何计算右侧更小的数据的个数呢?

Mainly based on the idea: For the calculation of the right side is smaller than the current number of rules, from the perspective of the need to maintain a first binary considerations, we reverse traverse a given numsarray, this array using a binary sort tree structure, for example (in the test sample is given as an example [5,2,6,1]) Some more images:

An Toy Example

  1. First, why should insert in reverse order?

    After inserting the reverse we found that for any node nums[i]for which data is to the right of the binary tree have been entered, when it is inserted into the BST, the insertion process is completed, calculation of the number of data is smaller than the right side thereof will get the results.

  2. countSmaller = root.count + 1Meaning?

    When the next node nums[i]is added BST, adding if the rootright subtree, then rootall of the values of the left subtree are less than the number, along with rootitself is less than nums[i]the calculation process becomes very easy.

Guess you like

Origin www.cnblogs.com/lijianming180/p/12302554.html